@hypequery/cli 1.3.1 → 1.4.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.
Files changed (40) hide show
  1. package/README.md +27 -10
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -1
  4. package/dist/commands/generate.d.ts +1 -0
  5. package/dist/commands/generate.d.ts.map +1 -1
  6. package/dist/commands/generate.js +22 -3
  7. package/dist/commands/init.d.ts +2 -0
  8. package/dist/commands/init.d.ts.map +1 -1
  9. package/dist/commands/init.js +180 -41
  10. package/dist/generators/chdb.d.ts +13 -0
  11. package/dist/generators/chdb.d.ts.map +1 -0
  12. package/dist/generators/chdb.js +17 -0
  13. package/dist/generators/dataset-generator.d.ts +2 -0
  14. package/dist/generators/dataset-generator.d.ts.map +1 -1
  15. package/dist/generators/dataset-generator.js +1 -1
  16. package/dist/generators/index.d.ts +2 -1
  17. package/dist/generators/index.d.ts.map +1 -1
  18. package/dist/generators/index.js +2 -0
  19. package/dist/templates/client.d.ts +7 -2
  20. package/dist/templates/client.d.ts.map +1 -1
  21. package/dist/templates/client.js +23 -2
  22. package/dist/templates/gitignore.d.ts +1 -1
  23. package/dist/templates/gitignore.d.ts.map +1 -1
  24. package/dist/templates/gitignore.js +20 -6
  25. package/dist/utils/chdb-client.d.ts +36 -0
  26. package/dist/utils/chdb-client.d.ts.map +1 -0
  27. package/dist/utils/chdb-client.js +122 -0
  28. package/dist/utils/dependency-installer.d.ts +3 -2
  29. package/dist/utils/dependency-installer.d.ts.map +1 -1
  30. package/dist/utils/dependency-installer.js +8 -3
  31. package/dist/utils/detect-database.d.ts +11 -4
  32. package/dist/utils/detect-database.d.ts.map +1 -1
  33. package/dist/utils/detect-database.js +23 -5
  34. package/dist/utils/prompts.d.ts +6 -8
  35. package/dist/utils/prompts.d.ts.map +1 -1
  36. package/dist/utils/prompts.js +31 -24
  37. package/package.json +3 -3
  38. package/dist/utils/clickhouse-sql.d.ts +0 -6
  39. package/dist/utils/clickhouse-sql.d.ts.map +0 -1
  40. package/dist/utils/clickhouse-sql.js +0 -18
package/README.md CHANGED
@@ -4,7 +4,7 @@ CLI for scaffolding and running the main hypequery path.
4
4
 
5
5
  Use it to:
6
6
 
7
- - generate schema types from ClickHouse
7
+ - generate schema types from ClickHouse or embedded chDB
8
8
  - scaffold `analytics/` files
9
9
  - run the local dev server with docs
10
10
 
@@ -36,27 +36,36 @@ npx hypequery init
36
36
 
37
37
  It will:
38
38
 
39
- - generate schema types
39
+ - connect to ClickHouse or start an embedded chDB session
40
+ - generate schema types when the database is available
40
41
  - create client and query files
41
- - write `.env` values
42
- - update `.gitignore`
43
- - install scaffold dependencies, including `zod`
42
+ - write `.env` values for ClickHouse connections
43
+ - update `.gitignore`, including a project-local persistent chDB directory
44
+ - install scaffold dependencies, including `zod` and the selected database adapter
44
45
 
45
46
  Options:
46
47
 
47
48
  - `--path <path>`: output directory, default `analytics/`
48
49
  - `--style <style>`: `queries` (default) or `datasets`
50
+ - `--database <type>`: `clickhouse` (default) or `chdb`
51
+ - `--chdb-path <path>`: persistent chDB data directory; omit for an in-memory session
49
52
  - `--auth <mode>`: `none` (default) or `context`
50
53
  - `--all-tables`: with `--style datasets`, scaffold every table
51
54
  - `--tables <names>`: with `--style datasets`, scaffold these comma-separated tables
52
55
  - `--exclude-tables <names>`: with `--style datasets`, exclude these comma-separated tables
53
56
  - `--no-example`: skip the example query
54
- - `--no-interactive`: read connection details from env vars
57
+ - `--no-interactive`: skip prompts; ClickHouse connection details come from env vars
55
58
  - `--force`: overwrite existing scaffold files
