@hypequery/cli 1.12.1 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -42,6 +42,15 @@ Scaffolds the standard hypequery setup.
42
42
  npx hypequery init
43
43
  ```
44
44
 
45
+ Interactive setup asks which database driver to use, whether to include
46
+ request-context authentication scaffolding, where to write the generated
47
+ files, and which API style to create. Selecting chDB replaces the remote
48
+ credential questions with an embedded-storage choice.
49
+
50
+ Run `init` from the project directory that contains `package.json`. If no
51
+ package manifest is found, interactive setup asks for confirmation before
52
+ writing files and dependency installation must be completed manually.
53
+
45
54
  It will:
46
55
 
47
56
  - connect to ClickHouse or start an embedded chDB session
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;AAyBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAsND,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;AA0BpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA4ND,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -9,6 +9,7 @@ import { generateManifestCommand } from './commands/generate-manifest.js';
9
9
  import { buildDeploymentCommand, prepareDeploymentReleaseCommand, validateDeploymentCommand, } from './commands/deployment.js';
10
10
  import { deployCommand, submitDeploymentCommand, } from './commands/deploy.js';
11
11
  import { loginCommand, logoutCommand, } from './commands/login.js';
12
+ import { isPromptCancelled } from './utils/prompts.js';
12
13
  const program = new Command();
13
14
  function getCliVersion() {
14
15
  try {
@@ -48,6 +49,12 @@ function runCommand(action) {
48
49
  await action(...args);
49
50
  }
50
51
  catch (error) {
52
+ // Ctrl+C at a prompt is a deliberate abort, not a failure: report it as
53
+ // the interrupt it is instead of dumping an error and exiting 1.
54
+ if (isPromptCancelled(error)) {
55
+ console.log('\nCancelled.');
56
+ process.exit(130);
57
+ }
51
58
  console.error(error instanceof Error ? error.message : error);
52
59
  process.exit(1);
53
60
  }
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,EAcL,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;AAqKD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAya1D"}
@@ -2,7 +2,7 @@ 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, promptChdbStorage, promptOutputDirectory, promptInitStyle, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
5
+ import { promptClickHouseConnection, promptInitDatabase, promptChdbStorage, promptOutputDirectory, promptInitStyle, promptInitAuthMode, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmWithoutPackageJson, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
6
6
  import { validateConnection, getTableCount, getTables, } from '../utils/detect-database.js';
7
7
  import { ChdbNotInstalledError, ensureChdbInstalled, getChdbTypeGenerationClient, } from '../utils/chdb-client.js';
8
8
  import { hasEnvFile, hasGitignore } from '../utils/find-files.js';
@@ -78,6 +78,15 @@ async function resolveConnectionConfig(options) {
78
78
  }
79
79
  return promptClickHouseConnection();
80
80
  }
81
+ async function hasProjectPackageJson() {
82
+ try {
83
+ await readFile(path.join(process.cwd(), 'package.json'), 'utf8');
84
+ return true;
85
+ }
86
+ catch {
87
+ return false;
88
+ }
89
+ }
81
90
  async function testChdbConnection(chdbPath) {
82
91
  const spinner = ora('Starting embedded chDB...').start();
83
92
  try {
@@ -134,9 +143,18 @@ async function testConnection(connectionConfig) {
134
143
  }
135
144
  export async function initCommand(options = {}) {
136
145
  const noInteractive = options.noInteractive === true || options.interactive === false;
137
- const database = normalizeInitDatabase(options.database);
138
146
  logger.newline();
139
147
  logger.header('Welcome to hypequery!');
148
+ if (!noInteractive && !(await hasProjectPackageJson())) {
149
+ logger.warn(`package.json not found in ${process.cwd()}`);
150
+ const shouldContinue = await confirmWithoutPackageJson(process.cwd());
151
+ if (!shouldContinue) {
152
+ logger.info('Setup cancelled. Run init from your project directory.');
153
+ return;
154
+ }
155
+ logger.newline();
156
+ }
157
+ const database = normalizeInitDatabase(options.database ?? (noInteractive ? undefined : await promptInitDatabase()));
140
158
  logger.info(database === 'chdb'
141
159
  ? "Let's set up your analytics layer on embedded ClickHouse (chDB)."
142
160
  : "Let's set up your analytics layer.");
@@ -173,7 +191,7 @@ export async function initCommand(options = {}) {
173
191
  }
174
192
  const retry = await promptRetry('Try again?');
175
193
  if (retry) {
176
- return initCommand(options);
194
+ return initCommand({ ...options, database });
177
195
  }
178
196
  const continueWithout = await promptContinueWithoutDb();
179
197
  if (!continueWithout) {
@@ -198,10 +216,13 @@ export async function initCommand(options = {}) {
198
216
  }
199
217
  const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
200
218
  let style = normalizeInitStyle(options.style);
201
- const auth = normalizeAuthMode(options.auth);
202
219
  if (!options.style && !noInteractive) {
203
220
  style = await promptInitStyle();
204
221
  }
222
+ let auth = normalizeAuthMode(options.auth);
223
+ if (!options.auth && !noInteractive) {
224
+ auth = normalizeAuthMode(await promptInitAuthMode());
225
+ }
205
226
  // Step 5: Check for existing files
206
227
  const filesToCreate = [
207
228
  path.join(resolvedOutputDir, 'client.ts'),
@@ -1,4 +1,4 @@
1
- import { generateTypes } from '@hypequery/clickhouse/cli';
1
+ import { generateTypes } from '../typegen/index.js';
2
2
  import { getChdbTypeGenerationClient } from '../utils/chdb-client.js';
3
3
  /**
4
4
  * Same generator as ClickHouse — the introspection SQL (SHOW TABLES,
@@ -1,4 +1,4 @@
1
- import { clickhouseToTsType } from '@hypequery/clickhouse/cli';
1
+ import { clickhouseToTsType } from '../typegen/index.js';
2
2
  export interface ClickHouseGeneratorOptions {
3
3
  outputPath: string;
4
4
  includeTables?: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,2BAA2B,CAAC;AAGnC,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,iBAUhF"}
@@ -1,4 +1,4 @@
1
- import { clickhouseToTsType, generateTypes, } from '@hypequery/clickhouse/cli';
1
+ import { clickhouseToTsType, generateTypes, } from '../typegen/index.js';
2
2
  import { getClickHouseClient } from '../utils/clickhouse-client.js';
3
3
  export { clickhouseToTsType };
4
4
  export async function generateClickHouseTypes(options) {
@@ -4,7 +4,7 @@
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
+ import type { TypeGenerationClickHouseClient } from '../typegen/index.js';
8
8
  export interface DatasetGeneratorOptions {
9
9
  outputPath: string;
10
10
  includeTables?: string[];
@@ -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,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"}
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,7 +1,7 @@
1
- // Loaded on demand. Both generators pull in `@hypequery/clickhouse/cli`, and importing
2
- // that eagerly puts it on the module graph of every command a resolution failure there
3
- // took down even `hypequery --help`. Deferring it keeps the blast radius on the commands
4
- // that actually generate types.
1
+ // Loaded on demand. The generator itself is local now, but it still reaches for a driver —
2
+ // `@clickhouse/client` for one, the native `chdb` package for the otherand importing
3
+ // those eagerly puts them on the module graph of every command. Deferring keeps the blast
4
+ // radius on the commands that actually generate types, including `hypequery --help`.
5
5
  const generators = {
6
6
  clickhouse: async (options) => {
7
7
  const { generateClickHouseTypes } = await import('./clickhouse.js');
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ClickHouse schema introspection to TypeScript types.
3
+ *
4
+ * Ported from `@hypequery/clickhouse/src/cli` so the CLI no longer has to
5
+ * depend on the whole query-builder package to generate types. The copy over
6
+ * there still backs the legacy `hypequery-generate-types` bin and the
7
+ * `@hypequery/clickhouse/cli` subpath; this one is the living version.
8
+ *
9
+ * The one deliberate difference: there is no `ClickHouseConnection` fallback.
10
+ * A client is always supplied by the caller, which is what let the query
11
+ * builder drop off the CLI's module graph in the first place.
12
+ */
13
+ /**
14
+ * Minimal ClickHouse client surface the type generator needs. Both the HTTP
15
+ * client and the embedded chDB session satisfy it.
16
+ */
17
+ export interface TypeGenerationClickHouseClient {
18
+ query(options: {
19
+ query: string;
20
+ format: 'JSONEachRow';
21
+ }): Promise<{
22
+ json(): Promise<Array<Record<string, string>>>;
23
+ }>;
24
+ }
25
+ export interface GenerateTypesOptions {
26
+ /** Only introspect these tables. Defaults to every table in the database. */
27
+ includeTables?: string[];
28
+ /** Skip these tables. Applied after `includeTables`. */
29
+ excludeTables?: string[];
30
+ /** Client used for introspection. */
31
+ client: TypeGenerationClickHouseClient;
32
+ /** Package name written into the generated file header. */
33
+ generatedBy?: string;
34
+ /** Append the historical usage-example comment block. */
35
+ includeUsageExample?: boolean;
36
+ }
37
+ /**
38
+ * Generates the TypeScript type definition source from a ClickHouse client.
39
+ */
40
+ export declare function generateTypeDefinitions(client: TypeGenerationClickHouseClient, options?: Omit<GenerateTypesOptions, 'client'>): Promise<string>;
41
+ /**
42
+ * Generates TypeScript type definitions from the ClickHouse database schema and
43
+ * writes them to `outputPath`, creating the containing directory if needed.
44
+ */
45
+ export declare function generateTypes(outputPath: string, options: GenerateTypesOptions): Promise<void>;
46
+ //# sourceMappingURL=generate-types.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * ClickHouse schema introspection to TypeScript types.
3
+ *
4
+ * Ported from `@hypequery/clickhouse/src/cli` so the CLI no longer has to
5
+ * depend on the whole query-builder package to generate types. The copy over
6
+ * there still backs the legacy `hypequery-generate-types` bin and the
7
+ * `@hypequery/clickhouse/cli` subpath; this one is the living version.
8
+ *
9
+ * The one deliberate difference: there is no `ClickHouseConnection` fallback.
10
+ * A client is always supplied by the caller, which is what let the query
11
+ * builder drop off the CLI's module graph in the first place.
12
+ */
13
+ import fs from 'node:fs/promises';
14
+ import path from 'node:path';
15
+ import { clickhouseToTsType } from './type-parsing.js';
16
+ /**
17
+ * Capitalize the first letter of a string.
18
+ */
19
+ function capitalizeFirstLetter(str) {
20
+ return str.charAt(0).toUpperCase() + str.slice(1);
21
+ }
22
+ async function describeTable(client, tableName) {
23
+ const columnsQuery = await client.query({
24
+ query: `DESCRIBE TABLE ${tableName}`,
25
+ format: 'JSONEachRow',
26
+ });
27
+ return columnsQuery.json();
28
+ }
29
+ /**
30
+ * Generates the TypeScript type definition source from a ClickHouse client.
31
+ */
32
+ export async function generateTypeDefinitions(client, options = {}) {
33
+ const { includeTables = [], excludeTables = [], generatedBy = 'hypequery', includeUsageExample = true, } = options;
34
+ // Get all tables
35
+ const tablesQuery = await client.query({
36
+ query: 'SHOW TABLES',
37
+ format: 'JSONEachRow',
38
+ });
39
+ let tables = await tablesQuery.json();
40
+ // Filter tables if includeTables or excludeTables are specified
41
+ if (includeTables.length > 0) {
42
+ tables = tables.filter((table) => includeTables.includes(table.name));
43
+ }
44
+ if (excludeTables.length > 0) {
45
+ tables = tables.filter((table) => !excludeTables.includes(table.name));
46
+ }
47
+ // If no tables remain after filtering, log a warning
48
+ if (tables.length === 0) {
49
+ console.warn('Warning: No tables match the filter criteria. Check your include/exclude options.');
50
+ }
51
+ let typeDefinitions = `// Generated by ${generatedBy}
52
+ // This file defines TypeScript types based on your ClickHouse database schema
53
+
54
+ /**
55
+ * Schema interface for use with createQueryBuilder<IntrospectedSchema>()
56
+ * The string literals represent ClickHouse data types for each column
57
+ */
58
+ export interface IntrospectedSchema {`;
59
+ // Get columns for each table
60
+ for (const table of tables) {
61
+ const columns = await describeTable(client, table.name);
62
+ typeDefinitions += `\n ${table.name}: {`;
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}';`;
66
+ }
67
+ typeDefinitions += '\n };';
68
+ }
69
+ typeDefinitions += '\n}\n';
70
+ // Also generate a type-safe record type for each table
71
+ typeDefinitions += `\n// Type-safe record types for each table\n`;
72
+ for (const table of tables) {
73
+ const columns = await describeTable(client, table.name);
74
+ typeDefinitions += `export interface ${capitalizeFirstLetter(table.name)}Record {`;
75
+ for (const column of columns) {
76
+ const tsType = clickhouseToTsType(column.type).replace(/'/g, '');
77
+ typeDefinitions += `\n '${column.name}': ${tsType};`;
78
+ }
79
+ typeDefinitions += '\n}\n\n';
80
+ }
81
+ if (includeUsageExample) {
82
+ typeDefinitions += `
83
+ /**
84
+ * Usage example:
85
+ *
86
+ * import { createQueryBuilder } from '@hypequery/clickhouse';
87
+ * import { IntrospectedSchema } from './path-to-this-file';
88
+ *
89
+ * // Create a type-safe query builder
90
+ * const db = createQueryBuilder<IntrospectedSchema>();
91
+ *
92
+ * // Now you have full type safety and autocomplete
93
+ * const results = await db
94
+ * .table('${tables.length > 0 ? tables[0].name : 'table_name'}')
95
+ * .select(['column1', 'column2'])
96
+ * .where('column1', 'eq', 'value')
97
+ * .execute();
98
+ */
99
+ `;
100
+ }
101
+ return typeDefinitions;
102
+ }
103
+ /**
104
+ * Generates TypeScript type definitions from the ClickHouse database schema and
105
+ * writes them to `outputPath`, creating the containing directory if needed.
106
+ */
107
+ export async function generateTypes(outputPath, options) {
108
+ const { client, ...definitionOptions } = options;
109
+ const typeDefinitions = await generateTypeDefinitions(client, definitionOptions);
110
+ // Ensure the output directory exists
111
+ const outputDir = path.dirname(path.resolve(outputPath));
112
+ await fs.mkdir(outputDir, { recursive: true });
113
+ // Write the file
114
+ await fs.writeFile(path.resolve(outputPath), typeDefinitions);
115
+ }
@@ -0,0 +1,3 @@
1
+ export { generateTypeDefinitions, generateTypes, type GenerateTypesOptions, type TypeGenerationClickHouseClient, } from './generate-types.js';
2
+ export { clickhouseToTsType } from './type-parsing.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/typegen/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,8BAA8B,GACpC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { generateTypeDefinitions, generateTypes, } from './generate-types.js';
2
+ export { clickhouseToTsType } from './type-parsing.js';
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Converts a ClickHouse type string, as returned by `DESCRIBE TABLE`, into the
3
+ * equivalent TypeScript type string.
4
+ */
5
+ export declare const clickhouseToTsType: (type: string) => string;
6
+ //# sourceMappingURL=type-parsing.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,139 @@
1
+ function splitTopLevelArgs(value) {
2
+ const parts = [];
3
+ let current = '';
4
+ let depth = 0;
5
+ for (const char of value) {
6
+ if (char === '(') {
7
+ depth += 1;
8
+ current += char;
9
+ continue;
10
+ }
11
+ if (char === ')') {
12
+ depth -= 1;
13
+ current += char;
14
+ continue;
15
+ }
16
+ if (char === ',' && depth === 0) {
17
+ parts.push(current.trim());
18
+ current = '';
19
+ continue;
20
+ }
21
+ current += char;
22
+ }
23
+ if (current.trim()) {
24
+ parts.push(current.trim());
25
+ }
26
+ return parts;
27
+ }
28
+ function unwrapType(type, wrapperName) {
29
+ // Trim whitespace and handle case-insensitive matching
30
+ const trimmedType = type.trim();
31
+ const lowerType = trimmedType.toLowerCase();
32
+ const lowerWrapper = wrapperName.toLowerCase();
33
+ const prefix = `${lowerWrapper}(`;
34
+ if (lowerType.startsWith(prefix) && trimmedType.endsWith(')')) {
35
+ // Extract inner type, preserving original case
36
+ const innerStart = trimmedType.indexOf('(') + 1;
37
+ const innerEnd = trimmedType.lastIndexOf(')');
38
+ return trimmedType.slice(innerStart, innerEnd).trim();
39
+ }
40
+ return null;
41
+ }
42
+ function getPrimitiveTsType(type) {
43
+ const lowerType = type.toLowerCase();
44
+ switch (lowerType) {
45
+ case 'string':
46
+ case 'uuid':
47
+ case 'ipv4':
48
+ case 'ipv6':
49
+ return 'string';
50
+ case 'int8':
51
+ case 'int16':
52
+ case 'int32':
53
+ case 'uint8':
54
+ case 'uint16':
55
+ case 'uint32':
56
+ return 'number';
57
+ case 'int64':
58
+ case 'uint64':
59
+ case 'uint128':
60
+ case 'uint256':
61
+ case 'int128':
62
+ case 'int256':
63
+ return 'string';
64
+ case 'float32':
65
+ case 'float64':
66
+ case 'decimal':
67
+ return 'number';
68
+ case 'datetime':
69
+ case 'datetime64':
70
+ case 'date':
71
+ case 'date32':
72
+ return 'string';
73
+ case 'bool':
74
+ case 'boolean':
75
+ return 'boolean';
76
+ case 'json':
77
+ return 'unknown';
78
+ default:
79
+ if (type.startsWith('FixedString('))
80
+ return 'string';
81
+ if (type.startsWith('Decimal('))
82
+ return 'number';
83
+ if (type.startsWith('Decimal32('))
84
+ return 'number';
85
+ if (type.startsWith('Decimal64('))
86
+ return 'number';
87
+ if (type.startsWith('Decimal128('))
88
+ return 'number';
89
+ if (type.startsWith('Decimal256('))
90
+ return 'number';
91
+ if (type.startsWith('DateTime64('))
92
+ return 'string';
93
+ if (type.startsWith('DateTime('))
94
+ return 'string';
95
+ if (type.startsWith('Enum8('))
96
+ return 'string';
97
+ if (type.startsWith('Enum16('))
98
+ return 'string';
99
+ return null;
100
+ }
101
+ }
102
+ /**
103
+ * Converts a ClickHouse type string, as returned by `DESCRIBE TABLE`, into the
104
+ * equivalent TypeScript type string.
105
+ */
106
+ export const clickhouseToTsType = (type) => {
107
+ const wrappedArrayType = unwrapType(type, 'Array');
108
+ if (wrappedArrayType) {
109
+ return `Array<${clickhouseToTsType(wrappedArrayType)}>`;
110
+ }
111
+ const wrappedNullableType = unwrapType(type, 'Nullable');
112
+ if (wrappedNullableType) {
113
+ return `${clickhouseToTsType(wrappedNullableType)} | null`;
114
+ }
115
+ const wrappedLowCardinalityType = unwrapType(type, 'LowCardinality');
116
+ if (wrappedLowCardinalityType) {
117
+ return clickhouseToTsType(wrappedLowCardinalityType);
118
+ }
119
+ const wrappedTupleType = unwrapType(type, 'Tuple');
120
+ if (wrappedTupleType) {
121
+ const tupleParts = splitTopLevelArgs(wrappedTupleType);
122
+ return `[${tupleParts.map((part) => clickhouseToTsType(part)).join(', ')}]`;
123
+ }
124
+ const wrappedMapType = unwrapType(type, 'Map');
125
+ if (wrappedMapType) {
126
+ const mapParts = splitTopLevelArgs(wrappedMapType);
127
+ if (mapParts.length === 2) {
128
+ const [, valueType] = mapParts;
129
+ // JSON object keys are strings even when ClickHouse map keys are numeric.
130
+ return `Record<string, ${clickhouseToTsType(valueType)}>`;
131
+ }
132
+ return 'Record<string, unknown>';
133
+ }
134
+ const primitiveType = getPrimitiveTsType(type);
135
+ if (primitiveType)
136
+ return primitiveType;
137
+ // Unsupported or more complex ClickHouse types currently preserve the historical fallback.
138
+ return 'string';
139
+ };
@@ -1,4 +1,4 @@
1
- import type { TypeGenerationClickHouseClient } from '@hypequery/clickhouse/cli';
1
+ import type { TypeGenerationClickHouseClient } from '../typegen/index.js';
2
2
  interface ChdbSession {
3
3
  queryAsync(sql: string, options?: {
4
4
  format?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"chdb-client.d.ts","sourceRoot":"","sources":["../../src/utils/chdb-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAYhF,UAAU,WAAW;IACnB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,IAAI,MAAM,CAAA;KAAE,CAAC,CAAC;IACpF,KAAK,IAAI,IAAI,CAAC;CACf;AAKD,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,wBAAwB;;CAQtC;AA6CD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAY1E;AAYD;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,8BAA8B,CAQ3F;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEzD;AAED,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOpE;AAED,wBAAsB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOtE;AAED,wBAAsB,0BAA0B,kBAM/C"}
1
+ {"version":3,"file":"chdb-client.d.ts","sourceRoot":"","sources":["../../src/utils/chdb-client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAY1E,UAAU,WAAW;IACnB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,IAAI,IAAI,MAAM,CAAA;KAAE,CAAC,CAAC;IACpF,KAAK,IAAI,IAAI,CAAC;CACf;AAKD,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,wBAAwB;;CAQtC;AA6CD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAY1E;AAYD;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,8BAA8B,CAQ3F;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAEzD;AAED,wBAAsB,YAAY,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOpE;AAED,wBAAsB,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAOtE;AAED,wBAAsB,0BAA0B,kBAM/C"}
@@ -1,5 +1,6 @@
1
1
  type ScaffoldStyle = 'queries' | 'datasets';
2
2
  type ScaffoldDatabase = 'clickhouse' | 'chdb';
3
+ export declare function isCanaryVersion(version: string | undefined): boolean;
3
4
  export declare function resolveScaffoldPackages(cliVersion: string | undefined, style?: ScaffoldStyle, database?: ScaffoldDatabase): string[];
4
5
  export declare function installScaffoldDependencies(style?: ScaffoldStyle, database?: ScaffoldDatabase): Promise<void>;
5
6
  export declare const installServeDependencies: typeof installScaffoldDependencies;
@@ -1 +1 @@
1
- {"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AA0F9C,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AAsBD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAkD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
1
+ {"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AAwE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAqCD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AAsBD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAyD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
@@ -68,26 +68,44 @@ function detectPackageManager(pkgJson) {
68
68
  return 'bun';
69
69
  return 'npm';
70
70
  }
71
- function getInstallArgs(manager, packages) {
71
+ export function isCanaryVersion(version) {
72
+ return version?.includes('canary') === true;
73
+ }
74
+ /**
75
+ * Canary builds pin every hypequery package to a `0.0.0-canary-*` version, which
76
+ * sorts below every published semver range. Any third-party peer range on a
77
+ * hypequery package is therefore unsatisfiable — `chdb` declares
78
+ * `peerOptional @hypequery/clickhouse@">=2.1.2"` — and npm aborts the whole
79
+ * install with ERESOLVE, leaving the scaffold without its dependencies.
80
+ *
81
+ * Only npm needs this: pnpm (strict-peer-dependencies defaults to false), yarn
82
+ * and bun all install through peer conflicts rather than failing. Stable
83
+ * installs keep strict peer checking on every manager.
84
+ */
85
+ function getPeerRelaxFlags(manager, canary) {
86
+ return canary && manager === 'npm' ? ['--legacy-peer-deps'] : [];
87
+ }
88
+ function getInstallArgs(manager, packages, canary = false) {
89
+ const relaxFlags = getPeerRelaxFlags(manager, canary);
72
90
  switch (manager) {
73
91
  case 'pnpm':
74
- return ['add', ...packages];
92
+ return ['add', ...relaxFlags, ...packages];
75
93
  case 'yarn':
76
- return ['add', ...packages];
94
+ return ['add', ...relaxFlags, ...packages];
77
95
  case 'bun':
78
- return ['add', ...packages];
96
+ return ['add', ...relaxFlags, ...packages];
79
97
  case 'npm':
80
98
  default:
81
- return ['install', ...packages];
99
+ return ['install', ...relaxFlags, ...packages];
82
100
  }
83
101
  }
84
- function formatManualCommand(manager, packages) {
85
- return `${MANUAL_COMMANDS[manager]} ${packages.join(' ')}`;
102
+ function formatManualCommand(manager, packages, canary = false) {
103
+ return [MANUAL_COMMANDS[manager], ...getPeerRelaxFlags(manager, canary), ...packages].join(' ');
86
104
  }
87
105
  export function resolveScaffoldPackages(cliVersion, style = 'queries', database = 'clickhouse') {
88
106
  const includeDatasets = style === 'datasets';
89
107
  const includeChdb = database === 'chdb';
90
- if (cliVersion?.includes('canary')) {
108
+ if (isCanaryVersion(cliVersion)) {
91
109
  return [
92
110
  `@hypequery/clickhouse@${cliVersion}`,
93
111
  `@hypequery/serve@${cliVersion}`,
@@ -126,8 +144,14 @@ export async function installScaffoldDependencies(style = 'queries', database =
126
144
  readCliPackageJson(),
127
145
  ]);
128
146
  if (!pkgJson) {
129
- const extra = style === 'datasets' ? ', @hypequery/datasets' : '';
130
- logger.warn(`package.json not found. Install @hypequery/clickhouse, @hypequery/serve${extra}, and zod manually.`);
147
+ const packages = [
148
+ '@hypequery/clickhouse',
149
+ '@hypequery/serve',
150
+ ...(style === 'datasets' ? ['@hypequery/datasets'] : []),
151
+ 'zod',
152
+ ...(database === 'chdb' ? ['chdb'] : []),
153
+ ];
154
+ logger.warn(`package.json not found. Install ${packages.join(', ')} manually.`);
131
155
  return;
132
156
  }
133
157
  const requestedPackages = resolveScaffoldPackages(cliPkgJson?.version, style, database);
@@ -135,9 +159,10 @@ export async function installScaffoldDependencies(style = 'queries', database =
135
159
  if (missing.length === 0) {
136
160
  return;
137
161
  }
162
+ const canary = isCanaryVersion(cliPkgJson?.version);
138
163
  const manager = detectPackageManager(pkgJson);
139
164
  const command = manager === 'npm' ? 'npm' : manager;
140
- const args = getInstallArgs(manager, missing);
165
+ const args = getInstallArgs(manager, missing, canary);
141
166
  logger.info(`Installing ${missing.join(', ')} with ${manager}...`);
142
167
  try {
143
168
  await new Promise((resolve, reject) => {
@@ -159,7 +184,7 @@ export async function installScaffoldDependencies(style = 'queries', database =
159
184
  }
160
185
  catch (error) {
161
186
  logger.warn('Failed to install hypequery packages automatically.');
162
- logger.info(`Run manually: ${formatManualCommand(manager, missing)}`);
187
+ logger.info(`Run manually: ${formatManualCommand(manager, missing, canary)}`);
163
188
  if (error instanceof Error && error.message) {
164
189
  logger.info(error.message);
165
190
  }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * Raised when the user aborts a prompt with Ctrl+C or Esc.
3
+ */
4
+ export declare class PromptCancelledError extends Error {
5
+ constructor();
6
+ }
7
+ export declare function isPromptCancelled(error: unknown): error is PromptCancelledError;
1
8
  /**
2
9
  * Prompt for ClickHouse connection details
3
10
  */
@@ -7,6 +14,11 @@ export declare function promptClickHouseConnection(): Promise<{
7
14
  username: string;
8
15
  password: string;
9
16
  } | null>;
17
+ export type InitDatabase = 'clickhouse' | 'chdb';
18
+ /**
19
+ * Choose between a remote ClickHouse server and embedded chDB.
20
+ */
21
+ export declare function promptInitDatabase(): Promise<InitDatabase>;
10
22
  /**
11
23
  * Prompt for embedded chDB storage: ephemeral in-memory session, or an
12
24
  * on-disk directory that persists between runs. Returns the session path,
@@ -19,6 +31,15 @@ export declare function promptChdbStorage(): Promise<string | undefined>;
19
31
  export declare function promptOutputDirectory(): Promise<string>;
20
32
  export type InitStyle = 'queries' | 'datasets';
21
33
  export declare function promptInitStyle(): Promise<InitStyle>;
34
+ export type InitAuthMode = 'none' | 'context';
35
+ /**
36
+ * Choose whether generated routes include request-context auth scaffolding.
37
+ */
38
+ export declare function promptInitAuthMode(): Promise<InitAuthMode>;
39
+ /**
40
+ * Confirm scaffolding when init is run outside a Node project.
41
+ */
42
+ export declare function confirmWithoutPackageJson(directory: string): Promise<boolean>;
22
43
  /**
23
44
  * Prompt for example query generation
24
45
  */
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAQA;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAAC,CAkCR;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4BrE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B7D;AAED,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAE/C,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAa1D;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS9D;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BnF;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,GAAE,MAAM,EAAO,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnE;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAShE"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/utils/prompts.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;;CAK9C;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,oBAAoB,CAE/E;AA8BD;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,IAAI,CAAC,CAyCR;AAED,MAAM,MAAM,YAAY,GAAG,YAAY,GAAG,MAAM,CAAC;AAEjD;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAahE;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4BrE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,MAAM,CAAC,CA6B7D;AAED,MAAM,MAAM,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAE/C,wBAAsB,eAAe,IAAI,OAAO,CAAC,SAAS,CAAC,CAa1D;AAED,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9C;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,YAAY,CAAC,CAahE;AAED;;GAEG;AACH,wBAAsB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnF;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,OAAO,CAAC,CAS9D;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA0BnF;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,MAAM,EAAE,EAChB,aAAa,GAAE,MAAM,EAAO,GAC3B,OAAO,CAAC,MAAM,EAAE,CAAC,CA2BnB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CASxE;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CASnE;AAED;;GAEG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,OAAO,CAAC,CAShE"}
@@ -1,19 +1,55 @@
1
1
  import prompts from 'prompts';
2
2
  import { logger } from './logger.js';
3
- const noop = () => undefined;
4
- // Configure prompts to not exit on cancel
5
- prompts.override({ onCancel: noop });
3
+ /**
4
+ * Raised when the user aborts a prompt with Ctrl+C or Esc.
5
+ */
6
+ export class PromptCancelledError extends Error {
7
+ constructor() {
8
+ super('Prompt cancelled');
9
+ this.name = 'PromptCancelledError';
10
+ }
11
+ }
12
+ export function isPromptCancelled(error) {
13
+ return error instanceof PromptCancelledError;
14
+ }
15
+ /**
16
+ * Ask a question and turn an aborted prompt into a thrown PromptCancelledError.
17
+ *
18
+ * `prompts` only reports cancellation through the `onCancel` callback passed as
19
+ * its second argument — the resolved answers simply omit the cancelled key. So
20
+ * without this wrapper Ctrl+C is indistinguishable from an unanswered question,
21
+ * and each caller's `?? default` silently answers on the user's behalf.
22
+ */
23
+ async function ask(questions) {
24
+ let cancelled = false;
25
+ const answers = await prompts(questions, {
26
+ onCancel: () => {
27
+ cancelled = true;
28
+ // Returning false stops any remaining questions in the same chain.
29
+ return false;
30
+ },
31
+ });
32
+ if (cancelled) {
33
+ throw new PromptCancelledError();
34
+ }
35
+ return answers;
36
+ }
6
37
  /**
7
38
  * Prompt for ClickHouse connection details
8
39
  */
9
40
  export async function promptClickHouseConnection() {
10
- const response = await prompts([
11
- {
12
- type: 'text',
13
- name: 'host',
14
- message: 'ClickHouse URL (or skip to configure later):',
15
- initial: process.env.CLICKHOUSE_URL ?? process.env.CLICKHOUSE_HOST ?? '',
16
- },
41
+ const hostResponse = await ask({
42
+ type: 'text',
43
+ name: 'host',
44
+ message: 'ClickHouse URL (or skip to configure later):',
45
+ initial: process.env.CLICKHOUSE_URL ?? process.env.CLICKHOUSE_HOST ?? '',
46
+ });
47
+ // Do not ask for credentials when the user has chosen to configure the
48
+ // connection later.
49
+ if (!hostResponse.host) {
50
+ return null;
51
+ }
52
+ const credentials = await ask([
17
53
  {
18
54
  type: 'text',
19
55
  name: 'database',
@@ -33,11 +69,28 @@ export async function promptClickHouseConnection() {
33
69
  initial: process.env.CLICKHOUSE_PASSWORD ?? '',
34
70
  },
35
71
  ]);
36
- // If user cancelled or skipped
37
- if (!response.host) {
38
- return null;
39
- }
40
- return response;
72
+ return {
73
+ host: hostResponse.host,
74
+ database: credentials.database ?? '',
75
+ username: credentials.username ?? '',
76
+ password: credentials.password ?? '',
77
+ };
78
+ }
79
+ /**
80
+ * Choose between a remote ClickHouse server and embedded chDB.
81
+ */
82
+ export async function promptInitDatabase() {
83
+ const response = await ask({
84
+ type: 'select',
85
+ name: 'database',
86
+ message: 'Choose your database',
87
+ choices: [
88
+ { title: 'ClickHouse server or Cloud', value: 'clickhouse' },
89
+ { title: 'chDB (embedded, no server)', value: 'chdb' },
90
+ ],
91
+ initial: 0,
92
+ });
93
+ return response.database ?? 'clickhouse';
41
94
  }
42
95
  /**
43
96
  * Prompt for embedded chDB storage: ephemeral in-memory session, or an
@@ -45,7 +98,7 @@ export async function promptClickHouseConnection() {
45
98
  * or undefined for in-memory.
46
99
  */
47
100
  export async function promptChdbStorage() {
48
- const response = await prompts({
101
+ const response = await ask({
49
102
  type: 'select',
50
103
  name: 'storage',
51
104
  message: 'Where should embedded chDB store data?',
@@ -60,7 +113,7 @@ export async function promptChdbStorage() {
60
113
  return undefined;
61
114
  }
62
115
  if (response.storage === 'custom') {
63
- const customResponse = await prompts({
116
+ const customResponse = await ask({
64
117
  type: 'text',
65
118
  name: 'path',
66
119
  message: 'Enter chDB data directory:',
@@ -74,7 +127,7 @@ export async function promptChdbStorage() {
74
127
  * Prompt for output directory
75
128
  */
76
129
  export async function promptOutputDirectory() {
77
- const response = await prompts({
130
+ const response = await ask({
78
131
  type: 'select',
79
132
  name: 'directory',
80
133
  message: 'Where should we create your analytics files?',
@@ -89,7 +142,7 @@ export async function promptOutputDirectory() {
89
142
  return 'analytics'; // Default fallback
90
143
  }
91
144
  if (response.directory === 'custom') {
92
- const customResponse = await prompts({
145
+ const customResponse = await ask({
93
146
  type: 'text',
94
147
  name: 'path',
95
148
  message: 'Enter custom path:',
@@ -100,7 +153,7 @@ export async function promptOutputDirectory() {
100
153
  return response.directory;
101
154
  }
102
155
  export async function promptInitStyle() {
103
- const response = await prompts({
156
+ const response = await ask({
104
157
  type: 'select',
105
158
  name: 'style',
106
159
  message: 'Choose your dev API style',
@@ -112,11 +165,39 @@ export async function promptInitStyle() {
112
165
  });
113
166
  return response.style ?? 'queries';
114
167
  }
168
+ /**
169
+ * Choose whether generated routes include request-context auth scaffolding.
170
+ */
171
+ export async function promptInitAuthMode() {
172
+ const response = await ask({
173
+ type: 'select',
174
+ name: 'auth',
175
+ message: 'Choose authentication scaffolding',
176
+ choices: [
177
+ { title: 'None (add later)', value: 'none' },
178
+ { title: 'Request context authentication', value: 'context' },
179
+ ],
180
+ initial: 0,
181
+ });
182
+ return response.auth ?? 'none';
183
+ }
184
+ /**
185
+ * Confirm scaffolding when init is run outside a Node project.
186
+ */
187
+ export async function confirmWithoutPackageJson(directory) {
188
+ const response = await ask({
189
+ type: 'confirm',
190
+ name: 'continue',
191
+ message: `package.json was not found in ${directory}. Continue scaffolding here?`,
192
+ initial: false,
193
+ });
194
+ return response.continue ?? false;
195
+ }
115
196
  /**
116
197
  * Prompt for example query generation
117
198
  */
118
199
  export async function promptGenerateExample() {
119
- const response = await prompts({
200
+ const response = await ask({
120
201
  type: 'confirm',
121
202
  name: 'generate',
122
203
  message: 'Generate an example query?',
@@ -141,7 +222,7 @@ export async function promptTableSelection(tables) {
141
222
  ...tables.slice(0, 10).map(table => ({ title: table, value: table })),
142
223
  { title: 'Skip example', value: null },
143
224
  ];
144
- const response = await prompts({
225
+ const response = await ask({
145
226
  type: 'select',
146
227
  name: 'table',
147
228
  message: 'Which table should we use for the example?',
@@ -164,7 +245,7 @@ export async function promptDatasetTableSelection(tables, defaultTables = []) {
164
245
  logger.newline();
165
246
  }
166
247
  const defaults = new Set(defaultTables);
167
- const response = await prompts({
248
+ const response = await ask({
168
249
  type: 'multiselect',
169
250
  name: 'tables',
170
251
  message: 'Which tables should we scaffold as datasets?',
@@ -182,7 +263,7 @@ export async function promptDatasetTableSelection(tables, defaultTables = []) {
182
263
  * Confirm overwrite of existing files
183
264
  */
184
265
  export async function confirmOverwrite(files) {
185
- const response = await prompts({
266
+ const response = await ask({
186
267
  type: 'confirm',
187
268
  name: 'overwrite',
188
269
  message: `The following files will be overwritten:\n${files.map(f => ` • ${f}`).join('\n')}\n\nContinue?`,
@@ -194,7 +275,7 @@ export async function confirmOverwrite(files) {
194
275
  * Retry prompt for failed operations
195
276
  */
196
277
  export async function promptRetry(message) {
197
- const response = await prompts({
278
+ const response = await ask({
198
279
  type: 'confirm',
199
280
  name: 'retry',
200
281
  message,
@@ -206,7 +287,7 @@ export async function promptRetry(message) {
206
287
  * Ask if user wants to continue without DB connection
207
288
  */
208
289
  export async function promptContinueWithoutDb() {
209
- const response = await prompts({
290
+ const response = await ask({
210
291
  type: 'confirm',
211
292
  name: 'continue',
212
293
  message: 'Continue setup without database connection?',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.12.1",
3
+ "version": "1.13.1",
4
4
  "description": "Command-line interface for hypequery",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,9 +19,8 @@
19
19
  "open": "^10.0.0",
20
20
  "ora": "^8.0.1",
21
21
  "prompts": "^2.4.2",
22
- "@hypequery/deployment": "0.6.0",
23
22
  "@hypequery/protocol": "0.9.0",
24
- "@hypequery/clickhouse": "2.5.2"
23
+ "@hypequery/deployment": "0.6.0"
25
24
  },
26
25
  "optionalDependencies": {
27
26
  "@napi-rs/keyring": "1.3.0"