@hypequery/cli 1.16.4 → 1.18.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/bin/cli.js +8 -2
- package/dist/cli.js +1 -1
- package/dist/commands/generate-datasets.d.ts.map +1 -1
- package/dist/commands/generate-datasets.js +26 -8
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +8 -3
- package/dist/generators/chdb.d.ts +1 -1
- package/dist/generators/chdb.d.ts.map +1 -1
- package/dist/generators/chdb.js +1 -1
- package/dist/generators/clickhouse.d.ts +1 -1
- package/dist/generators/clickhouse.d.ts.map +1 -1
- package/dist/generators/clickhouse.js +1 -1
- package/dist/generators/dataset-generator.d.ts +7 -1
- package/dist/generators/dataset-generator.d.ts.map +1 -1
- package/dist/generators/dataset-generator.js +9 -0
- package/dist/generators/index.d.ts +1 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -2
- package/dist/typegen/generate-types.d.ts +2 -1
- package/dist/typegen/generate-types.d.ts.map +1 -1
- package/dist/typegen/generate-types.js +18 -7
- package/dist/typegen/type-parsing.d.ts.map +1 -1
- package/dist/typegen/type-parsing.js +77 -6
- package/dist/utils/env-files.d.ts +13 -0
- package/dist/utils/env-files.d.ts.map +1 -0
- package/dist/utils/env-files.js +19 -0
- package/package.json +2 -2
package/dist/bin/cli.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from '../cli.js';
|
|
3
|
+
import { envFiles } from '../utils/env-files.js';
|
|
3
4
|
async function loadEnv() {
|
|
4
5
|
try {
|
|
5
6
|
const dotenvx = await import('@dotenvx/dotenvx');
|
|
6
7
|
if (dotenvx?.config && typeof dotenvx.config.load === 'function') {
|
|
7
8
|
await dotenvx.config.load();
|
|
8
|
-
|
|
9
|
+
// Deliberately falls through to the dotenv cascade below. dotenvx has
|
|
10
|
+
// already set whatever it found, and dotenv will not overwrite it, so this
|
|
11
|
+
// only fills in files dotenvx does not read (notably `.env.local`).
|
|
9
12
|
}
|
|
10
13
|
}
|
|
11
14
|
catch {
|
|
@@ -13,7 +16,10 @@ async function loadEnv() {
|
|
|
13
16
|
}
|
|
14
17
|
try {
|
|
15
18
|
const { config } = await import('dotenv');
|
|
16
|
-
|
|
19
|
+
for (const path of envFiles(process.env.NODE_ENV)) {
|
|
20
|
+
// Missing files are a no-op.
|
|
21
|
+
config({ path });
|
|
22
|
+
}
|
|
17
23
|
}
|
|
18
24
|
catch {
|
|
19
25
|
// dotenv is optional; continue if not available
|
package/dist/cli.js
CHANGED
|
@@ -138,7 +138,7 @@ addTypeGenerationOptions(program.command('generate:types'))
|
|
|
138
138
|
program
|
|
139
139
|
.command('generate:datasets')
|
|
140
140
|
.description('Generate dataset definitions from ClickHouse schema')
|
|
141
|
-
.option('-o, --output <path>', 'Output file (default:
|
|
141
|
+
.option('-o, --output <path>', 'Output file (default: analytics/datasets.ts)')
|
|
142
142
|
.option('--path <path>', 'Analytics directory (derives <path>/datasets.ts)')
|
|
143
143
|
.option('--tables <names>', 'Only generate for specific tables (comma-separated)')
|
|
144
144
|
.option('--exclude-tables <names>', 'Exclude specific tables (comma-separated)')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAqBD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBA0GlF"}
|
|
@@ -10,6 +10,15 @@ import { logger } from '../utils/logger.js';
|
|
|
10
10
|
import { getTableCount } from '../utils/detect-database.js';
|
|
11
11
|
import { generateDatasets } from '../generators/dataset-generator.js';
|
|
12
12
|
import { redactConnectionUrl } from '../utils/redact-connection-url.js';
|
|
13
|
+
/**
|
|
14
|
+
* Turns the written path into something pasteable into an import statement:
|
|
15
|
+
* strips the `.ts` extension and makes it explicitly relative.
|
|
16
|
+
*/
|
|
17
|
+
function toImportSpecifier(relativePath) {
|
|
18
|
+
const withoutExtension = relativePath.replace(/\.tsx?$/, '');
|
|
19
|
+
const normalized = withoutExtension.split(path.sep).join('/');
|
|
20
|
+
return normalized.startsWith('.') ? normalized : `./${normalized}`;
|
|
21
|
+
}
|
|
13
22
|
function parseTableList(value) {
|
|
14
23
|
const parsed = value
|
|
15
24
|
?.split(',')
|
|
@@ -27,8 +36,9 @@ export async function generateDatasetsCommand(options = {}) {
|
|
|
27
36
|
outputPath = path.resolve(process.cwd(), options.path, 'datasets.ts');
|
|
28
37
|
}
|
|
29
38
|
else {
|
|
30
|
-
//
|
|
31
|
-
|
|
39
|
+
// Sibling of `hypequery generate`, which writes analytics/schema.ts. Keeping
|
|
40
|
+
// both commands in one directory is what the docs have always described.
|
|
41
|
+
outputPath = path.join(process.cwd(), 'analytics', 'datasets.ts');
|
|
32
42
|
}
|
|
33
43
|
const parsedTables = parseTableList(options.tables);
|
|
34
44
|
const excludedTables = parseTableList(options.excludeTables);
|
|
@@ -39,30 +49,38 @@ export async function generateDatasetsCommand(options = {}) {
|
|
|
39
49
|
// Get table count
|
|
40
50
|
const tableCount = await getTableCount('clickhouse');
|
|
41
51
|
spinner.succeed('Connected to ClickHouse');
|
|
42
|
-
logger.success(
|
|
52
|
+
logger.success(parsedTables
|
|
53
|
+
? `Found ${tableCount} tables, filtering to ${parsedTables.length}`
|
|
54
|
+
: `Found ${tableCount} tables`);
|
|
43
55
|
// Generate datasets
|
|
44
56
|
const datasetSpinner = ora('Generating dataset definitions...').start();
|
|
45
|
-
await generateDatasets({
|
|
57
|
+
const generated = await generateDatasets({
|
|
46
58
|
outputPath,
|
|
47
59
|
includeTables: parsedTables,
|
|
48
60
|
excludeTables: excludedTables,
|
|
49
61
|
});
|
|
50
|
-
|
|
51
|
-
|
|
62
|
+
const generatedTables = generated?.tables ?? parsedTables ?? [];
|
|
63
|
+
const generatedCount = generatedTables.length || tableCount;
|
|
64
|
+
datasetSpinner.succeed(`Generated dataset definitions for ${generatedCount} ${generatedCount === 1 ? 'table' : 'tables'}`);
|
|
65
|
+
const relativeOutput = path.relative(process.cwd(), outputPath);
|
|
66
|
+
logger.success(`Created ${relativeOutput}`);
|
|
52
67
|
logger.newline();
|
|
53
68
|
logger.header('Next steps:');
|
|
54
69
|
logger.indent('1. Review and customize the generated datasets');
|
|
55
70
|
logger.indent('2. Import datasets in your application code');
|
|
56
71
|
logger.indent('3. Create an analytics client and start querying!');
|
|
57
72
|
logger.newline();
|
|
73
|
+
// Built from what was actually written, so the snippet can be pasted as-is.
|
|
74
|
+
const importPath = toImportSpecifier(relativeOutput);
|
|
75
|
+
const firstTable = generatedTables[0] ?? 'orders';
|
|
58
76
|
logger.info('Example usage:');
|
|
59
|
-
logger.indent(
|
|
77
|
+
logger.indent(`import { datasets } from '${importPath}';`);
|
|
60
78
|
logger.indent('import { createDatasetClient } from \'@hypequery/datasets\';');
|
|
61
79
|
logger.indent('import { createQueryBuilder } from \'@hypequery/clickhouse\';');
|
|
62
80
|
logger.indent('');
|
|
63
81
|
logger.indent('const db = createQueryBuilder({ url, username, password, database });');
|
|
64
82
|
logger.indent('const analytics = createDatasetClient({ queryBuilder: db });');
|
|
65
|
-
logger.indent(
|
|
83
|
+
logger.indent(`const rowCount = datasets['${firstTable}'].metric('rowCount', { measure: 'totalCount' });`);
|
|
66
84
|
logger.indent('const result = await analytics.execute(rowCount);');
|
|
67
85
|
logger.newline();
|
|
68
86
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAK/F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAK/F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,iBA6JlE"}
|
|
@@ -57,17 +57,22 @@ export async function generateCommand(options = {}) {
|
|
|
57
57
|
// Get table count
|
|
58
58
|
const tableCount = await getTableCount(dbType, { chdbPath });
|
|
59
59
|
spinner.succeed(`Connected to ${dbType === 'clickhouse' ? 'ClickHouse' : dbType === 'chdb' ? 'embedded chDB' : dbType}`);
|
|
60
|
-
logger.success(
|
|
60
|
+
logger.success(parsedTables
|
|
61
|
+
? `Found ${tableCount} tables, applying --tables filter`
|
|
62
|
+
: `Found ${tableCount} tables`);
|
|
61
63
|
// Generate types
|
|
62
64
|
const typeSpinner = ora('Generating types...').start();
|
|
63
65
|
activeSpinner = typeSpinner;
|
|
64
66
|
failureMessage = 'Failed to generate types';
|
|
65
|
-
await generator({
|
|
67
|
+
const generatedTables = await generator({
|
|
66
68
|
outputPath,
|
|
67
69
|
includeTables: parsedTables,
|
|
68
70
|
chdbPath,
|
|
69
71
|
});
|
|
70
|
-
|
|
72
|
+
// The generator returns what it actually wrote after applying filters.
|
|
73
|
+
// Requested names may contain duplicates or tables that do not exist.
|
|
74
|
+
const generatedCount = generatedTables.length;
|
|
75
|
+
typeSpinner.succeed(`Generated types for ${generatedCount} ${generatedCount === 1 ? 'table' : 'tables'}`);
|
|
71
76
|
logger.success(`Updated ${path.relative(process.cwd(), outputPath)}`);
|
|
72
77
|
logger.newline();
|
|
73
78
|
logger.header('Types regenerated successfully!');
|
|
@@ -9,5 +9,5 @@ export interface ChdbGeneratorOptions {
|
|
|
9
9
|
* DESCRIBE TABLE) is identical on the embedded engine — but the client seam
|
|
10
10
|
* is backed by a chDB session instead of an HTTP connection.
|
|
11
11
|
*/
|
|
12
|
-
export declare function generateChdbTypes(options: ChdbGeneratorOptions): Promise<
|
|
12
|
+
export declare function generateChdbTypes(options: ChdbGeneratorOptions): Promise<string[]>;
|
|
13
13
|
//# sourceMappingURL=chdb.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chdb.d.ts","sourceRoot":"","sources":["../../src/generators/chdb.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,
|
|
1
|
+
{"version":3,"file":"chdb.d.ts","sourceRoot":"","sources":["../../src/generators/chdb.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,qBAUpE"}
|
package/dist/generators/chdb.js
CHANGED
|
@@ -13,5 +13,5 @@ export async function generateChdbTypes(options) {
|
|
|
13
13
|
...(options.includeTables ? { includeTables: options.includeTables } : {}),
|
|
14
14
|
...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
|
|
15
15
|
};
|
|
16
|
-
|
|
16
|
+
return generateTypes(options.outputPath, generatorOptions);
|
|
17
17
|
}
|
|
@@ -5,5 +5,5 @@ export interface ClickHouseGeneratorOptions {
|
|
|
5
5
|
excludeTables?: string[];
|
|
6
6
|
}
|
|
7
7
|
export { clickhouseToTsType };
|
|
8
|
-
export declare function generateClickHouseTypes(options: ClickHouseGeneratorOptions): Promise<
|
|
8
|
+
export declare function generateClickHouseTypes(options: ClickHouseGeneratorOptions): Promise<string[]>;
|
|
9
9
|
//# sourceMappingURL=clickhouse.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,0BAA0B,
|
|
1
|
+
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,qBAAqB,CAAC;AAG7B,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,0BAA0B,qBAUhF"}
|
|
@@ -9,5 +9,5 @@ export async function generateClickHouseTypes(options) {
|
|
|
9
9
|
...(options.includeTables ? { includeTables: options.includeTables } : {}),
|
|
10
10
|
...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
|
|
11
11
|
};
|
|
12
|
-
|
|
12
|
+
return generateTypes(options.outputPath, generatorOptions);
|
|
13
13
|
}
|
|
@@ -14,5 +14,11 @@ export interface DatasetGeneratorOptions {
|
|
|
14
14
|
/**
|
|
15
15
|
* Generate dataset definitions from ClickHouse schema
|
|
16
16
|
*/
|
|
17
|
-
export declare function generateDatasets(options: DatasetGeneratorOptions): Promise<
|
|
17
|
+
export declare function generateDatasets(options: DatasetGeneratorOptions): Promise<{
|
|
18
|
+
tables: string[];
|
|
19
|
+
exports: {
|
|
20
|
+
table: string;
|
|
21
|
+
exportName: string;
|
|
22
|
+
}[];
|
|
23
|
+
}>;
|
|
18
24
|
//# sourceMappingURL=dataset-generator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;CACzC;AAuPD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB
|
|
1
|
+
{"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;CACzC;AAuPD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB;;;;;;GAmEtE"}
|
|
@@ -243,4 +243,13 @@ ${tables.map((table) => ` ${table.name}: ${tableToPascalCase(table.name)}Datase
|
|
|
243
243
|
await fs.mkdir(outputDir, { recursive: true });
|
|
244
244
|
// Write the file
|
|
245
245
|
await fs.writeFile(path.resolve(options.outputPath), output);
|
|
246
|
+
// Returned so the CLI can report what it actually generated, and print an
|
|
247
|
+
// example that matches the file rather than a hardcoded placeholder.
|
|
248
|
+
return {
|
|
249
|
+
tables: tables.map((table) => table.name),
|
|
250
|
+
exports: tables.map((table) => ({
|
|
251
|
+
table: table.name,
|
|
252
|
+
exportName: `${tableToPascalCase(table.name)}Dataset`,
|
|
253
|
+
})),
|
|
254
|
+
};
|
|
246
255
|
}
|
|
@@ -2,7 +2,7 @@ import type { DatabaseType } from '../utils/detect-database.js';
|
|
|
2
2
|
import type { ClickHouseGeneratorOptions } from './clickhouse.js';
|
|
3
3
|
import type { ChdbGeneratorOptions } from './chdb.js';
|
|
4
4
|
export type TypeGeneratorOptions = ClickHouseGeneratorOptions & ChdbGeneratorOptions;
|
|
5
|
-
type GeneratorFn = (options: TypeGeneratorOptions) => Promise<
|
|
5
|
+
type GeneratorFn = (options: TypeGeneratorOptions) => Promise<string[]>;
|
|
6
6
|
export declare function getTypeGenerator(dbType: DatabaseType): GeneratorFn;
|
|
7
7
|
export {};
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,oBAAoB,CAAC;AAErF,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEtD,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,oBAAoB,CAAC;AAErF,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AAiBxE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAYlE"}
|
package/dist/generators/index.js
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
const generators = {
|
|
6
6
|
clickhouse: async (options) => {
|
|
7
7
|
const { generateClickHouseTypes } = await import('./clickhouse.js');
|
|
8
|
-
|
|
8
|
+
return generateClickHouseTypes(options);
|
|
9
9
|
},
|
|
10
10
|
chdb: async (options) => {
|
|
11
11
|
const { generateChdbTypes } = await import('./chdb.js');
|
|
12
|
-
|
|
12
|
+
return generateChdbTypes(options);
|
|
13
13
|
},
|
|
14
14
|
};
|
|
15
15
|
export function getTypeGenerator(dbType) {
|
|
@@ -41,6 +41,7 @@ export declare function generateTypeDefinitions(client: TypeGenerationClickHouse
|
|
|
41
41
|
/**
|
|
42
42
|
* Generates TypeScript type definitions from the ClickHouse database schema and
|
|
43
43
|
* writes them to `outputPath`, creating the containing directory if needed.
|
|
44
|
+
* Returns the names of the tables represented in the generated file.
|
|
44
45
|
*/
|
|
45
|
-
export declare function generateTypes(outputPath: string, options: GenerateTypesOptions): Promise<
|
|
46
|
+
export declare function generateTypes(outputPath: string, options: GenerateTypesOptions): Promise<string[]>;
|
|
46
47
|
//# sourceMappingURL=generate-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-types.d.ts","sourceRoot":"","sources":["../../src/typegen/generate-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,GAAG,OAAO,CAAC;QAChE,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;KAChD,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,qCAAqC;IACrC,MAAM,EAAE,8BAA8B,CAAC;IACvC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;
|
|
1
|
+
{"version":3,"file":"generate-types.d.ts","sourceRoot":"","sources":["../../src/typegen/generate-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH;;;GAGG;AACH,MAAM,WAAW,8BAA8B;IAC7C,KAAK,CAAC,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,GAAG,OAAO,CAAC;QAChE,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC;KAChD,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,qCAAqC;IACrC,MAAM,EAAE,8BAA8B,CAAC;IACvC,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yDAAyD;IACzD,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AA0HD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,8BAA8B,EACtC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB"}
|
|
@@ -29,7 +29,7 @@ async function describeTable(client, tableName) {
|
|
|
29
29
|
/**
|
|
30
30
|
* Generates the TypeScript type definition source from a ClickHouse client.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
async function buildTypeDefinitions(client, options = {}) {
|
|
33
33
|
const { includeTables = [], excludeTables = [], generatedBy = 'hypequery', includeUsageExample = true, } = options;
|
|
34
34
|
// Get all tables
|
|
35
35
|
const tablesQuery = await client.query({
|
|
@@ -61,8 +61,7 @@ export interface IntrospectedSchema {`;
|
|
|
61
61
|
const columns = await describeTable(client, table.name);
|
|
62
62
|
typeDefinitions += `\n ${table.name}: {`;
|
|
63
63
|
for (const column of columns) {
|
|
64
|
-
|
|
65
|
-
typeDefinitions += `\n '${column.name}': '${clickHouseType}';`;
|
|
64
|
+
typeDefinitions += `\n '${column.name}': ${JSON.stringify(column.type)};`;
|
|
66
65
|
}
|
|
67
66
|
typeDefinitions += '\n };';
|
|
68
67
|
}
|
|
@@ -73,7 +72,7 @@ export interface IntrospectedSchema {`;
|
|
|
73
72
|
const columns = await describeTable(client, table.name);
|
|
74
73
|
typeDefinitions += `export interface ${capitalizeFirstLetter(table.name)}Record {`;
|
|
75
74
|
for (const column of columns) {
|
|
76
|
-
const tsType = clickhouseToTsType(column.type)
|
|
75
|
+
const tsType = clickhouseToTsType(column.type);
|
|
77
76
|
typeDefinitions += `\n '${column.name}': ${tsType};`;
|
|
78
77
|
}
|
|
79
78
|
typeDefinitions += '\n}\n\n';
|
|
@@ -98,18 +97,30 @@ export interface IntrospectedSchema {`;
|
|
|
98
97
|
*/
|
|
99
98
|
`;
|
|
100
99
|
}
|
|
101
|
-
return
|
|
100
|
+
return {
|
|
101
|
+
contents: typeDefinitions,
|
|
102
|
+
tableNames: tables.map((table) => table.name),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Generates the TypeScript type definition source from a ClickHouse client.
|
|
107
|
+
*/
|
|
108
|
+
export async function generateTypeDefinitions(client, options = {}) {
|
|
109
|
+
const generated = await buildTypeDefinitions(client, options);
|
|
110
|
+
return generated.contents;
|
|
102
111
|
}
|
|
103
112
|
/**
|
|
104
113
|
* Generates TypeScript type definitions from the ClickHouse database schema and
|
|
105
114
|
* writes them to `outputPath`, creating the containing directory if needed.
|
|
115
|
+
* Returns the names of the tables represented in the generated file.
|
|
106
116
|
*/
|
|
107
117
|
export async function generateTypes(outputPath, options) {
|
|
108
118
|
const { client, ...definitionOptions } = options;
|
|
109
|
-
const
|
|
119
|
+
const generated = await buildTypeDefinitions(client, definitionOptions);
|
|
110
120
|
// Ensure the output directory exists
|
|
111
121
|
const outputDir = path.dirname(path.resolve(outputPath));
|
|
112
122
|
await fs.mkdir(outputDir, { recursive: true });
|
|
113
123
|
// Write the file
|
|
114
|
-
await fs.writeFile(path.resolve(outputPath),
|
|
124
|
+
await fs.writeFile(path.resolve(outputPath), generated.contents);
|
|
125
|
+
return generated.tableNames;
|
|
115
126
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-parsing.d.ts","sourceRoot":"","sources":["../../src/typegen/type-parsing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"type-parsing.d.ts","sourceRoot":"","sources":["../../src/typegen/type-parsing.ts"],"names":[],"mappings":"AAwLA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MA6CjD,CAAC"}
|
|
@@ -2,7 +2,27 @@ function splitTopLevelArgs(value) {
|
|
|
2
2
|
const parts = [];
|
|
3
3
|
let current = '';
|
|
4
4
|
let depth = 0;
|
|
5
|
+
let quote = null;
|
|
6
|
+
let escaped = false;
|
|
5
7
|
for (const char of value) {
|
|
8
|
+
if (quote) {
|
|
9
|
+
current += char;
|
|
10
|
+
if (escaped) {
|
|
11
|
+
escaped = false;
|
|
12
|
+
}
|
|
13
|
+
else if (char === '\\') {
|
|
14
|
+
escaped = true;
|
|
15
|
+
}
|
|
16
|
+
else if (char === quote) {
|
|
17
|
+
quote = null;
|
|
18
|
+
}
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (char === "'" || char === '"' || char === '`') {
|
|
22
|
+
quote = char;
|
|
23
|
+
current += char;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
6
26
|
if (char === '(') {
|
|
7
27
|
depth += 1;
|
|
8
28
|
current += char;
|
|
@@ -25,6 +45,50 @@ function splitTopLevelArgs(value) {
|
|
|
25
45
|
}
|
|
26
46
|
return parts;
|
|
27
47
|
}
|
|
48
|
+
function decodeClickHouseEscape(char) {
|
|
49
|
+
switch (char) {
|
|
50
|
+
case 'b': return '\b';
|
|
51
|
+
case 'f': return '\f';
|
|
52
|
+
case 'n': return '\n';
|
|
53
|
+
case 'r': return '\r';
|
|
54
|
+
case 't': return '\t';
|
|
55
|
+
case '0': return '\0';
|
|
56
|
+
default: return char;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function parseBacktickTuplePart(part) {
|
|
60
|
+
let name = '';
|
|
61
|
+
let escaped = false;
|
|
62
|
+
for (let index = 1; index < part.length; index += 1) {
|
|
63
|
+
const char = part[index];
|
|
64
|
+
if (escaped) {
|
|
65
|
+
name += decodeClickHouseEscape(char);
|
|
66
|
+
escaped = false;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (char === '\\') {
|
|
70
|
+
escaped = true;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (char === '`') {
|
|
74
|
+
const type = part.slice(index + 1);
|
|
75
|
+
return /^\s+\S/s.test(type) ? { name, type: type.trim() } : null;
|
|
76
|
+
}
|
|
77
|
+
name += char;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
function parseNamedTuplePart(value) {
|
|
82
|
+
const part = value.trim();
|
|
83
|
+
if (part.startsWith('`')) {
|
|
84
|
+
return parseBacktickTuplePart(part);
|
|
85
|
+
}
|
|
86
|
+
const match = /^([A-Za-z_][A-Za-z0-9_]*)\s+(.+)$/s.exec(part);
|
|
87
|
+
return match ? { name: match[1], type: match[2].trim() } : null;
|
|
88
|
+
}
|
|
89
|
+
function formatTypeScriptProperty(name) {
|
|
90
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name) ? name : JSON.stringify(name);
|
|
91
|
+
}
|
|
28
92
|
function unwrapType(type, wrapperName) {
|
|
29
93
|
// Trim whitespace and handle case-insensitive matching
|
|
30
94
|
const trimmedType = type.trim();
|
|
@@ -104,24 +168,31 @@ function getPrimitiveTsType(type) {
|
|
|
104
168
|
* equivalent TypeScript type string.
|
|
105
169
|
*/
|
|
106
170
|
export const clickhouseToTsType = (type) => {
|
|
107
|
-
const
|
|
171
|
+
const normalizedType = type.trim();
|
|
172
|
+
const wrappedArrayType = unwrapType(normalizedType, 'Array');
|
|
108
173
|
if (wrappedArrayType) {
|
|
109
174
|
return `Array<${clickhouseToTsType(wrappedArrayType)}>`;
|
|
110
175
|
}
|
|
111
|
-
const wrappedNullableType = unwrapType(
|
|
176
|
+
const wrappedNullableType = unwrapType(normalizedType, 'Nullable');
|
|
112
177
|
if (wrappedNullableType) {
|
|
113
178
|
return `${clickhouseToTsType(wrappedNullableType)} | null`;
|
|
114
179
|
}
|
|
115
|
-
const wrappedLowCardinalityType = unwrapType(
|
|
180
|
+
const wrappedLowCardinalityType = unwrapType(normalizedType, 'LowCardinality');
|
|
116
181
|
if (wrappedLowCardinalityType) {
|
|
117
182
|
return clickhouseToTsType(wrappedLowCardinalityType);
|
|
118
183
|
}
|
|
119
|
-
const wrappedTupleType = unwrapType(
|
|
184
|
+
const wrappedTupleType = unwrapType(normalizedType, 'Tuple');
|
|
120
185
|
if (wrappedTupleType) {
|
|
121
186
|
const tupleParts = splitTopLevelArgs(wrappedTupleType);
|
|
187
|
+
const namedParts = tupleParts.map(parseNamedTuplePart);
|
|
188
|
+
if (namedParts.length > 0 && namedParts.every((part) => part !== null)) {
|
|
189
|
+
return `{ ${namedParts
|
|
190
|
+
.map((part) => `${formatTypeScriptProperty(part.name)}: ${clickhouseToTsType(part.type)}`)
|
|
191
|
+
.join('; ')} }`;
|
|
192
|
+
}
|
|
122
193
|
return `[${tupleParts.map((part) => clickhouseToTsType(part)).join(', ')}]`;
|
|
123
194
|
}
|
|
124
|
-
const wrappedMapType = unwrapType(
|
|
195
|
+
const wrappedMapType = unwrapType(normalizedType, 'Map');
|
|
125
196
|
if (wrappedMapType) {
|
|
126
197
|
const mapParts = splitTopLevelArgs(wrappedMapType);
|
|
127
198
|
if (mapParts.length === 2) {
|
|
@@ -131,7 +202,7 @@ export const clickhouseToTsType = (type) => {
|
|
|
131
202
|
}
|
|
132
203
|
return 'Record<string, unknown>';
|
|
133
204
|
}
|
|
134
|
-
const primitiveType = getPrimitiveTsType(
|
|
205
|
+
const primitiveType = getPrimitiveTsType(normalizedType);
|
|
135
206
|
if (primitiveType)
|
|
136
207
|
return primitiveType;
|
|
137
208
|
// Unsupported or more complex ClickHouse types currently preserve the historical fallback.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env files the CLI loads, in precedence order, most specific first.
|
|
3
|
+
*
|
|
4
|
+
* This mirrors the cascade Next.js and Vite use. `dotenv` never overwrites a
|
|
5
|
+
* variable that is already set, so loading in this order means earlier entries
|
|
6
|
+
* win, and a real `process.env` value beats every file.
|
|
7
|
+
*
|
|
8
|
+
* `.env.local` matters in particular: it is the file those frameworks read, so
|
|
9
|
+
* without it a project could have working app credentials and a CLI that could
|
|
10
|
+
* not connect — with an error that never mentions the env file.
|
|
11
|
+
*/
|
|
12
|
+
export declare function envFiles(nodeEnv: string | undefined): string[];
|
|
13
|
+
//# sourceMappingURL=env-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-files.d.ts","sourceRoot":"","sources":["../../src/utils/env-files.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,EAAE,CAO9D"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Env files the CLI loads, in precedence order, most specific first.
|
|
3
|
+
*
|
|
4
|
+
* This mirrors the cascade Next.js and Vite use. `dotenv` never overwrites a
|
|
5
|
+
* variable that is already set, so loading in this order means earlier entries
|
|
6
|
+
* win, and a real `process.env` value beats every file.
|
|
7
|
+
*
|
|
8
|
+
* `.env.local` matters in particular: it is the file those frameworks read, so
|
|
9
|
+
* without it a project could have working app credentials and a CLI that could
|
|
10
|
+
* not connect — with an error that never mentions the env file.
|
|
11
|
+
*/
|
|
12
|
+
export function envFiles(nodeEnv) {
|
|
13
|
+
return [
|
|
14
|
+
...(nodeEnv ? [`.env.${nodeEnv}.local`] : []),
|
|
15
|
+
'.env.local',
|
|
16
|
+
...(nodeEnv ? [`.env.${nodeEnv}`] : []),
|
|
17
|
+
'.env',
|
|
18
|
+
];
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"clickhouse",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"@vitest/coverage-v8": "^3.2.6",
|
|
46
46
|
"typescript": "^5.7.3",
|
|
47
47
|
"vitest": "^3.2.6",
|
|
48
|
-
"@hypequery/serve": "0.
|
|
48
|
+
"@hypequery/serve": "0.16.0"
|
|
49
49
|
},
|
|
50
50
|
"repository": {
|
|
51
51
|
"type": "git",
|