56
- - `--skip-connection`: skip testing the ClickHouse connection before scaffolding
59
+ - `--skip-connection`: skip testing the selected database before scaffolding
57
60
 
58
61
  Set `HYPEQUERY_SKIP_INSTALL=1` to skip the automatic dependency install.
59
62
 
63
+ To scaffold against persistent embedded chDB without server credentials:
64
+
65
+ ```bash
66
+ npx hypequery init --database chdb --chdb-path ./analytics.chdb --no-interactive
67
+ ```
68
+
60
69
  ### `hypequery dev`
61
70
 
62
71
  Runs the local serve runtime with docs and hot reload.
@@ -78,7 +87,7 @@ The CLI understands TypeScript entry files directly, so `analytics/queries.ts` w
78
87
 
79
88
  ### `hypequery generate`
80
89
 
81
- Regenerates schema types from ClickHouse.
90
+ Regenerates schema types from ClickHouse or embedded chDB.
82
91
 
83
92
  ```bash
84
93
  npx hypequery generate
@@ -89,7 +98,14 @@ Options:
89
98
  - `--output <path>`: default `analytics/schema.ts`
90
99
  - `--path <path>`: analytics directory (derives `<path>/schema.ts`)
91
100
  - `--tables <names>`: comma-separated table list
92
- - `--database <type>`: currently `clickhouse`
101
+ - `--database <type>`: `clickhouse` or `chdb`; chDB generation must be selected explicitly
102
+ - `--chdb-path <path>`: persistent chDB data directory; omit for an in-memory session
103
+
104
+ For a persistent chDB scaffold, pass the same path used by `init`:
105
+
106
+ ```bash
107
+ npx hypequery generate --database chdb --chdb-path ./analytics.chdb
108
+ ```
93
109
 
94
110
  `hypequery generate:types` is an alias for `hypequery generate`.
95
111
 
@@ -121,7 +137,7 @@ semantic keys such as `dataset:orders`.
121
137
 
122
138
  ## Non-interactive Setup
123
139
 
124
- `hypequery init --no-interactive` reads:
140
+ For ClickHouse, `hypequery init --no-interactive` reads:
125
141
 
126
142
  - `CLICKHOUSE_URL` or deprecated `CLICKHOUSE_HOST`
127
143
  - `CLICKHOUSE_DATABASE`
@@ -133,6 +149,7 @@ semantic keys such as `dataset:orders`.
133
149
  - generated scaffold files use NodeNext-safe local `.js` imports
134
150
  - `CLICKHOUSE_URL` is now the preferred connection variable
135
151
  - the CLI bundles the ClickHouse driver for schema generation
152
+ - chDB runs in memory unless `--chdb-path` is provided; persistent paths must be reused by later `generate` commands
136
153
 
137
154
  ## Docs
138
155
 
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAgID,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAmID,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -43,7 +43,8 @@ function addTypeGenerationOptions(command) {
43
43
  .option('-o, --output <path>', 'Output file (default: analytics/schema.ts)')
44
44
  .option('--path <path>', 'Analytics directory (derives <path>/schema.ts)')
45
45
  .option('--tables <names>', 'Only generate for specific tables (comma-separated)')
46
- .option('--database <type>', 'Database driver to use (default: auto-detect)');
46
+ .option('--database <type>', 'Database driver to use: clickhouse or chdb (default: auto-detect)')
47
+ .option('--chdb-path <path>', 'Embedded chDB data directory (with --database chdb; omit for in-memory)');
47
48
  }
48
49
  // Init command
49
50
  program
@@ -51,6 +52,8 @@ program
51
52
  .description('Initialize a new hypequery project')
52
53
  .option('--path <path>', 'Output directory (default: analytics/)')
53
54
  .option('--style <style>', 'Scaffold style: queries or datasets')
55
+ .option('--database <type>', 'Database driver: clickhouse (default) or chdb for embedded, zero-server ClickHouse')
56
+ .option('--chdb-path <path>', 'Embedded chDB data directory (with --database chdb; omit for in-memory)')
54
57
  .option('--auth <mode>', 'Auth scaffold mode: none or context')
55
58
  .option('--all-tables', 'Generate datasets for all discovered tables when using --style datasets')
56
59
  .option('--tables <names>', 'Generate datasets for specific tables when using --style datasets (comma-separated)')
@@ -4,6 +4,7 @@ export interface GenerateOptions {
4
4
  path?: string;
5
5
  tables?: string;
6
6
  database?: DatabaseType;
7
+ chdbPath?: string;
7
8
  commandName?: string;
8
9
  }
9
10
  export declare function generateCommand(options?: GenerateOptions): Promise<void>;
@@ -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;AAI/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,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,iBA4HlE"}
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"}
@@ -3,6 +3,7 @@ import ora from 'ora';
3
3
  import { logger } from '../utils/logger.js';
4
4
  import { findSchemaFile } from '../utils/find-files.js';
5
5
  import { detectDatabase, getTableCount } from '../utils/detect-database.js';
6
+ import { ensureChdbInstalled } from '../utils/chdb-client.js';
6
7
  import { getTypeGenerator } from '../generators/index.js';
7
8
  import { redactConnectionUrl } from '../utils/redact-connection-url.js';
8
9
  export async function generateCommand(options = {}) {
@@ -33,20 +34,38 @@ export async function generateCommand(options = {}) {
33
34
  : undefined;
34
35
  const requestedDbType = options.database;
35
36
  const dbType = requestedDbType ?? (await detectDatabase());
37
+ const chdbPath = dbType === 'chdb' ? options.chdbPath : undefined;
38
+ if (dbType === 'chdb' && !requestedDbType) {
39
+ throw new Error('chDB generation must be explicit. Re-run with --database chdb and add --chdb-path <dir> for persistent storage.');
40
+ }
36
41
  logger.newline();
37
42
  logger.header(options.commandName ?? 'hypequery generate');
38
43
  const spinner = ora(`Connecting to ${dbType}...`).start();
44
+ let activeSpinner = spinner;
45
+ let failureMessage = dbType === 'chdb'
46
+ ? 'Failed to start embedded chDB'
47
+ : 'Failed to generate types';
39
48
  try {
40
49
  const generator = getTypeGenerator(dbType);
50
+ // Surface a missing chdb install as the connection failure it is —
51
+ // getTableCount swallows errors, so without this probe the spinner
52
+ // would report a successful connection and 0 tables before type
53
+ // generation fails.
54
+ if (dbType === 'chdb') {
55
+ await ensureChdbInstalled();
56
+ }
41
57
  // Get table count
42
- const tableCount = await getTableCount(dbType);
43
- spinner.succeed(`Connected to ${dbType === 'clickhouse' ? 'ClickHouse' : dbType}`);
58
+ const tableCount = await getTableCount(dbType, { chdbPath });
59
+ spinner.succeed(`Connected to ${dbType === 'clickhouse' ? 'ClickHouse' : dbType === 'chdb' ? 'embedded chDB' : dbType}`);
44
60
  logger.success(`Found ${tableCount} tables`);
45
61
  // Generate types
46
62
  const typeSpinner = ora('Generating types...').start();
63
+ activeSpinner = typeSpinner;
64
+ failureMessage = 'Failed to generate types';
47
65
  await generator({
48
66
  outputPath,
49
67
  includeTables: parsedTables,
68
+ chdbPath,
50
69
  });
51
70
  typeSpinner.succeed(`Generated types for ${tableCount} tables`);
52
71
  logger.success(`Updated ${path.relative(process.cwd(), outputPath)}`);
@@ -55,7 +74,7 @@ export async function generateCommand(options = {}) {
55
74
  logger.newline();
56
75
  }
57
76
  catch (error) {
58
- spinner.fail('Failed to generate types');
77
+ activeSpinner.fail(failureMessage);
59
78
  logger.newline();
60
79
  if (error instanceof Error) {
61
80
  logger.error(error.message);
@@ -3,6 +3,8 @@ import { type AuthTemplateMode } from '../templates/queries.js';
3
3
  export interface InitOptions {
4
4
  path?: string;
5
5
  style?: InitStyle;
6
+ database?: string;
7
+ chdbPath?: string;
6
8
  allTables?: boolean;
7
9
  tables?: string;
8
10
  excludeTables?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,EAUL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAS7B,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAQzF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAwFD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAsT1D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,EAWL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAQzF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AA4JD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAyZ1D"}
@@ -2,8 +2,9 @@ import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import ora from 'ora';
4
4
  import { logger } from '../utils/logger.js';
5
- import { promptClickHouseConnection, promptOutputDirectory, promptInitStyle, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
5
+ import { promptClickHouseConnection, promptChdbStorage, promptOutputDirectory, promptInitStyle, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
6
6
  import { validateConnection, getTableCount, getTables, } from '../utils/detect-database.js';
7
+ import { ChdbNotInstalledError, ensureChdbInstalled, getChdbTypeGenerationClient, } from '../utils/chdb-client.js';
7
8
  import { hasEnvFile, hasGitignore } from '../utils/find-files.js';
8
9
  import { generateEnvTemplate, appendToEnv } from '../templates/env.js';
9
10
  import { generateClientTemplate } from '../templates/client.js';
@@ -14,6 +15,15 @@ import { appendToGitignore } from '../templates/gitignore.js';
14
15
  import { getTypeGenerator } from '../generators/index.js';
15
16
  import { generateDatasets } from '../generators/dataset-generator.js';
16
17
  import { installScaffoldDependencies } from '../utils/dependency-installer.js';
18
+ function normalizeInitDatabase(database) {
19
+ if (!database || database === 'clickhouse') {
20
+ return 'clickhouse';
21
+ }
22
+ if (database === 'chdb') {
23
+ return 'chdb';
24
+ }
25
+ throw new Error(`Unsupported database "${database}". Use "clickhouse" or "chdb".`);
26
+ }
17
27
  function normalizeInitStyle(style) {
18
28
  return style === 'datasets' ? 'datasets' : 'queries';
19
29
  }
@@ -33,6 +43,22 @@ function parseTableList(value) {
33
43
  .filter(Boolean);
34
44
  return parsed && parsed.length > 0 ? parsed : undefined;
35
45
  }
46
+ function getChdbGitignoreEntry(chdbPath) {
47
+ if (!chdbPath || /[\0\r\n]/.test(chdbPath)) {
48
+ return undefined;
49
+ }
50
+ const cwd = path.resolve(process.cwd());
51
+ const relativePath = path.relative(cwd, path.resolve(cwd, chdbPath));
52
+ if (relativePath.length === 0 ||
53
+ relativePath === '..' ||
54
+ relativePath.startsWith(`..${path.sep}`) ||
55
+ path.isAbsolute(relativePath)) {
56
+ return undefined;
57
+ }
58
+ const normalizedPath = relativePath.split(path.sep).join('/');
59
+ const escapedPath = normalizedPath.replace(/[\\*?[\]]/g, '\\$&');
60
+ return `/${escapedPath}/`;
61
+ }
36
62
  async function resolveConnectionConfig(options) {
37
63
  if (options.noInteractive) {
38
64
  const required = (keys) => {
@@ -52,6 +78,34 @@ async function resolveConnectionConfig(options) {
52
78
  }
53
79
  return promptClickHouseConnection();
54
80
  }
81
+ async function testChdbConnection(chdbPath) {
82
+ const spinner = ora('Starting embedded chDB...').start();
83
+ try {
84
+ await ensureChdbInstalled();
85
+ }
86
+ catch (error) {
87
+ const isNotInstalled = error instanceof ChdbNotInstalledError;
88
+ spinner.fail(isNotInstalled ? 'chdb is not installed' : 'Embedded chDB failed to load');
89
+ logger.newline();
90
+ logger.error(error instanceof Error ? error.message : String(error));
91
+ logger.newline();
92
+ return { ok: false, reason: isNotInstalled ? 'not-installed' : 'engine-error' };
93
+ }
94
+ const isValid = await validateConnection('chdb', { chdbPath });
95
+ if (!isValid) {
96
+ spinner.fail('Embedded chDB failed to run a query');
97
+ logger.newline();
98
+ logger.info('Common issues:');
99
+ logger.indent('• Unsupported platform (chdb ships linux/macOS binaries; Windows needs WSL2)');
100
+ logger.indent(`• The session directory is locked by another process${chdbPath ? ` (${chdbPath})` : ''}`);
101
+ logger.newline();
102
+ return { ok: false, reason: 'engine-error' };
103
+ }
104
+ const tableCount = await getTableCount('chdb', { chdbPath });
105
+ spinner.succeed(`Embedded chDB ready (${chdbPath ? `${tableCount} tables in ${chdbPath}` : 'in-memory session'})`);
106
+ logger.newline();
107
+ return { ok: true };
108
+ }
55
109
  async function testConnection(connectionConfig) {
56
110
  const spinner = ora('Testing connection...').start();
57
111
  process.env.CLICKHOUSE_URL = connectionConfig.host;
@@ -80,43 +134,58 @@ async function testConnection(connectionConfig) {
80
134
  }
81
135
  export async function initCommand(options = {}) {
82
136
  const noInteractive = options.noInteractive === true || options.interactive === false;
137
+ const database = normalizeInitDatabase(options.database);
83
138
  logger.newline();
84
139
  logger.header('Welcome to hypequery!');
85
- logger.info("Let's set up your analytics layer.");
140
+ logger.info(database === 'chdb'
141
+ ? "Let's set up your analytics layer on embedded ClickHouse (chDB)."
142
+ : "Let's set up your analytics layer.");
86
143
  logger.newline();
87
144
  // Step 2: Get connection details
88
- let connectionConfig = await resolveConnectionConfig(options);
145
+ let connectionConfig = null;
89
146
  let hasValidConnection = false;
90
- // Handle user skipping connection details
91
- if (!connectionConfig) {
92
- logger.info('Skipping database connection for now.');
93
- logger.newline();
94
- }
95
- else if (options.skipConnection) {
96
- logger.info('Skipping database connection test (requested).');
97
- logger.newline();
147
+ let chdbPath = options.chdbPath;
148
+ let chdbFailureReason;
149
+ if (database === 'chdb') {
150
+ // No server, no credentials — the only connection question is where the
151
+ // embedded session stores its data.
152
+ if (!chdbPath && !noInteractive) {
153
+ chdbPath = await promptChdbStorage();
154
+ }
98
155
  }
99
156
  else {
100
- const { hasValidConnection: valid } = await testConnection(connectionConfig);
101
- hasValidConnection = valid;
102
- if (!hasValidConnection) {
103
- if (noInteractive) {
104
- throw new Error('Failed to connect to ClickHouse in non-interactive mode. Check your environment variables or use interactive setup.');
105
- }
106
- const retry = await promptRetry('Try again?');
107
- if (retry) {
108
- return initCommand(options);
109
- }
110
- const continueWithout = await promptContinueWithoutDb();
111
- if (!continueWithout) {
112
- logger.info('Setup cancelled');
113
- process.exit(0);
114
- }
157
+ connectionConfig = await resolveConnectionConfig(options);
158
+ // Handle user skipping connection details
159
+ if (!connectionConfig) {
160
+ logger.info('Skipping database connection for now.');
115
161
  logger.newline();
116
- logger.info('Continuing without database connection.');
117
- logger.info('You can configure the connection later in .env');
162
+ }
163
+ else if (options.skipConnection) {
164
+ logger.info('Skipping database connection test (requested).');
118
165
  logger.newline();
119
- connectionConfig = null;
166
+ }
167
+ else {
168
+ const { hasValidConnection: valid } = await testConnection(connectionConfig);
169
+ hasValidConnection = valid;
170
+ if (!hasValidConnection) {
171
+ if (noInteractive) {
172
+ throw new Error('Failed to connect to ClickHouse in non-interactive mode. Check your environment variables or use interactive setup.');
173
+ }
174
+ const retry = await promptRetry('Try again?');
175
+ if (retry) {
176
+ return initCommand(options);
177
+ }
178
+ const continueWithout = await promptContinueWithoutDb();
179
+ if (!continueWithout) {
180
+ logger.info('Setup cancelled');
181
+ process.exit(0);
182
+ }
183
+ logger.newline();
184
+ logger.info('Continuing without database connection.');
185
+ logger.info('You can configure the connection later in .env');
186
+ logger.newline();
187
+ connectionConfig = null;
188
+ }
120
189
  }
121
190
  }
122
191
  // Step 4: Get output directory
@@ -166,6 +235,37 @@ export async function initCommand(options = {}) {
166
235
  }
167
236
  logger.newline();
168
237
  }
238
+ if (database === 'chdb') {
239
+ // All prompts and overwrite checks are complete, so installing packages
240
+ // here cannot leave a cancelled scaffold with unexpected dependencies.
241
+ await installScaffoldDependencies(style, 'chdb');
242
+ if (options.skipConnection) {
243
+ logger.info('Skipping embedded chDB test (requested).');
244
+ logger.newline();
245
+ }
246
+ else {
247
+ const chdbTest = await testChdbConnection(chdbPath);
248
+ hasValidConnection = chdbTest.ok;
249
+ if (!chdbTest.ok) {
250
+ chdbFailureReason = chdbTest.reason;
251
+ }
252
+ if (!hasValidConnection) {
253
+ if (noInteractive) {
254
+ throw new Error(chdbFailureReason === 'not-installed'
255
+ ? 'Embedded chDB failed to start in non-interactive mode. Install the chdb package and re-run.'
256
+ : 'Embedded chDB failed to start in non-interactive mode. Resolve the engine error shown above and re-run.');
257
+ }
258
+ const continueWithout = await promptContinueWithoutDb();
259
+ if (!continueWithout) {
260
+ logger.info('Setup cancelled');
261
+ process.exit(0);
262
+ }
263
+ logger.newline();
264
+ logger.info('Continuing without a working embedded engine.');
265
+ logger.newline();
266
+ }
267
+ }
268
+ }
169
269
  // Step 6: Ask about example query (only if we have a valid connection)
170
270
  let generateExample = !options.noExample && hasValidConnection;
171
271
  let selectedTable = null;
@@ -173,7 +273,7 @@ export async function initCommand(options = {}) {
173
273
  if (generateExample && !noInteractive && hasValidConnection) {
174
274
  generateExample = await promptGenerateExample();
175
275
  if (generateExample) {
176
- discoveredTables = await getTables('clickhouse');
276
+ discoveredTables = await getTables(database, { chdbPath });
177
277
  selectedTable = await promptTableSelection(discoveredTables);
178
278
  generateExample = selectedTable !== null;
179
279
  }
@@ -185,14 +285,19 @@ export async function initCommand(options = {}) {
185
285
  !options.allTables &&
186
286
  !datasetTables &&
187
287
  !noInteractive) {
188
- discoveredTables ??= await getTables('clickhouse');
288
+ discoveredTables ??= await getTables(database, { chdbPath });
189
289
  datasetTables = await promptDatasetTableSelection(discoveredTables, selectedTable ? [selectedTable] : []);
190
290
  }
191
291
  logger.newline();
192
292
  // Step 7: Create directory
193
293
  await mkdir(resolvedOutputDir, { recursive: true });
194
- // Step 8: Save credentials to .env (if we have connection config)
195
- if (connectionConfig) {
294
+ // Step 8: Save credentials to .env (if we have connection config).
295
+ // Embedded chDB has no credentials — the storage path lives in client.ts —
296
+ // so the chdb scaffold writes no .env at all.
297
+ if (database === 'chdb') {
298
+ // nothing to persist
299
+ }
300
+ else if (connectionConfig) {
196
301
  const envPath = path.join(process.cwd(), '.env');
197
302
  const envExists = await hasEnvFile();
198
303
  if (envExists) {
@@ -226,8 +331,8 @@ export async function initCommand(options = {}) {
226
331
  if (hasValidConnection) {
227
332
  const typeSpinner = ora('Generating TypeScript types...').start();
228
333
  try {
229
- const generator = getTypeGenerator('clickhouse');
230
- await generator({ outputPath: schemaPath });
334
+ const generator = getTypeGenerator(database);
335
+ await generator({ outputPath: schemaPath, chdbPath });
231
336
  typeSpinner.succeed(`Generated TypeScript types (${path.relative(process.cwd(), schemaPath)})`);
232
337
  }
233
338
  catch (error) {
@@ -238,8 +343,11 @@ export async function initCommand(options = {}) {
238
343
  }
239
344
  else {
240
345
  // Create placeholder schema file
346
+ const regenerateHint = database === 'chdb'
347
+ ? "// Run 'npx hypequery generate --database chdb --chdb-path <dir>' after creating persistent tables"
348
+ : "// Run 'npx hypequery generate' after configuring your database connection";
241
349
  await writeFile(schemaPath, `// Generated by hypequery
