@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 +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +7 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +25 -4
- package/dist/generators/chdb.js +1 -1
- package/dist/generators/clickhouse.d.ts +1 -1
- package/dist/generators/clickhouse.d.ts.map +1 -1
- package/dist/generators/clickhouse.js +1 -1
- package/dist/generators/dataset-generator.d.ts +1 -1
- package/dist/generators/dataset-generator.d.ts.map +1 -1
- package/dist/generators/index.js +4 -4
- package/dist/typegen/generate-types.d.ts +46 -0
- package/dist/typegen/generate-types.d.ts.map +1 -0
- package/dist/typegen/generate-types.js +115 -0
- package/dist/typegen/index.d.ts +3 -0
- package/dist/typegen/index.d.ts.map +1 -0
- package/dist/typegen/index.js +2 -0
- package/dist/typegen/type-parsing.d.ts +6 -0
- package/dist/typegen/type-parsing.d.ts.map +1 -0
- package/dist/typegen/type-parsing.js +139 -0
- package/dist/utils/chdb-client.d.ts +1 -1
- package/dist/utils/chdb-client.d.ts.map +1 -1
- package/dist/utils/dependency-installer.d.ts +1 -0
- package/dist/utils/dependency-installer.d.ts.map +1 -1
- package/dist/utils/dependency-installer.js +37 -12
- package/dist/utils/prompts.d.ts +21 -0
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +107 -26
- package/package.json +2 -3
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;
|
|
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,
|
|
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"}
|
package/dist/commands/init.js
CHANGED
|
@@ -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'),
|
package/dist/generators/chdb.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,
|
|
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 '
|
|
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 '
|
|
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,
|
|
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"}
|
package/dist/generators/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
// Loaded on demand.
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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 other — and 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 @@
|
|
|
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 @@
|
|
|
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 +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,
|
|
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;
|
|
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
|
|
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
|
|
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
|
|
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
|
|
130
|
-
|
|
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
|
}
|
package/dist/utils/prompts.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/utils/prompts.js
CHANGED
|
@@ -1,19 +1,55 @@
|
|
|
1
1
|
import prompts from 'prompts';
|
|
2
2
|
import { logger } from './logger.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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/
|
|
23
|
+
"@hypequery/deployment": "0.6.0"
|
|
25
24
|
},
|
|
26
25
|
"optionalDependencies": {
|
|
27
26
|
"@napi-rs/keyring": "1.3.0"
|