@hypequery/cli 1.12.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -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.map +1 -1
- package/dist/utils/dependency-installer.js +8 -2
- package/dist/utils/prompts.d.ts +14 -0
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +62 -12
- 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
|
|
@@ -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 +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,
|
|
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,iBAwD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
|
|
@@ -126,8 +126,14 @@ export async function installScaffoldDependencies(style = 'queries', database =
|
|
|
126
126
|
readCliPackageJson(),
|
|
127
127
|
]);
|
|
128
128
|
if (!pkgJson) {
|
|
129
|
-
const
|
|
130
|
-
|
|
129
|
+
const packages = [
|
|
130
|
+
'@hypequery/clickhouse',
|
|
131
|
+
'@hypequery/serve',
|
|
132
|
+
...(style === 'datasets' ? ['@hypequery/datasets'] : []),
|
|
133
|
+
'zod',
|
|
134
|
+
...(database === 'chdb' ? ['chdb'] : []),
|
|
135
|
+
];
|
|
136
|
+
logger.warn(`package.json not found. Install ${packages.join(', ')} manually.`);
|
|
131
137
|
return;
|
|
132
138
|
}
|
|
133
139
|
const requestedPackages = resolveScaffoldPackages(cliPkgJson?.version, style, database);
|
package/dist/utils/prompts.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export declare function promptClickHouseConnection(): Promise<{
|
|
|
7
7
|
username: string;
|
|
8
8
|
password: string;
|
|
9
9
|
} | null>;
|
|
10
|
+
export type InitDatabase = 'clickhouse' | 'chdb';
|
|
11
|
+
/**
|
|
12
|
+
* Choose between a remote ClickHouse server and embedded chDB.
|
|
13
|
+
*/
|
|
14
|
+
export declare function promptInitDatabase(): Promise<InitDatabase>;
|
|
10
15
|
/**
|
|
11
16
|
* Prompt for embedded chDB storage: ephemeral in-memory session, or an
|
|
12
17
|
* on-disk directory that persists between runs. Returns the session path,
|
|
@@ -19,6 +24,15 @@ export declare function promptChdbStorage(): Promise<string | undefined>;
|
|
|
19
24
|
export declare function promptOutputDirectory(): Promise<string>;
|
|
20
25
|
export type InitStyle = 'queries' | 'datasets';
|
|
21
26
|
export declare function promptInitStyle(): Promise<InitStyle>;
|
|
27
|
+
export type InitAuthMode = 'none' | 'context';
|
|
28
|
+
/**
|
|
29
|
+
* Choose whether generated routes include request-context auth scaffolding.
|
|
30
|
+
*/
|
|
31
|
+
export declare function promptInitAuthMode(): Promise<InitAuthMode>;
|
|
32
|
+
/**
|
|
33
|
+
* Confirm scaffolding when init is run outside a Node project.
|
|
34
|
+
*/
|
|
35
|
+
export declare function confirmWithoutPackageJson(directory: string): Promise<boolean>;
|
|
22
36
|
/**
|
|
23
37
|
* Prompt for example query generation
|
|
24
38
|
*/
|
|
@@ -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,
|
|
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,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
|
@@ -7,13 +7,18 @@ prompts.override({ onCancel: noop });
|
|
|
7
7
|
* Prompt for ClickHouse connection details
|
|
8
8
|
*/
|
|
9
9
|
export async function promptClickHouseConnection() {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
const hostResponse = await prompts({
|
|
11
|
+
type: 'text',
|
|
12
|
+
name: 'host',
|
|
13
|
+
message: 'ClickHouse URL (or skip to configure later):',
|
|
14
|
+
initial: process.env.CLICKHOUSE_URL ?? process.env.CLICKHOUSE_HOST ?? '',
|
|
15
|
+
});
|
|
16
|
+
// Do not ask for credentials when the user has chosen to configure the
|
|
17
|
+
// connection later.
|
|
18
|
+
if (!hostResponse.host) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const credentials = await prompts([
|
|
17
22
|
{
|
|
18
23
|
type: 'text',
|
|
19
24
|
name: 'database',
|
|
@@ -33,11 +38,28 @@ export async function promptClickHouseConnection() {
|
|
|
33
38
|
initial: process.env.CLICKHOUSE_PASSWORD ?? '',
|
|
34
39
|
},
|
|
35
40
|
]);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
return {
|
|
42
|
+
host: hostResponse.host,
|
|
43
|
+
database: credentials.database ?? '',
|
|
44
|
+
username: credentials.username ?? '',
|
|
45
|
+
password: credentials.password ?? '',
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Choose between a remote ClickHouse server and embedded chDB.
|
|
50
|
+
*/
|
|
51
|
+
export async function promptInitDatabase() {
|
|
52
|
+
const response = await prompts({
|
|
53
|
+
type: 'select',
|
|
54
|
+
name: 'database',
|
|
55
|
+
message: 'Choose your database',
|
|
56
|
+
choices: [
|
|
57
|
+
{ title: 'ClickHouse server or Cloud', value: 'clickhouse' },
|
|
58
|
+
{ title: 'chDB (embedded, no server)', value: 'chdb' },
|
|
59
|
+
],
|
|
60
|
+
initial: 0,
|
|
61
|
+
});
|
|
62
|
+
return response.database ?? 'clickhouse';
|
|
41
63
|
}
|
|
42
64
|
/**
|
|
43
65
|
* Prompt for embedded chDB storage: ephemeral in-memory session, or an
|
|
@@ -112,6 +134,34 @@ export async function promptInitStyle() {
|
|
|
112
134
|
});
|
|
113
135
|
return response.style ?? 'queries';
|
|
114
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Choose whether generated routes include request-context auth scaffolding.
|
|
139
|
+
*/
|
|
140
|
+
export async function promptInitAuthMode() {
|
|
141
|
+
const response = await prompts({
|
|
142
|
+
type: 'select',
|
|
143
|
+
name: 'auth',
|
|
144
|
+
message: 'Choose authentication scaffolding',
|
|
145
|
+
choices: [
|
|
146
|
+
{ title: 'None (add later)', value: 'none' },
|
|
147
|
+
{ title: 'Request context authentication', value: 'context' },
|
|
148
|
+
],
|
|
149
|
+
initial: 0,
|
|
150
|
+
});
|
|
151
|
+
return response.auth ?? 'none';
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Confirm scaffolding when init is run outside a Node project.
|
|
155
|
+
*/
|
|
156
|
+
export async function confirmWithoutPackageJson(directory) {
|
|
157
|
+
const response = await prompts({
|
|
158
|
+
type: 'confirm',
|
|
159
|
+
name: 'continue',
|
|
160
|
+
message: `package.json was not found in ${directory}. Continue scaffolding here?`,
|
|
161
|
+
initial: false,
|
|
162
|
+
});
|
|
163
|
+
return response.continue ?? false;
|
|
164
|
+
}
|
|
115
165
|
/**
|
|
116
166
|
* Prompt for example query generation
|
|
117
167
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "Command-line interface for hypequery",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
"ora": "^8.0.1",
|
|
21
21
|
"prompts": "^2.4.2",
|
|
22
22
|
"@hypequery/deployment": "0.6.0",
|
|
23
|
-
"@hypequery/protocol": "0.9.0"
|
|
24
|
-
"@hypequery/clickhouse": "2.5.2"
|
|
23
|
+
"@hypequery/protocol": "0.9.0"
|
|
25
24
|
},
|
|
26
25
|
"optionalDependencies": {
|
|
27
26
|
"@napi-rs/keyring": "1.3.0"
|