242
- // Run 'npx hypequery generate' after configuring your database connection
350
+ ${regenerateHint}
243
351
 
244
352
  export interface IntrospectedSchema {
245
353
  // Your table types will appear here after generation
@@ -249,8 +357,8 @@ export interface IntrospectedSchema {
249
357
  }
250
358
  // Step 10: Create client.ts
251
359
  const clientPath = path.join(resolvedOutputDir, 'client.ts');
252
- await writeFile(clientPath, generateClientTemplate());
253
- logger.success(`Created ClickHouse client (${path.relative(process.cwd(), clientPath)})`);
360
+ await writeFile(clientPath, generateClientTemplate({ database, chdbPath }));
361
+ logger.success(`Created ${database === 'chdb' ? 'embedded chDB' : 'ClickHouse'} client (${path.relative(process.cwd(), clientPath)})`);
254
362
  // Step 11: Create API entrypoint
255
363
  let apiPath;
256
364
  let generatedAnyDatasets = false;
@@ -264,6 +372,9 @@ export interface IntrospectedSchema {
264
372
  outputPath: datasetsPath,
265
373
  includeTables: options.allTables ? undefined : datasetTables,
266
374
  excludeTables: excludedDatasetTables,
375
+ ...(database === 'chdb'
376
+ ? { client: getChdbTypeGenerationClient(chdbPath) }
377
+ : {}),
267
378
  });
