@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 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
- return;
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
- config();
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: src/datasets/generated.ts)')
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;AAWD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBA0FlF"}
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
- // Default to src/datasets/generated.ts
31
- outputPath = path.join(process.cwd(), 'src', 'datasets', 'generated.ts');
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(`Found ${tableCount} tables`);
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
- datasetSpinner.succeed(`Generated dataset definitions for ${parsedTables?.length || tableCount} tables`);
51
- logger.success(`Created ${path.relative(process.cwd(), outputPath)}`);
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('import { datasets } from \'./datasets/generated\';');
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('const rowCount = datasets.orders.metric(\'rowCount\', { measure: \'totalCount\' });');
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,iBAoJlE"}
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(`Found ${tableCount} tables`);
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
- typeSpinner.succeed(`Generated types for ${tableCount} tables`);
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<void>;
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,iBAUpE"}
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"}
@@ -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
- await generateTypes(options.outputPath, generatorOptions);
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<void>;
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,iBAUhF"}
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
- await generateTypes(options.outputPath, generatorOptions);
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<void>;
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,iBAyDtE"}
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<void>;
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,IAAI,CAAC,CAAC;AAiBpE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAYlE"}
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"}
@@ -5,11 +5,11 @@
5
5
  const generators = {
6
6
  clickhouse: async (options) => {
7
7
  const { generateClickHouseTypes } = await import('./clickhouse.js');
8
- await generateClickHouseTypes(options);
8
+ return generateClickHouseTypes(options);
9
9
  },
10
10
  chdb: async (options) => {
11
11
  const { generateChdbTypes } = await import('./chdb.js');
12
- await generateChdbTypes(options);
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<void>;
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;AAqBD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,8BAA8B,EACtC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,MAAM,CAAC,CAsFjB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAUpG"}
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
- export async function generateTypeDefinitions(client, options = {}) {
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
- const clickHouseType = column.type.replace(/'/g, "\\'"); // Escape single quotes, e.g. `DateTime('UTC')`
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).replace(/'/g, '');
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 typeDefinitions;
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 typeDefinitions = await generateTypeDefinitions(client, definitionOptions);
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), typeDefinitions);
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":"AAuGA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAsCjD,CAAC"}
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 wrappedArrayType = unwrapType(type, 'Array');
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(type, 'Nullable');
176
+ const wrappedNullableType = unwrapType(normalizedType, 'Nullable');
112
177
  if (wrappedNullableType) {
113
178
  return `${clickhouseToTsType(wrappedNullableType)} | null`;
114
179
  }
115
- const wrappedLowCardinalityType = unwrapType(type, 'LowCardinality');
180
+ const wrappedLowCardinalityType = unwrapType(normalizedType, 'LowCardinality');
116
181
  if (wrappedLowCardinalityType) {
117
182
  return clickhouseToTsType(wrappedLowCardinalityType);
118
183
  }
119
- const wrappedTupleType = unwrapType(type, 'Tuple');
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(type, 'Map');
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(type);
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.16.4",
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.15.3"
48
+ "@hypequery/serve": "0.16.0"
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",