@sqg/sqg 0.13.0 → 0.14.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/README.md +8 -6
- package/dist/sqg.mjs +356 -6
- package/dist/templates/python.hbs +256 -0
- package/package.json +45 -43
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ queries with it and then generate the code from the same file.
|
|
|
12
12
|
## Features
|
|
13
13
|
|
|
14
14
|
- **Type-safe by design** - Generates fully-typed code with accurate column types inferred from your database
|
|
15
|
-
- **Multiple database engines** - Supports SQLite, DuckDB, and
|
|
16
|
-
- **Multiple language targets** - Generate TypeScript or
|
|
15
|
+
- **Multiple database engines** - Supports SQLite, DuckDB, and PostgreSQL
|
|
16
|
+
- **Multiple language targets** - Generate TypeScript, Java, or Python code from the same SQL files
|
|
17
17
|
- **Arrow API support** - Can generate Apache Arrow API bindings for DuckDB (Java)
|
|
18
18
|
- **DBeaver compatible** - Works seamlessly with DBeaver for database development and testing
|
|
19
19
|
- **Complex type support** - DuckDB: Handles structs, lists, and maps
|
|
@@ -129,11 +129,13 @@ console.log(user?.name);
|
|
|
129
129
|
|
|
130
130
|
| Language | Database | API | Generator | Status |
|
|
131
131
|
|----------|----------|-----|-----------|--------|
|
|
132
|
-
| TypeScript | SQLite | better-sqlite3 | `typescript/
|
|
132
|
+
| TypeScript | SQLite | better-sqlite3 | `typescript/sqlite` | Tested |
|
|
133
133
|
| TypeScript | DuckDB | @duckdb/node-api | `typescript/duckdb` | Tested |
|
|
134
|
-
| Java |
|
|
135
|
-
| Java | DuckDB | Apache Arrow | `java/duckdb
|
|
136
|
-
|
|
|
134
|
+
| Java | SQLite/DuckDB/PostgreSQL | JDBC | `java/sqlite`, `java/duckdb`, `java/postgres` | Tested |
|
|
135
|
+
| Java | DuckDB | Apache Arrow | `java/duckdb/arrow` | Tested |
|
|
136
|
+
| Python | SQLite | sqlite3 | `python/sqlite` | Tested |
|
|
137
|
+
| Python | DuckDB | duckdb | `python/duckdb` | Tested |
|
|
138
|
+
| Python | PostgreSQL | psycopg3 | `python/postgres` | Tested |
|
|
137
139
|
|
|
138
140
|
## CLI Commands
|
|
139
141
|
|
package/dist/sqg.mjs
CHANGED
|
@@ -13,7 +13,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
|
13
13
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
14
14
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
15
15
|
import YAML from "yaml";
|
|
16
|
-
import { camelCase, pascalCase } from "es-toolkit/string";
|
|
16
|
+
import { camelCase, pascalCase, snakeCase } from "es-toolkit/string";
|
|
17
17
|
import Handlebars from "handlebars";
|
|
18
18
|
import { z } from "zod";
|
|
19
19
|
import { DuckDBEnumType, DuckDBInstance, DuckDBListType, DuckDBMapType, DuckDBStructType } from "@duckdb/node-api";
|
|
@@ -25,6 +25,7 @@ import types from "pg-types";
|
|
|
25
25
|
import BetterSqlite3 from "better-sqlite3";
|
|
26
26
|
import prettier from "prettier/standalone";
|
|
27
27
|
import prettierPluginJava from "prettier-plugin-java";
|
|
28
|
+
import { execSync } from "node:child_process";
|
|
28
29
|
import typescriptPlugin from "prettier/parser-typescript";
|
|
29
30
|
import estree from "prettier/plugins/estree";
|
|
30
31
|
import yoctoSpinner from "yocto-spinner";
|
|
@@ -115,6 +116,30 @@ const GENERATORS = {
|
|
|
115
116
|
description: "Java with JDBC for PostgreSQL",
|
|
116
117
|
extension: ".java",
|
|
117
118
|
template: "java-jdbc.hbs"
|
|
119
|
+
},
|
|
120
|
+
"python/sqlite/sqlite3": {
|
|
121
|
+
language: "python",
|
|
122
|
+
engine: "sqlite",
|
|
123
|
+
driver: "sqlite3",
|
|
124
|
+
description: "Python with sqlite3 standard library",
|
|
125
|
+
extension: ".py",
|
|
126
|
+
template: "python.hbs"
|
|
127
|
+
},
|
|
128
|
+
"python/duckdb/duckdb": {
|
|
129
|
+
language: "python",
|
|
130
|
+
engine: "duckdb",
|
|
131
|
+
driver: "duckdb",
|
|
132
|
+
description: "Python with duckdb driver",
|
|
133
|
+
extension: ".py",
|
|
134
|
+
template: "python.hbs"
|
|
135
|
+
},
|
|
136
|
+
"python/postgres/psycopg": {
|
|
137
|
+
language: "python",
|
|
138
|
+
engine: "postgres",
|
|
139
|
+
driver: "psycopg",
|
|
140
|
+
description: "Python with psycopg3 driver",
|
|
141
|
+
extension: ".py",
|
|
142
|
+
template: "python.hbs"
|
|
118
143
|
}
|
|
119
144
|
};
|
|
120
145
|
/** Default drivers for language/engine combinations */
|
|
@@ -123,7 +148,10 @@ const DEFAULT_DRIVERS = {
|
|
|
123
148
|
"typescript/duckdb": "node-api",
|
|
124
149
|
"java/sqlite": "jdbc",
|
|
125
150
|
"java/duckdb": "jdbc",
|
|
126
|
-
"java/postgres": "jdbc"
|
|
151
|
+
"java/postgres": "jdbc",
|
|
152
|
+
"python/sqlite": "sqlite3",
|
|
153
|
+
"python/duckdb": "duckdb",
|
|
154
|
+
"python/postgres": "psycopg"
|
|
127
155
|
};
|
|
128
156
|
/** List of all full generator names */
|
|
129
157
|
const GENERATOR_NAMES = Object.keys(GENERATORS);
|
|
@@ -1033,6 +1061,11 @@ async function initializeDatabase(queries, execQueries, reporter) {
|
|
|
1033
1061
|
|
|
1034
1062
|
//#endregion
|
|
1035
1063
|
//#region src/db/duckdb.ts
|
|
1064
|
+
/** Cache of enum type names, keyed by stringified sorted values for lookup */
|
|
1065
|
+
let enumNameCache = /* @__PURE__ */ new Map();
|
|
1066
|
+
function enumCacheKey(values) {
|
|
1067
|
+
return values.join("\0");
|
|
1068
|
+
}
|
|
1036
1069
|
function convertType(type) {
|
|
1037
1070
|
if (type instanceof DuckDBListType) return new ListType(convertType(type.valueType));
|
|
1038
1071
|
if (type instanceof DuckDBStructType) return new StructType(type.entryTypes.map((t, index) => ({
|
|
@@ -1049,7 +1082,10 @@ function convertType(type) {
|
|
|
1049
1082
|
type: convertType(type.valueType),
|
|
1050
1083
|
nullable: true
|
|
1051
1084
|
});
|
|
1052
|
-
if (type instanceof DuckDBEnumType)
|
|
1085
|
+
if (type instanceof DuckDBEnumType) {
|
|
1086
|
+
const name = type.alias ?? enumNameCache.get(enumCacheKey(type.values));
|
|
1087
|
+
return new EnumType(type.values, name);
|
|
1088
|
+
}
|
|
1053
1089
|
return type.toString();
|
|
1054
1090
|
}
|
|
1055
1091
|
const duckdb = new class {
|
|
@@ -1065,6 +1101,21 @@ const duckdb = new class {
|
|
|
1065
1101
|
throw new SqlExecutionError(e.message, query.id, query.filename, query.rawQuery, e);
|
|
1066
1102
|
}
|
|
1067
1103
|
}, reporter);
|
|
1104
|
+
await this.loadEnumCache();
|
|
1105
|
+
}
|
|
1106
|
+
async loadEnumCache() {
|
|
1107
|
+
enumNameCache = /* @__PURE__ */ new Map();
|
|
1108
|
+
try {
|
|
1109
|
+
const result = await this.connection.runAndReadAll("SELECT type_name, labels FROM duckdb_types() WHERE logical_type = 'ENUM' AND internal = false");
|
|
1110
|
+
for (const row of result.getRows()) {
|
|
1111
|
+
const typeName = row[0];
|
|
1112
|
+
const labels = row[1];
|
|
1113
|
+
if (typeName && labels?.items) enumNameCache.set(enumCacheKey(labels.items), typeName);
|
|
1114
|
+
}
|
|
1115
|
+
consola.debug("DuckDB enum types:", Object.fromEntries(enumNameCache));
|
|
1116
|
+
} catch (e) {
|
|
1117
|
+
consola.debug("Failed to load DuckDB enum types:", e.message);
|
|
1118
|
+
}
|
|
1068
1119
|
}
|
|
1069
1120
|
async executeQueries(queries, reporter) {
|
|
1070
1121
|
const connection = this.connection;
|
|
@@ -1099,7 +1150,11 @@ const duckdb = new class {
|
|
|
1099
1150
|
query.parameterTypes = paramTypes;
|
|
1100
1151
|
consola.debug("Parameter types:", Object.fromEntries(paramTypes));
|
|
1101
1152
|
}
|
|
1102
|
-
for (let i = 0; i < stmt.parameterCount; i++)
|
|
1153
|
+
for (let i = 0; i < stmt.parameterCount; i++) {
|
|
1154
|
+
let value = statement.parameters[i].value;
|
|
1155
|
+
if (value.startsWith("'") && value.endsWith("'") || value.startsWith("\"") && value.endsWith("\"")) value = value.slice(1, -1);
|
|
1156
|
+
stmt.bindValue(i + 1, value);
|
|
1157
|
+
}
|
|
1103
1158
|
if (query.isQuery) {
|
|
1104
1159
|
const result = await stmt.stream();
|
|
1105
1160
|
const columnNames = result.columnNames();
|
|
@@ -1149,6 +1204,7 @@ const duckdb = new class {
|
|
|
1149
1204
|
}
|
|
1150
1205
|
close() {
|
|
1151
1206
|
this.connection.closeSync();
|
|
1207
|
+
enumNameCache = /* @__PURE__ */ new Map();
|
|
1152
1208
|
}
|
|
1153
1209
|
}();
|
|
1154
1210
|
|
|
@@ -1894,6 +1950,154 @@ var TypeScriptTypeMapper = class extends TypeMapper {
|
|
|
1894
1950
|
return value;
|
|
1895
1951
|
}
|
|
1896
1952
|
};
|
|
1953
|
+
/**
|
|
1954
|
+
* Type mapper for generating Python types from SQL column types.
|
|
1955
|
+
* Maps SQL types to Python types (e.g., INTEGER -> int, VARCHAR -> str).
|
|
1956
|
+
* Generates frozen dataclasses for struct types and handles Python reserved keywords.
|
|
1957
|
+
*/
|
|
1958
|
+
var PythonTypeMapper = class PythonTypeMapper extends TypeMapper {
|
|
1959
|
+
constructor() {
|
|
1960
|
+
super();
|
|
1961
|
+
}
|
|
1962
|
+
typeMap = {
|
|
1963
|
+
INTEGER: "int",
|
|
1964
|
+
INT: "int",
|
|
1965
|
+
INT2: "int",
|
|
1966
|
+
INT4: "int",
|
|
1967
|
+
TINYINT: "int",
|
|
1968
|
+
SMALLINT: "int",
|
|
1969
|
+
BIGINT: "int",
|
|
1970
|
+
INT8: "int",
|
|
1971
|
+
HUGEINT: "int",
|
|
1972
|
+
UHUGEINT: "int",
|
|
1973
|
+
UTINYINT: "int",
|
|
1974
|
+
USMALLINT: "int",
|
|
1975
|
+
UINTEGER: "int",
|
|
1976
|
+
UBIGINT: "int",
|
|
1977
|
+
SERIAL: "int",
|
|
1978
|
+
BIGSERIAL: "int",
|
|
1979
|
+
REAL: "float",
|
|
1980
|
+
DOUBLE: "float",
|
|
1981
|
+
FLOAT: "float",
|
|
1982
|
+
FLOAT4: "float",
|
|
1983
|
+
FLOAT8: "float",
|
|
1984
|
+
TEXT: "str",
|
|
1985
|
+
VARCHAR: "str",
|
|
1986
|
+
INTERVAL: "str",
|
|
1987
|
+
BIT: "str",
|
|
1988
|
+
UUID: "str",
|
|
1989
|
+
BOOLEAN: "bool",
|
|
1990
|
+
BOOL: "bool",
|
|
1991
|
+
BLOB: "bytes",
|
|
1992
|
+
BYTEA: "bytes",
|
|
1993
|
+
DATE: "datetime.date",
|
|
1994
|
+
TIMESTAMP: "datetime.datetime",
|
|
1995
|
+
DATETIME: "datetime.datetime",
|
|
1996
|
+
TIMESTAMPTZ: "datetime.datetime",
|
|
1997
|
+
"TIMESTAMP WITH TIME ZONE": "datetime.datetime",
|
|
1998
|
+
TIMESTAMP_S: "datetime.datetime",
|
|
1999
|
+
TIMESTAMP_MS: "datetime.datetime",
|
|
2000
|
+
TIMESTAMP_NS: "datetime.datetime",
|
|
2001
|
+
TIME: "datetime.time",
|
|
2002
|
+
"TIME WITH TIME ZONE": "datetime.time",
|
|
2003
|
+
NUMERIC: "Decimal",
|
|
2004
|
+
DECIMAL: "Decimal",
|
|
2005
|
+
BIGNUM: "Decimal",
|
|
2006
|
+
JSON: "Any",
|
|
2007
|
+
JSONB: "Any",
|
|
2008
|
+
NULL: "None",
|
|
2009
|
+
UNKNOWN: "Any"
|
|
2010
|
+
};
|
|
2011
|
+
static pythonReservedKeywords = new Set([
|
|
2012
|
+
"False",
|
|
2013
|
+
"None",
|
|
2014
|
+
"True",
|
|
2015
|
+
"and",
|
|
2016
|
+
"as",
|
|
2017
|
+
"assert",
|
|
2018
|
+
"async",
|
|
2019
|
+
"await",
|
|
2020
|
+
"break",
|
|
2021
|
+
"class",
|
|
2022
|
+
"continue",
|
|
2023
|
+
"def",
|
|
2024
|
+
"del",
|
|
2025
|
+
"elif",
|
|
2026
|
+
"else",
|
|
2027
|
+
"except",
|
|
2028
|
+
"finally",
|
|
2029
|
+
"for",
|
|
2030
|
+
"from",
|
|
2031
|
+
"global",
|
|
2032
|
+
"if",
|
|
2033
|
+
"import",
|
|
2034
|
+
"in",
|
|
2035
|
+
"is",
|
|
2036
|
+
"lambda",
|
|
2037
|
+
"nonlocal",
|
|
2038
|
+
"not",
|
|
2039
|
+
"or",
|
|
2040
|
+
"pass",
|
|
2041
|
+
"raise",
|
|
2042
|
+
"return",
|
|
2043
|
+
"try",
|
|
2044
|
+
"while",
|
|
2045
|
+
"with",
|
|
2046
|
+
"yield"
|
|
2047
|
+
]);
|
|
2048
|
+
mapPrimitiveType(type, nullable) {
|
|
2049
|
+
const upperType = type.toString().toUpperCase();
|
|
2050
|
+
const mappedType = this.typeMap[upperType];
|
|
2051
|
+
if (mappedType) return nullable ? `${mappedType} | None` : mappedType;
|
|
2052
|
+
if (upperType.startsWith("DECIMAL(") || upperType.startsWith("NUMERIC(")) return nullable ? "Decimal | None" : "Decimal";
|
|
2053
|
+
if (upperType.startsWith("ENUM(")) return nullable ? "str | None" : "str";
|
|
2054
|
+
if (upperType.startsWith("UNION(")) return nullable ? "Any | None" : "Any";
|
|
2055
|
+
return nullable ? "Any | None" : "Any";
|
|
2056
|
+
}
|
|
2057
|
+
formatListType(elementType) {
|
|
2058
|
+
return `list[${elementType}]`;
|
|
2059
|
+
}
|
|
2060
|
+
formatStructTypeName(fieldName) {
|
|
2061
|
+
return `${pascalCase(fieldName)}Struct`;
|
|
2062
|
+
}
|
|
2063
|
+
formatMapTypeName(_fieldName) {
|
|
2064
|
+
return "dict";
|
|
2065
|
+
}
|
|
2066
|
+
generateStructDeclaration(column, path = "") {
|
|
2067
|
+
if (!(column.type instanceof StructType)) throw new Error(`Expected StructType ${column}`);
|
|
2068
|
+
const structName = this.formatStructTypeName(column.name);
|
|
2069
|
+
const newPath = `${path}${structName}.`;
|
|
2070
|
+
const children = column.type.fields.map((field) => {
|
|
2071
|
+
return this.getDeclarations({
|
|
2072
|
+
name: field.name,
|
|
2073
|
+
type: field.type,
|
|
2074
|
+
nullable: true
|
|
2075
|
+
}, newPath);
|
|
2076
|
+
}).filter(Boolean).join("\n\n");
|
|
2077
|
+
const fields = column.type.fields.map((field) => {
|
|
2078
|
+
const fieldType = this.getTypeName(field);
|
|
2079
|
+
return ` ${this.varName(field.name)}: ${fieldType}`;
|
|
2080
|
+
}).join("\n");
|
|
2081
|
+
let result = "";
|
|
2082
|
+
if (children) result += `${children}\n\n`;
|
|
2083
|
+
result += `@dataclass(frozen=True)\nclass ${structName}:\n${fields}`;
|
|
2084
|
+
return result;
|
|
2085
|
+
}
|
|
2086
|
+
varName(str) {
|
|
2087
|
+
const name = snakeCase(str);
|
|
2088
|
+
if (PythonTypeMapper.pythonReservedKeywords.has(name)) return `${name}_`;
|
|
2089
|
+
return name;
|
|
2090
|
+
}
|
|
2091
|
+
parseValue(column, value, _path) {
|
|
2092
|
+
if (column.type instanceof StructType) return `${this.formatStructTypeName(column.name)}(${column.type.fields.map((field) => {
|
|
2093
|
+
const pyName = this.varName(field.name);
|
|
2094
|
+
const dictAccess = `${value}["${field.name}"]`;
|
|
2095
|
+
if (field.type instanceof StructType) return `${pyName}=${this.parseValue(field, dictAccess, _path)}`;
|
|
2096
|
+
return `${pyName}=${dictAccess}`;
|
|
2097
|
+
}).join(", ")})`;
|
|
2098
|
+
return value;
|
|
2099
|
+
}
|
|
2100
|
+
};
|
|
1897
2101
|
|
|
1898
2102
|
//#endregion
|
|
1899
2103
|
//#region src/generators/base-generator.ts
|
|
@@ -2167,6 +2371,149 @@ var JavaDuckDBArrowGenerator = class extends BaseGenerator {
|
|
|
2167
2371
|
}
|
|
2168
2372
|
};
|
|
2169
2373
|
|
|
2374
|
+
//#endregion
|
|
2375
|
+
//#region src/generators/python-generator.ts
|
|
2376
|
+
var PythonGenerator = class extends BaseGenerator {
|
|
2377
|
+
engine;
|
|
2378
|
+
constructor(template, engine) {
|
|
2379
|
+
super(template, new PythonTypeMapper());
|
|
2380
|
+
this.engine = engine;
|
|
2381
|
+
}
|
|
2382
|
+
get isDuckDB() {
|
|
2383
|
+
return this.engine === "duckdb";
|
|
2384
|
+
}
|
|
2385
|
+
get isPostgres() {
|
|
2386
|
+
return this.engine === "postgres";
|
|
2387
|
+
}
|
|
2388
|
+
getFunctionName(id) {
|
|
2389
|
+
return snakeCase(id);
|
|
2390
|
+
}
|
|
2391
|
+
getFilename(sqlFileName) {
|
|
2392
|
+
return `${snakeCase(sqlFileName)}.py`;
|
|
2393
|
+
}
|
|
2394
|
+
getClassName(name) {
|
|
2395
|
+
return pascalCase(name);
|
|
2396
|
+
}
|
|
2397
|
+
rowType(query) {
|
|
2398
|
+
if (query.isPluck) return this.mapType({
|
|
2399
|
+
...query.columns[0],
|
|
2400
|
+
name: query.id
|
|
2401
|
+
});
|
|
2402
|
+
return this.getClassName(`${query.id}_row`);
|
|
2403
|
+
}
|
|
2404
|
+
getStatement(q) {
|
|
2405
|
+
if (this.isDuckDB) return q.queryPositional;
|
|
2406
|
+
if (this.isPostgres) {
|
|
2407
|
+
const anon = q.queryAnonymous;
|
|
2408
|
+
return {
|
|
2409
|
+
...anon,
|
|
2410
|
+
sql: anon.sql.replace(/\?/g, "%s")
|
|
2411
|
+
};
|
|
2412
|
+
}
|
|
2413
|
+
return q.queryAnonymous;
|
|
2414
|
+
}
|
|
2415
|
+
supportsAppenders(_engine) {
|
|
2416
|
+
return this.engine === "duckdb";
|
|
2417
|
+
}
|
|
2418
|
+
async beforeGenerate(_projectDir, _gen, _queries, _tables) {
|
|
2419
|
+
const pyMapper = this.typeMapper;
|
|
2420
|
+
Handlebars.registerHelper("isDuckDB", () => this.isDuckDB);
|
|
2421
|
+
Handlebars.registerHelper("isPostgres", () => this.isPostgres);
|
|
2422
|
+
Handlebars.registerHelper("connType", () => {
|
|
2423
|
+
if (this.isDuckDB) return "duckdb.DuckDBPyConnection";
|
|
2424
|
+
if (this.isPostgres) return "psycopg.Connection";
|
|
2425
|
+
return "sqlite3.Connection";
|
|
2426
|
+
});
|
|
2427
|
+
Handlebars.registerHelper("quote", (value) => {
|
|
2428
|
+
if (value.includes("\n") || value.includes("'") || value.includes("\"")) return `"""\\\n${value}"""`;
|
|
2429
|
+
return `"${value}"`;
|
|
2430
|
+
});
|
|
2431
|
+
Handlebars.registerHelper("pyType", (column) => {
|
|
2432
|
+
return this.getPyType(column);
|
|
2433
|
+
});
|
|
2434
|
+
Handlebars.registerHelper("declareTypes", (queryHelper) => {
|
|
2435
|
+
const query = queryHelper.query;
|
|
2436
|
+
if (queryHelper.isPluck) return "";
|
|
2437
|
+
const columns = queryHelper.columns;
|
|
2438
|
+
const rowTypeName = this.getClassName(`${query.id}_row`);
|
|
2439
|
+
const nestedDecls = [];
|
|
2440
|
+
for (const col of columns) {
|
|
2441
|
+
const decl = this.typeMapper.getDeclarations(col);
|
|
2442
|
+
if (decl) nestedDecls.push(decl);
|
|
2443
|
+
}
|
|
2444
|
+
const fields = columns.map((col) => {
|
|
2445
|
+
const pyType = this.getPyType(col);
|
|
2446
|
+
return ` ${pyMapper.varName(col.name)}: ${pyType}`;
|
|
2447
|
+
}).join("\n");
|
|
2448
|
+
let result = "";
|
|
2449
|
+
if (nestedDecls.length > 0) result += `${nestedDecls.join("\n\n")}\n\n`;
|
|
2450
|
+
result += `@dataclass(frozen=True)\nclass ${rowTypeName}:\n${fields}`;
|
|
2451
|
+
return new Handlebars.SafeString(result);
|
|
2452
|
+
});
|
|
2453
|
+
Handlebars.registerHelper("constructRow", (queryHelper) => {
|
|
2454
|
+
const query = queryHelper.query;
|
|
2455
|
+
const columns = queryHelper.columns;
|
|
2456
|
+
return `${this.getClassName(`${query.id}_row`)}(${columns.map((col, i) => {
|
|
2457
|
+
const pyName = pyMapper.varName(col.name);
|
|
2458
|
+
const value = `row[${i}]`;
|
|
2459
|
+
return `${pyName}=${this.typeMapper.parseValue(col, value, "")}`;
|
|
2460
|
+
}).join(", ")})`;
|
|
2461
|
+
});
|
|
2462
|
+
Handlebars.registerHelper("pyTypeOrNone", (column) => {
|
|
2463
|
+
const t = this.getPyType(column);
|
|
2464
|
+
if (t.endsWith(" | None")) return t;
|
|
2465
|
+
return `${t} | None`;
|
|
2466
|
+
});
|
|
2467
|
+
Handlebars.registerHelper("pyVarName", (name) => {
|
|
2468
|
+
return pyMapper.varName(name);
|
|
2469
|
+
});
|
|
2470
|
+
Handlebars.registerHelper("needsDatetime", (queries) => this.queryColumnsContainType(queries, "datetime."));
|
|
2471
|
+
Handlebars.registerHelper("needsDecimal", (queries) => this.queryColumnsContainType(queries, "Decimal"));
|
|
2472
|
+
}
|
|
2473
|
+
queryColumnsContainType(queries, substring) {
|
|
2474
|
+
for (const qh of queries) {
|
|
2475
|
+
if (!qh.isQuery || qh.skipGenerateFunction) continue;
|
|
2476
|
+
for (const col of qh.columns) if (this.getPyType(col).includes(substring)) return true;
|
|
2477
|
+
}
|
|
2478
|
+
return false;
|
|
2479
|
+
}
|
|
2480
|
+
getPyType(column) {
|
|
2481
|
+
const t = column.type;
|
|
2482
|
+
if (t instanceof ListType) {
|
|
2483
|
+
const base = `list[${this.getPyType({
|
|
2484
|
+
name: column.name,
|
|
2485
|
+
type: t.baseType,
|
|
2486
|
+
nullable: true
|
|
2487
|
+
})}]`;
|
|
2488
|
+
return column.nullable ? `${base} | None` : base;
|
|
2489
|
+
}
|
|
2490
|
+
if (t instanceof MapType) {
|
|
2491
|
+
const base = `dict[${this.getPyType({
|
|
2492
|
+
name: "key",
|
|
2493
|
+
type: t.keyType.type,
|
|
2494
|
+
nullable: false
|
|
2495
|
+
})}, ${this.getPyType({
|
|
2496
|
+
name: "value",
|
|
2497
|
+
type: t.valueType.type,
|
|
2498
|
+
nullable: true
|
|
2499
|
+
})}]`;
|
|
2500
|
+
return column.nullable ? `${base} | None` : base;
|
|
2501
|
+
}
|
|
2502
|
+
if (t instanceof StructType) {
|
|
2503
|
+
const structName = `${pascalCase(column.name)}Struct`;
|
|
2504
|
+
return column.nullable ? `${structName} | None` : structName;
|
|
2505
|
+
}
|
|
2506
|
+
return this.typeMapper.getTypeName(column);
|
|
2507
|
+
}
|
|
2508
|
+
async afterGenerate(outputPath) {
|
|
2509
|
+
try {
|
|
2510
|
+
execSync(`ruff format "${outputPath}"`, { stdio: "ignore" });
|
|
2511
|
+
} catch {
|
|
2512
|
+
consola.debug("ruff not available, skipping format for:", outputPath);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
};
|
|
2516
|
+
|
|
2170
2517
|
//#endregion
|
|
2171
2518
|
//#region src/generators/typescript-generator.ts
|
|
2172
2519
|
/** Resolve a ColumnType to its DuckDB type constant name (e.g. "VARCHAR", "INTEGER") for use in generated code. */
|
|
@@ -2454,6 +2801,9 @@ function getGenerator(generator) {
|
|
|
2454
2801
|
case "typescript/node-api": return new TsDuckDBGenerator(`templates/${info.template}`);
|
|
2455
2802
|
case "java/jdbc": return new JavaGenerator(`templates/${info.template}`);
|
|
2456
2803
|
case "java/arrow": return new JavaDuckDBArrowGenerator(`templates/${info.template}`);
|
|
2804
|
+
case "python/sqlite3": return new PythonGenerator(`templates/${info.template}`, "sqlite");
|
|
2805
|
+
case "python/duckdb": return new PythonGenerator(`templates/${info.template}`, "duckdb");
|
|
2806
|
+
case "python/psycopg": return new PythonGenerator(`templates/${info.template}`, "postgres");
|
|
2457
2807
|
default: throw new Error(`No generator class for ${key}`);
|
|
2458
2808
|
}
|
|
2459
2809
|
} catch {
|
|
@@ -2971,7 +3321,7 @@ async function processProject(projectPath, ui) {
|
|
|
2971
3321
|
//#region src/mcp-server.ts
|
|
2972
3322
|
const server = new Server({
|
|
2973
3323
|
name: "sqg-mcp",
|
|
2974
|
-
version: process.env.npm_package_version ?? "0.
|
|
3324
|
+
version: process.env.npm_package_version ?? "0.14.0"
|
|
2975
3325
|
}, { capabilities: {
|
|
2976
3326
|
tools: {},
|
|
2977
3327
|
resources: {}
|
|
@@ -3438,7 +3788,7 @@ function formatMs(ms) {
|
|
|
3438
3788
|
|
|
3439
3789
|
//#endregion
|
|
3440
3790
|
//#region src/sqg.ts
|
|
3441
|
-
const version = process.env.npm_package_version ?? "0.
|
|
3791
|
+
const version = process.env.npm_package_version ?? "0.14.0";
|
|
3442
3792
|
updateNotifier({ pkg: {
|
|
3443
3793
|
name: "@sqg/sqg",
|
|
3444
3794
|
version
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# {{generatedComment}}
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
{{#if (isDuckDB)}}
|
|
5
|
+
import duckdb
|
|
6
|
+
{{else if (isPostgres)}}
|
|
7
|
+
import psycopg
|
|
8
|
+
{{else}}
|
|
9
|
+
import sqlite3
|
|
10
|
+
{{/if}}
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from typing import Any
|
|
13
|
+
{{#if (needsDatetime queries)}}
|
|
14
|
+
import datetime
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if (needsDecimal queries)}}
|
|
17
|
+
from decimal import Decimal
|
|
18
|
+
{{/if}}
|
|
19
|
+
|
|
20
|
+
{{#each queries}}
|
|
21
|
+
{{#unless skipGenerateFunction}}
|
|
22
|
+
{{#if isQuery}}
|
|
23
|
+
{{#unless isPluck}}
|
|
24
|
+
{{{declareTypes this}}}
|
|
25
|
+
|
|
26
|
+
{{/unless}}
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{/unless}}
|
|
29
|
+
{{/each}}
|
|
30
|
+
|
|
31
|
+
class {{className}}:
|
|
32
|
+
def __init__(self, conn: {{{connType}}}) -> None:
|
|
33
|
+
self._conn = conn
|
|
34
|
+
|
|
35
|
+
@staticmethod
|
|
36
|
+
def get_migrations() -> list[str]:
|
|
37
|
+
return [
|
|
38
|
+
{{#each migrations}}
|
|
39
|
+
{{{quote sqlQuery}}},
|
|
40
|
+
{{/each}}
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
{{#if config.migrations}}
|
|
44
|
+
@staticmethod
|
|
45
|
+
def apply_migrations(conn: {{{connType}}}, project_name: str = "{{projectName}}") -> None:
|
|
46
|
+
{{#if (isDuckDB)}}
|
|
47
|
+
conn.execute("""
|
|
48
|
+
CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
49
|
+
project TEXT NOT NULL,
|
|
50
|
+
migration_id TEXT NOT NULL,
|
|
51
|
+
applied_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
52
|
+
PRIMARY KEY (project, migration_id)
|
|
53
|
+
)""")
|
|
54
|
+
conn.begin()
|
|
55
|
+
try:
|
|
56
|
+
applied = set(
|
|
57
|
+
row[0]
|
|
58
|
+
for row in conn.execute(
|
|
59
|
+
"SELECT migration_id FROM _sqg_migrations WHERE project = $1",
|
|
60
|
+
[project_name],
|
|
61
|
+
).fetchall()
|
|
62
|
+
)
|
|
63
|
+
migrations: list[tuple[str, str]] = [
|
|
64
|
+
{{#each migrations}}
|
|
65
|
+
("{{{id}}}", {{{quote sqlQuery}}}),
|
|
66
|
+
{{/each}}
|
|
67
|
+
]
|
|
68
|
+
for mid, sql in migrations:
|
|
69
|
+
if mid not in applied:
|
|
70
|
+
conn.execute(sql)
|
|
71
|
+
conn.execute(
|
|
72
|
+
"INSERT INTO _sqg_migrations (project, migration_id) VALUES ($1, $2)",
|
|
73
|
+
[project_name, mid],
|
|
74
|
+
)
|
|
75
|
+
conn.commit()
|
|
76
|
+
except Exception:
|
|
77
|
+
conn.rollback()
|
|
78
|
+
raise
|
|
79
|
+
{{else if (isPostgres)}}
|
|
80
|
+
with conn.cursor() as cur:
|
|
81
|
+
cur.execute("""
|
|
82
|
+
CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
83
|
+
project TEXT NOT NULL,
|
|
84
|
+
migration_id TEXT NOT NULL,
|
|
85
|
+
applied_at TIMESTAMP NOT NULL DEFAULT now(),
|
|
86
|
+
PRIMARY KEY (project, migration_id)
|
|
87
|
+
)""")
|
|
88
|
+
conn.commit()
|
|
89
|
+
cur.execute(
|
|
90
|
+
"SELECT migration_id FROM _sqg_migrations WHERE project = %s",
|
|
91
|
+
(project_name,),
|
|
92
|
+
)
|
|
93
|
+
applied = set(row[0] for row in cur.fetchall())
|
|
94
|
+
migrations: list[tuple[str, str]] = [
|
|
95
|
+
{{#each migrations}}
|
|
96
|
+
("{{{id}}}", {{{quote sqlQuery}}}),
|
|
97
|
+
{{/each}}
|
|
98
|
+
]
|
|
99
|
+
for mid, sql in migrations:
|
|
100
|
+
if mid not in applied:
|
|
101
|
+
cur.execute(sql)
|
|
102
|
+
cur.execute(
|
|
103
|
+
"INSERT INTO _sqg_migrations (project, migration_id) VALUES (%s, %s)",
|
|
104
|
+
(project_name, mid),
|
|
105
|
+
)
|
|
106
|
+
conn.commit()
|
|
107
|
+
{{else}}
|
|
108
|
+
conn.execute("""
|
|
109
|
+
CREATE TABLE IF NOT EXISTS _sqg_migrations (
|
|
110
|
+
project TEXT NOT NULL,
|
|
111
|
+
migration_id TEXT NOT NULL,
|
|
112
|
+
applied_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
113
|
+
PRIMARY KEY (project, migration_id)
|
|
114
|
+
)""")
|
|
115
|
+
applied = set(
|
|
116
|
+
row[0]
|
|
117
|
+
for row in conn.execute(
|
|
118
|
+
"SELECT migration_id FROM _sqg_migrations WHERE project = ?",
|
|
119
|
+
(project_name,),
|
|
120
|
+
).fetchall()
|
|
121
|
+
)
|
|
122
|
+
migrations: list[tuple[str, str]] = [
|
|
123
|
+
{{#each migrations}}
|
|
124
|
+
("{{{id}}}", {{{quote sqlQuery}}}),
|
|
125
|
+
{{/each}}
|
|
126
|
+
]
|
|
127
|
+
for mid, sql in migrations:
|
|
128
|
+
if mid not in applied:
|
|
129
|
+
conn.executescript(sql)
|
|
130
|
+
conn.execute(
|
|
131
|
+
"INSERT INTO _sqg_migrations (project, migration_id) VALUES (?, ?)",
|
|
132
|
+
(project_name, mid),
|
|
133
|
+
)
|
|
134
|
+
conn.commit()
|
|
135
|
+
{{/if}}
|
|
136
|
+
{{/if}}
|
|
137
|
+
|
|
138
|
+
{{#each queries}}
|
|
139
|
+
{{#unless skipGenerateFunction}}
|
|
140
|
+
{{#if isQuery}}
|
|
141
|
+
{{#if isPluck}}
|
|
142
|
+
{{#if isOne}}
|
|
143
|
+
def {{functionName}}(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> {{{pyTypeOrNone (lookup columns 0)}}}:
|
|
144
|
+
row = self._conn.execute(
|
|
145
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
146
|
+
).fetchone()
|
|
147
|
+
if row is None:
|
|
148
|
+
return None
|
|
149
|
+
return row[0]
|
|
150
|
+
|
|
151
|
+
def {{functionName}}_raw(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> tuple[Any, ...] | None:
|
|
152
|
+
return self._conn.execute(
|
|
153
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
154
|
+
).fetchone()
|
|
155
|
+
|
|
156
|
+
{{else}}
|
|
157
|
+
def {{functionName}}(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> list[{{{pyType (lookup columns 0)}}}]:
|
|
158
|
+
rows = self._conn.execute(
|
|
159
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
160
|
+
).fetchall()
|
|
161
|
+
return [r[0] for r in rows]
|
|
162
|
+
|
|
163
|
+
def {{functionName}}_raw(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> list[tuple[Any, ...]]:
|
|
164
|
+
return self._conn.execute(
|
|
165
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
166
|
+
).fetchall()
|
|
167
|
+
|
|
168
|
+
{{/if}}
|
|
169
|
+
{{else}}
|
|
170
|
+
{{#if isOne}}
|
|
171
|
+
def {{functionName}}(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> {{{rowType}}} | None:
|
|
172
|
+
row = self._conn.execute(
|
|
173
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
174
|
+
).fetchone()
|
|
175
|
+
if row is None:
|
|
176
|
+
return None
|
|
177
|
+
return {{{constructRow this}}}
|
|
178
|
+
|
|
179
|
+
def {{functionName}}_raw(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> tuple[Any, ...] | None:
|
|
180
|
+
return self._conn.execute(
|
|
181
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
182
|
+
).fetchone()
|
|
183
|
+
|
|
184
|
+
{{else}}
|
|
185
|
+
def {{functionName}}(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> list[{{{rowType}}}]:
|
|
186
|
+
rows = self._conn.execute(
|
|
187
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
188
|
+
).fetchall()
|
|
189
|
+
return [{{{constructRow this}}} for row in rows]
|
|
190
|
+
|
|
191
|
+
def {{functionName}}_raw(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> list[tuple[Any, ...]]:
|
|
192
|
+
return self._conn.execute(
|
|
193
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
194
|
+
).fetchall()
|
|
195
|
+
|
|
196
|
+
{{/if}}
|
|
197
|
+
{{/if}}
|
|
198
|
+
{{else}}
|
|
199
|
+
def {{functionName}}(self{{#each variables}}, {{name}}: {{{type}}}{{/each}}) -> None:
|
|
200
|
+
self._conn.execute(
|
|
201
|
+
{{{quote sqlQuery}}}, {{> params}}
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
{{/if}}
|
|
205
|
+
{{/unless}}
|
|
206
|
+
{{/each}}
|
|
207
|
+
{{#if (isDuckDB)}}
|
|
208
|
+
{{#if tables.length}}
|
|
209
|
+
# ==================== Appenders ====================
|
|
210
|
+
{{#each tables}}
|
|
211
|
+
|
|
212
|
+
def {{functionName}}(self) -> {{className}}:
|
|
213
|
+
return {{className}}(self._conn.cursor())
|
|
214
|
+
|
|
215
|
+
{{/each}}
|
|
216
|
+
{{/if}}
|
|
217
|
+
{{/if}}
|
|
218
|
+
|
|
219
|
+
{{#if (isDuckDB)}}
|
|
220
|
+
{{#each tables}}
|
|
221
|
+
|
|
222
|
+
@dataclass(frozen=True)
|
|
223
|
+
class {{rowTypeName}}:
|
|
224
|
+
{{#each columns}}
|
|
225
|
+
{{pyVarName name}}: {{{pyType this}}}
|
|
226
|
+
{{/each}}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class {{className}}:
|
|
230
|
+
def __init__(self, cursor: duckdb.DuckDBPyConnection) -> None:
|
|
231
|
+
self._cursor = cursor
|
|
232
|
+
|
|
233
|
+
def append(self, row: {{rowTypeName}}) -> {{className}}:
|
|
234
|
+
self._cursor.execute(
|
|
235
|
+
"INSERT INTO {{tableName}} VALUES ({{> placeholders}})",
|
|
236
|
+
[{{#each columns}}row.{{pyVarName name}}{{#unless @last}}, {{/unless}}{{/each}}],
|
|
237
|
+
)
|
|
238
|
+
return self
|
|
239
|
+
|
|
240
|
+
def append_many(self, rows: list[{{rowTypeName}}]) -> {{className}}:
|
|
241
|
+
for row in rows:
|
|
242
|
+
self.append(row)
|
|
243
|
+
return self
|
|
244
|
+
|
|
245
|
+
def close(self) -> None:
|
|
246
|
+
self._cursor.close()
|
|
247
|
+
|
|
248
|
+
{{/each}}
|
|
249
|
+
{{/if}}
|
|
250
|
+
|
|
251
|
+
{{!-- ==================== Inline partials ==================== --}}
|
|
252
|
+
|
|
253
|
+
{{#*inline "params"}}{{#if (isDuckDB)}}[{{#each parameterNames}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}]{{else}}({{#each parameterNames}}{{this}}, {{/each}}){{/if}}{{/inline}}
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
{{#*inline "placeholders"}}{{#each columns}}${{plusOne @index}}{{#unless @last}}, {{/unless}}{{/each}}{{/inline}}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqg/sqg",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "SQG - SQL Query Generator - Type-safe code generation from SQL (https://sqg.dev)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"sqg": "
|
|
7
|
+
"sqg": "dist/sqg.mjs"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist"
|
|
@@ -20,6 +20,19 @@
|
|
|
20
20
|
"bugs": {
|
|
21
21
|
"url": "https://github.com/sqg-dev/sqg/issues"
|
|
22
22
|
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"prepublishOnly": "pnpm run build",
|
|
25
|
+
"build": "tsc --noEmit && tsdown && cp -r src/templates dist",
|
|
26
|
+
"lezer-gen": "lezer-generator src/parser/sql.grammar -o src/parser/sql-parser.ts",
|
|
27
|
+
"test-grammar": "tsx src/test-grammar.ts",
|
|
28
|
+
"check": "biome check --write src/",
|
|
29
|
+
"check:errors": "pnpm biome check --write --diagnostic-level=error src/",
|
|
30
|
+
"test": "vitest",
|
|
31
|
+
"coverage": "vitest run --coverage",
|
|
32
|
+
"test:ui": "vitest --ui",
|
|
33
|
+
"test:run": "vitest run",
|
|
34
|
+
"sqg": "tsx src/sqg.ts"
|
|
35
|
+
},
|
|
23
36
|
"keywords": [
|
|
24
37
|
"sql",
|
|
25
38
|
"codegen",
|
|
@@ -37,56 +50,45 @@
|
|
|
37
50
|
],
|
|
38
51
|
"author": "Uwe Maurer",
|
|
39
52
|
"license": "Apache-2.0",
|
|
53
|
+
"packageManager": "pnpm@10.26.0",
|
|
40
54
|
"dependencies": {
|
|
41
|
-
"@biomejs/biome": "
|
|
55
|
+
"@biomejs/biome": "catalog:",
|
|
42
56
|
"@clack/prompts": "^1.1.0",
|
|
43
|
-
"@duckdb/node-api": "
|
|
44
|
-
"@duckdb/node-bindings-linux-x64": "
|
|
45
|
-
"@lezer-unofficial/printer": "
|
|
46
|
-
"@lezer/common": "
|
|
47
|
-
"@lezer/generator": "
|
|
48
|
-
"@lezer/lr": "
|
|
49
|
-
"@modelcontextprotocol/sdk": "
|
|
57
|
+
"@duckdb/node-api": "catalog:",
|
|
58
|
+
"@duckdb/node-bindings-linux-x64": "catalog:",
|
|
59
|
+
"@lezer-unofficial/printer": "catalog:",
|
|
60
|
+
"@lezer/common": "catalog:",
|
|
61
|
+
"@lezer/generator": "catalog:",
|
|
62
|
+
"@lezer/lr": "catalog:",
|
|
63
|
+
"@modelcontextprotocol/sdk": "catalog:",
|
|
50
64
|
"@testcontainers/postgresql": "^10.21.0",
|
|
51
|
-
"better-sqlite3": "
|
|
52
|
-
"commander": "
|
|
53
|
-
"consola": "
|
|
54
|
-
"dotenv": "
|
|
55
|
-
"es-toolkit": "
|
|
56
|
-
"handlebars": "
|
|
57
|
-
"pg": "
|
|
58
|
-
"pg-types": "
|
|
65
|
+
"better-sqlite3": "catalog:",
|
|
66
|
+
"commander": "catalog:",
|
|
67
|
+
"consola": "catalog:",
|
|
68
|
+
"dotenv": "catalog:",
|
|
69
|
+
"es-toolkit": "catalog:",
|
|
70
|
+
"handlebars": "catalog:",
|
|
71
|
+
"pg": "catalog:",
|
|
72
|
+
"pg-types": "catalog:",
|
|
59
73
|
"picocolors": "^1.1.1",
|
|
60
|
-
"prettier": "
|
|
61
|
-
"prettier-plugin-java": "
|
|
74
|
+
"prettier": "catalog:",
|
|
75
|
+
"prettier-plugin-java": "catalog:",
|
|
62
76
|
"update-notifier": "^7.3.1",
|
|
63
|
-
"yaml": "
|
|
77
|
+
"yaml": "catalog:",
|
|
64
78
|
"yocto-spinner": "^1.1.0",
|
|
65
|
-
"zod": "
|
|
79
|
+
"zod": "catalog:"
|
|
66
80
|
},
|
|
67
81
|
"devDependencies": {
|
|
68
82
|
"@libsql/client": "^0.17.0",
|
|
69
83
|
"@tursodatabase/database": "^0.4.3",
|
|
70
|
-
"@types/better-sqlite3": "
|
|
71
|
-
"@types/node": "
|
|
72
|
-
"@types/pg": "
|
|
84
|
+
"@types/better-sqlite3": "catalog:",
|
|
85
|
+
"@types/node": "catalog:",
|
|
86
|
+
"@types/pg": "catalog:",
|
|
73
87
|
"@types/update-notifier": "^6.0.8",
|
|
74
|
-
"@vitest/ui": "
|
|
75
|
-
"tsdown": "
|
|
76
|
-
"tsx": "
|
|
77
|
-
"typescript": "
|
|
78
|
-
"vitest": "
|
|
79
|
-
},
|
|
80
|
-
"scripts": {
|
|
81
|
-
"build": "tsc --noEmit && tsdown && cp -r src/templates dist",
|
|
82
|
-
"lezer-gen": "lezer-generator src/parser/sql.grammar -o src/parser/sql-parser.ts",
|
|
83
|
-
"test-grammar": "tsx src/test-grammar.ts",
|
|
84
|
-
"check": "biome check --write src/",
|
|
85
|
-
"check:errors": "pnpm biome check --write --diagnostic-level=error src/",
|
|
86
|
-
"test": "vitest",
|
|
87
|
-
"coverage": "vitest run --coverage",
|
|
88
|
-
"test:ui": "vitest --ui",
|
|
89
|
-
"test:run": "vitest run",
|
|
90
|
-
"sqg": "tsx src/sqg.ts"
|
|
88
|
+
"@vitest/ui": "catalog:",
|
|
89
|
+
"tsdown": "catalog:",
|
|
90
|
+
"tsx": "catalog:",
|
|
91
|
+
"typescript": "catalog:",
|
|
92
|
+
"vitest": "catalog:"
|
|
91
93
|
}
|
|
92
|
-
}
|
|
94
|
+
}
|