268
379
  generatedAnyDatasets = true;
269
380
  generatedSelectedDataset = selectedTable !== null && (options.allTables === true ||
@@ -295,20 +406,24 @@ export interface IntrospectedSchema {
295
406
  // Step 12: Update .gitignore
296
407
  const gitignorePath = path.join(process.cwd(), '.gitignore');
297
408
  const gitignoreExists = await hasGitignore();
409
+ const chdbGitignoreEntry = database === 'chdb'
410
+ ? getChdbGitignoreEntry(chdbPath)
411
+ : undefined;
412
+ const gitignoreEntries = chdbGitignoreEntry ? [chdbGitignoreEntry] : [];
298
413
  if (gitignoreExists) {
299
414
  const existingGitignore = await readFile(gitignorePath, 'utf-8');
300
- const newGitignore = appendToGitignore(existingGitignore);
415
+ const newGitignore = appendToGitignore(existingGitignore, gitignoreEntries);
301
416
  if (newGitignore !== existingGitignore) {
302
417
  await writeFile(gitignorePath, newGitignore);
303
418
  logger.success('Updated .gitignore');
304
419
  }
305
420
  }
306
421
  else {
307
- await writeFile(gitignorePath, appendToGitignore(''));
422
+ await writeFile(gitignorePath, appendToGitignore('', gitignoreEntries));
308
423
  logger.success('Created .gitignore');
309
424
  }
310
425
  // Step 13: Ensure required hypequery packages are installed
311
- await installScaffoldDependencies(style);
426
+ await installScaffoldDependencies(style, database);
312
427
  // Step 14: Success message
313
428
  logger.newline();
314
429
  logger.header('Setup complete!');
@@ -342,6 +457,24 @@ export interface IntrospectedSchema {
342
457
  logger.newline();
343
458
  }
344
459
  }
460
+ else if (database === 'chdb') {
461
+ logger.info('Next steps:');
462
+ logger.newline();
463
+ // chdb is normally installed by the scaffold itself — only tell the user
464
+ // to install when that is actually what failed, not when an installed
465
+ // engine could not run (unsupported platform, locked session directory).
466
+ const firstStep = options.skipConnection
467
+ ? '1. Verify the embedded engine and create your tables'
468
+ : chdbFailureReason === 'engine-error'
469
+ ? '1. Resolve the engine error shown above'
470
+ : '1. Install the embedded engine: npm install chdb';
471
+ logger.indent(firstStep);
472
+ logger.indent(chdbPath
473
+ ? `2. Run: npx hypequery generate --database chdb --chdb-path ${chdbPath}`
474
+ : '2. Re-run init with --chdb-path <dir> if later generate commands must see your tables');
475
+ logger.indent('3. Run: npx hypequery dev (to start dev server)');
476
+ logger.newline();
477
+ }
345
478
  else {
346
479
  logger.info('Next steps:');
347
480
  logger.newline();
@@ -350,6 +483,12 @@ export interface IntrospectedSchema {
350
483
  logger.indent('3. Run: npx hypequery dev (to start dev server)');
351
484
  logger.newline();
352
485
  }
486
+ if (database === 'chdb' && hasValidConnection) {
487
+ logger.indent(chdbPath
488
+ ? `hypequery generate --database chdb --chdb-path ${chdbPath} Refresh types after creating tables`
489
+ : 'In-memory chDB is process-local; use --chdb-path <dir> for later type generation');
490
+ logger.newline();
491
+ }
353
492
  logger.info('Docs: https://hypequery.com/docs');
354
493
  logger.newline();
355
494
  }
@@ -0,0 +1,13 @@
1
+ export interface ChdbGeneratorOptions {
2
+ outputPath: string;
3
+ includeTables?: string[];
4
+ excludeTables?: string[];
5
+ chdbPath?: string;
6
+ }
7
+ /**
8
+ * Same generator as ClickHouse — the introspection SQL (SHOW TABLES,
9
+ * DESCRIBE TABLE) is identical on the embedded engine — but the client seam
10
+ * is backed by a chDB session instead of an HTTP connection.
11
+ */
12
+ export declare function generateChdbTypes(options: ChdbGeneratorOptions): Promise<void>;
13
+ //# sourceMappingURL=chdb.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,17 @@
1
+ import { generateTypes } from '@hypequery/clickhouse/cli';
2
+ import { getChdbTypeGenerationClient } from '../utils/chdb-client.js';
3
+ /**
4
+ * Same generator as ClickHouse — the introspection SQL (SHOW TABLES,
5
+ * DESCRIBE TABLE) is identical on the embedded engine — but the client seam
6
+ * is backed by a chDB session instead of an HTTP connection.
7
+ */
8
+ export async function generateChdbTypes(options) {
9
+ const generatorOptions = {
10
+ client: getChdbTypeGenerationClient(options.chdbPath),
11
+ generatedBy: 'hypequery',
12
+ includeUsageExample: false,
13
+ ...(options.includeTables ? { includeTables: options.includeTables } : {}),
14
+ ...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
15
+ };
16
+ await generateTypes(options.outputPath, generatorOptions);
17
+ }
@@ -4,10 +4,12 @@
4
4
  * Generates dataset DSL code from ClickHouse schema introspection.
5
5
  * This reduces quickstart friction by auto-scaffolding dataset definitions.
6
6
  */
7
+ import type { TypeGenerationClickHouseClient } from '@hypequery/clickhouse/cli';
7
8
  export interface DatasetGeneratorOptions {
8
9
  outputPath: string;
9
10
  includeTables?: string[];
10
11
  excludeTables?: string[];
12
+ client?: TypeGenerationClickHouseClient;
11
13
  }
12
14
  /**
13
15
  * Generate dataset definitions from ClickHouse schema
@@ -1 +1 @@
1
- {"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;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,2BAA2B,CAAC;AAGhF,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"}
@@ -199,7 +199,7 @@ ${configLines.join('\n')}
199
199
  * Generate dataset definitions from ClickHouse schema
200
200
  */
201
201
  export async function generateDatasets(options) {
202
- const client = getClickHouseClient();
202
+ const client = options.client ?? getClickHouseClient();
203
203
  // Get all tables
204
204
  const tablesQuery = await client.query({
205
205
  query: 'SHOW TABLES',
@@ -1,6 +1,7 @@
1
1
  import type { DatabaseType } from '../utils/detect-database.js';
2
2
  import { type ClickHouseGeneratorOptions } from './clickhouse.js';
3
- export type TypeGeneratorOptions = ClickHouseGeneratorOptions;
3
+ import { type ChdbGeneratorOptions } from './chdb.js';
4
+ export type TypeGeneratorOptions = ClickHouseGeneratorOptions & ChdbGeneratorOptions;
4
5
  type GeneratorFn = (options: TypeGeneratorOptions) => Promise<void>;
5
6
  export declare function getTypeGenerator(dbType: DatabaseType): GeneratorFn;
6
7
  export {};
@@ -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,EAA2B,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAE3F,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,CAAC;AAE9D,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAMpE,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,EAA2B,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,oBAAoB,CAAC;AAErF,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAOpE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAYlE"}
@@ -1,6 +1,8 @@
1
1
  import { generateClickHouseTypes } from './clickhouse.js';
2
+ import { generateChdbTypes } from './chdb.js';
2
3
  const generators = {
3
4
  clickhouse: generateClickHouseTypes,
5
+ chdb: generateChdbTypes,
4
6
  };
5
7
  export function getTypeGenerator(dbType) {
6
8
  const generator = generators[dbType];