@hypequery/cli 1.3.1 → 1.5.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 +61 -10
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +24 -1
- package/dist/commands/deployment.d.ts +11 -0
- package/dist/commands/deployment.d.ts.map +1 -0
- package/dist/commands/deployment.js +84 -0
- package/dist/commands/generate.d.ts +1 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +22 -3
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +180 -41
- package/dist/generators/chdb.d.ts +13 -0
- package/dist/generators/chdb.d.ts.map +1 -0
- package/dist/generators/chdb.js +17 -0
- package/dist/generators/dataset-generator.d.ts +2 -0
- package/dist/generators/dataset-generator.d.ts.map +1 -1
- package/dist/generators/dataset-generator.js +1 -1
- package/dist/generators/index.d.ts +2 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +2 -0
- package/dist/templates/client.d.ts +7 -2
- package/dist/templates/client.d.ts.map +1 -1
- package/dist/templates/client.js +23 -2
- package/dist/templates/gitignore.d.ts +1 -1
- package/dist/templates/gitignore.d.ts.map +1 -1
- package/dist/templates/gitignore.js +20 -6
- package/dist/utils/chdb-client.d.ts +36 -0
- package/dist/utils/chdb-client.d.ts.map +1 -0
- package/dist/utils/chdb-client.js +122 -0
- package/dist/utils/dependency-installer.d.ts +3 -2
- package/dist/utils/dependency-installer.d.ts.map +1 -1
- package/dist/utils/dependency-installer.js +8 -3
- package/dist/utils/detect-database.d.ts +11 -4
- package/dist/utils/detect-database.d.ts.map +1 -1
- package/dist/utils/detect-database.js +23 -5
- package/dist/utils/prompts.d.ts +6 -8
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +31 -24
- package/package.json +5 -4
- package/dist/utils/clickhouse-sql.d.ts +0 -6
- package/dist/utils/clickhouse-sql.d.ts.map +0 -1
- package/dist/utils/clickhouse-sql.js +0 -18
package/dist/commands/init.js
CHANGED
|
@@ -2,8 +2,9 @@ import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import ora from 'ora';
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
5
|
-
import { promptClickHouseConnection, promptOutputDirectory, promptInitStyle, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
|
|
5
|
+
import { promptClickHouseConnection, promptChdbStorage, promptOutputDirectory, promptInitStyle, promptGenerateExample, promptTableSelection, promptDatasetTableSelection, confirmOverwrite, promptRetry, promptContinueWithoutDb, } from '../utils/prompts.js';
|
|
6
6
|
import { validateConnection, getTableCount, getTables, } from '../utils/detect-database.js';
|
|
7
|
+
import { ChdbNotInstalledError, ensureChdbInstalled, getChdbTypeGenerationClient, } from '../utils/chdb-client.js';
|
|
7
8
|
import { hasEnvFile, hasGitignore } from '../utils/find-files.js';
|
|
8
9
|
import { generateEnvTemplate, appendToEnv } from '../templates/env.js';
|
|
9
10
|
import { generateClientTemplate } from '../templates/client.js';
|
|
@@ -14,6 +15,15 @@ import { appendToGitignore } from '../templates/gitignore.js';
|
|
|
14
15
|
import { getTypeGenerator } from '../generators/index.js';
|
|
15
16
|
import { generateDatasets } from '../generators/dataset-generator.js';
|
|
16
17
|
import { installScaffoldDependencies } from '../utils/dependency-installer.js';
|
|
18
|
+
function normalizeInitDatabase(database) {
|
|
19
|
+
if (!database || database === 'clickhouse') {
|
|
20
|
+
return 'clickhouse';
|
|
21
|
+
}
|
|
22
|
+
if (database === 'chdb') {
|
|
23
|
+
return 'chdb';
|
|
24
|
+
}
|
|
25
|
+
throw new Error(`Unsupported database "${database}". Use "clickhouse" or "chdb".`);
|
|
26
|
+
}
|
|
17
27
|
function normalizeInitStyle(style) {
|
|
18
28
|
return style === 'datasets' ? 'datasets' : 'queries';
|
|
19
29
|
}
|
|
@@ -33,6 +43,22 @@ function parseTableList(value) {
|
|
|
33
43
|
.filter(Boolean);
|
|
34
44
|
return parsed && parsed.length > 0 ? parsed : undefined;
|
|
35
45
|
}
|
|
46
|
+
function getChdbGitignoreEntry(chdbPath) {
|
|
47
|
+
if (!chdbPath || /[\0\r\n]/.test(chdbPath)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const cwd = path.resolve(process.cwd());
|
|
51
|
+
const relativePath = path.relative(cwd, path.resolve(cwd, chdbPath));
|
|
52
|
+
if (relativePath.length === 0 ||
|
|
53
|
+
relativePath === '..' ||
|
|
54
|
+
relativePath.startsWith(`..${path.sep}`) ||
|
|
55
|
+
path.isAbsolute(relativePath)) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const normalizedPath = relativePath.split(path.sep).join('/');
|
|
59
|
+
const escapedPath = normalizedPath.replace(/[\\*?[\]]/g, '\\$&');
|
|
60
|
+
return `/${escapedPath}/`;
|
|
61
|
+
}
|
|
36
62
|
async function resolveConnectionConfig(options) {
|
|
37
63
|
if (options.noInteractive) {
|
|
38
64
|
const required = (keys) => {
|
|
@@ -52,6 +78,34 @@ async function resolveConnectionConfig(options) {
|
|
|
52
78
|
}
|
|
53
79
|
return promptClickHouseConnection();
|
|
54
80
|
}
|
|
81
|
+
async function testChdbConnection(chdbPath) {
|
|
82
|
+
const spinner = ora('Starting embedded chDB...').start();
|
|
83
|
+
try {
|
|
84
|
+
await ensureChdbInstalled();
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const isNotInstalled = error instanceof ChdbNotInstalledError;
|
|
88
|
+
spinner.fail(isNotInstalled ? 'chdb is not installed' : 'Embedded chDB failed to load');
|
|
89
|
+
logger.newline();
|
|
90
|
+
logger.error(error instanceof Error ? error.message : String(error));
|
|
91
|
+
logger.newline();
|
|
92
|
+
return { ok: false, reason: isNotInstalled ? 'not-installed' : 'engine-error' };
|
|
93
|
+
}
|
|
94
|
+
const isValid = await validateConnection('chdb', { chdbPath });
|
|
95
|
+
if (!isValid) {
|
|
96
|
+
spinner.fail('Embedded chDB failed to run a query');
|
|
97
|
+
logger.newline();
|
|
98
|
+
logger.info('Common issues:');
|
|
99
|
+
logger.indent('• Unsupported platform (chdb ships linux/macOS binaries; Windows needs WSL2)');
|
|
100
|
+
logger.indent(`• The session directory is locked by another process${chdbPath ? ` (${chdbPath})` : ''}`);
|
|
101
|
+
logger.newline();
|
|
102
|
+
return { ok: false, reason: 'engine-error' };
|
|
103
|
+
}
|
|
104
|
+
const tableCount = await getTableCount('chdb', { chdbPath });
|
|
105
|
+
spinner.succeed(`Embedded chDB ready (${chdbPath ? `${tableCount} tables in ${chdbPath}` : 'in-memory session'})`);
|
|
106
|
+
logger.newline();
|
|
107
|
+
return { ok: true };
|
|
108
|
+
}
|
|
55
109
|
async function testConnection(connectionConfig) {
|
|
56
110
|
const spinner = ora('Testing connection...').start();
|
|
57
111
|
process.env.CLICKHOUSE_URL = connectionConfig.host;
|
|
@@ -80,43 +134,58 @@ async function testConnection(connectionConfig) {
|
|
|
80
134
|
}
|
|
81
135
|
export async function initCommand(options = {}) {
|
|
82
136
|
const noInteractive = options.noInteractive === true || options.interactive === false;
|
|
137
|
+
const database = normalizeInitDatabase(options.database);
|
|
83
138
|
logger.newline();
|
|
84
139
|
logger.header('Welcome to hypequery!');
|
|
85
|
-
logger.info(
|
|
140
|
+
logger.info(database === 'chdb'
|
|
141
|
+
? "Let's set up your analytics layer on embedded ClickHouse (chDB)."
|
|
142
|
+
: "Let's set up your analytics layer.");
|
|
86
143
|
logger.newline();
|
|
87
144
|
// Step 2: Get connection details
|
|
88
|
-
let connectionConfig =
|
|
145
|
+
let connectionConfig = null;
|
|
89
146
|
let hasValidConnection = false;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
147
|
+
let chdbPath = options.chdbPath;
|
|
148
|
+
let chdbFailureReason;
|
|
149
|
+
if (database === 'chdb') {
|
|
150
|
+
// No server, no credentials — the only connection question is where the
|
|
151
|
+
// embedded session stores its data.
|
|
152
|
+
if (!chdbPath && !noInteractive) {
|
|
153
|
+
chdbPath = await promptChdbStorage();
|
|
154
|
+
}
|
|
98
155
|
}
|
|
99
156
|
else {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
if (!
|
|
103
|
-
|
|
104
|
-
throw new Error('Failed to connect to ClickHouse in non-interactive mode. Check your environment variables or use interactive setup.');
|
|
105
|
-
}
|
|
106
|
-
const retry = await promptRetry('Try again?');
|
|
107
|
-
if (retry) {
|
|
108
|
-
return initCommand(options);
|
|
109
|
-
}
|
|
110
|
-
const continueWithout = await promptContinueWithoutDb();
|
|
111
|
-
if (!continueWithout) {
|
|
112
|
-
logger.info('Setup cancelled');
|
|
113
|
-
process.exit(0);
|
|
114
|
-
}
|
|
157
|
+
connectionConfig = await resolveConnectionConfig(options);
|
|
158
|
+
// Handle user skipping connection details
|
|
159
|
+
if (!connectionConfig) {
|
|
160
|
+
logger.info('Skipping database connection for now.');
|
|
115
161
|
logger.newline();
|
|
116
|
-
|
|
117
|
-
|
|
162
|
+
}
|
|
163
|
+
else if (options.skipConnection) {
|
|
164
|
+
logger.info('Skipping database connection test (requested).');
|
|
118
165
|
logger.newline();
|
|
119
|
-
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const { hasValidConnection: valid } = await testConnection(connectionConfig);
|
|
169
|
+
hasValidConnection = valid;
|
|
170
|
+
if (!hasValidConnection) {
|
|
171
|
+
if (noInteractive) {
|
|
172
|
+
throw new Error('Failed to connect to ClickHouse in non-interactive mode. Check your environment variables or use interactive setup.');
|
|
173
|
+
}
|
|
174
|
+
const retry = await promptRetry('Try again?');
|
|
175
|
+
if (retry) {
|
|
176
|
+
return initCommand(options);
|
|
177
|
+
}
|
|
178
|
+
const continueWithout = await promptContinueWithoutDb();
|
|
179
|
+
if (!continueWithout) {
|
|
180
|
+
logger.info('Setup cancelled');
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
logger.newline();
|
|
184
|
+
logger.info('Continuing without database connection.');
|
|
185
|
+
logger.info('You can configure the connection later in .env');
|
|
186
|
+
logger.newline();
|
|
187
|
+
connectionConfig = null;
|
|
188
|
+
}
|
|
120
189
|
}
|
|
121
190
|
}
|
|
122
191
|
// Step 4: Get output directory
|
|
@@ -166,6 +235,37 @@ export async function initCommand(options = {}) {
|
|
|
166
235
|
}
|
|
167
236
|
logger.newline();
|
|
168
237
|
}
|
|
238
|
+
if (database === 'chdb') {
|
|
239
|
+
// All prompts and overwrite checks are complete, so installing packages
|
|
240
|
+
// here cannot leave a cancelled scaffold with unexpected dependencies.
|
|
241
|
+
await installScaffoldDependencies(style, 'chdb');
|
|
242
|
+
if (options.skipConnection) {
|
|
243
|
+
logger.info('Skipping embedded chDB test (requested).');
|
|
244
|
+
logger.newline();
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
const chdbTest = await testChdbConnection(chdbPath);
|
|
248
|
+
hasValidConnection = chdbTest.ok;
|
|
249
|
+
if (!chdbTest.ok) {
|
|
250
|
+
chdbFailureReason = chdbTest.reason;
|
|
251
|
+
}
|
|
252
|
+
if (!hasValidConnection) {
|
|
253
|
+
if (noInteractive) {
|
|
254
|
+
throw new Error(chdbFailureReason === 'not-installed'
|
|
255
|
+
? 'Embedded chDB failed to start in non-interactive mode. Install the chdb package and re-run.'
|
|
256
|
+
: 'Embedded chDB failed to start in non-interactive mode. Resolve the engine error shown above and re-run.');
|
|
257
|
+
}
|
|
258
|
+
const continueWithout = await promptContinueWithoutDb();
|
|
259
|
+
if (!continueWithout) {
|
|
260
|
+
logger.info('Setup cancelled');
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
logger.newline();
|
|
264
|
+
logger.info('Continuing without a working embedded engine.');
|
|
265
|
+
logger.newline();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
169
269
|
// Step 6: Ask about example query (only if we have a valid connection)
|
|
170
270
|
let generateExample = !options.noExample && hasValidConnection;
|
|
171
271
|
let selectedTable = null;
|
|
@@ -173,7 +273,7 @@ export async function initCommand(options = {}) {
|
|
|
173
273
|
if (generateExample && !noInteractive && hasValidConnection) {
|
|
174
274
|
generateExample = await promptGenerateExample();
|
|
175
275
|
if (generateExample) {
|
|
176
|
-
discoveredTables = await getTables(
|
|
276
|
+
discoveredTables = await getTables(database, { chdbPath });
|
|
177
277
|
selectedTable = await promptTableSelection(discoveredTables);
|
|
178
278
|
generateExample = selectedTable !== null;
|
|
179
279
|
}
|
|
@@ -185,14 +285,19 @@ export async function initCommand(options = {}) {
|
|
|
185
285
|
!options.allTables &&
|
|
186
286
|
!datasetTables &&
|
|
187
287
|
!noInteractive) {
|
|
188
|
-
discoveredTables ??= await getTables(
|
|
288
|
+
discoveredTables ??= await getTables(database, { chdbPath });
|
|
189
289
|
datasetTables = await promptDatasetTableSelection(discoveredTables, selectedTable ? [selectedTable] : []);
|
|
190
290
|
}
|
|
191
291
|
logger.newline();
|
|
192
292
|
// Step 7: Create directory
|
|
193
293
|
await mkdir(resolvedOutputDir, { recursive: true });
|
|
194
|
-
// Step 8: Save credentials to .env (if we have connection config)
|
|
195
|
-
|
|
294
|
+
// Step 8: Save credentials to .env (if we have connection config).
|
|
295
|
+
// Embedded chDB has no credentials — the storage path lives in client.ts —
|
|
296
|
+
// so the chdb scaffold writes no .env at all.
|
|
297
|
+
if (database === 'chdb') {
|
|
298
|
+
// nothing to persist
|
|
299
|
+
}
|
|
300
|
+
else if (connectionConfig) {
|
|
196
301
|
const envPath = path.join(process.cwd(), '.env');
|
|
197
302
|
const envExists = await hasEnvFile();
|
|
198
303
|
if (envExists) {
|
|
@@ -226,8 +331,8 @@ export async function initCommand(options = {}) {
|
|
|
226
331
|
if (hasValidConnection) {
|
|
227
332
|
const typeSpinner = ora('Generating TypeScript types...').start();
|
|
228
333
|
try {
|
|
229
|
-
const generator = getTypeGenerator(
|
|
230
|
-
await generator({ outputPath: schemaPath });
|
|
334
|
+
const generator = getTypeGenerator(database);
|
|
335
|
+
await generator({ outputPath: schemaPath, chdbPath });
|
|
231
336
|
typeSpinner.succeed(`Generated TypeScript types (${path.relative(process.cwd(), schemaPath)})`);
|
|
232
337
|
}
|
|
233
338
|
catch (error) {
|
|
@@ -238,8 +343,11 @@ export async function initCommand(options = {}) {
|
|
|
238
343
|
}
|
|
239
344
|
else {
|
|
240
345
|
// Create placeholder schema file
|
|
346
|
+
const regenerateHint = database === 'chdb'
|
|
347
|
+
? "// Run 'npx hypequery generate --database chdb --chdb-path <dir>' after creating persistent tables"
|
|
348
|
+
: "// Run 'npx hypequery generate' after configuring your database connection";
|
|
241
349
|
await writeFile(schemaPath, `// Generated by hypequery
|
|
242
|
-
|
|
350
|
+
${regenerateHint}
|
|
243
351
|
|
|
244
352
|
export interface IntrospectedSchema {
|
|
245
353
|
// Your table types will appear here after generation
|
|
@@ -249,8 +357,8 @@ export interface IntrospectedSchema {
|
|
|
249
357
|
}
|
|
250
358
|
// Step 10: Create client.ts
|
|
251
359
|
const clientPath = path.join(resolvedOutputDir, 'client.ts');
|
|
252
|
-
await writeFile(clientPath, generateClientTemplate());
|
|
253
|
-
logger.success(`Created ClickHouse client (${path.relative(process.cwd(), clientPath)})`);
|
|
360
|
+
await writeFile(clientPath, generateClientTemplate({ database, chdbPath }));
|
|
361
|
+
logger.success(`Created ${database === 'chdb' ? 'embedded chDB' : 'ClickHouse'} client (${path.relative(process.cwd(), clientPath)})`);
|
|
254
362
|
// Step 11: Create API entrypoint
|
|
255
363
|
let apiPath;
|
|
256
364
|
let generatedAnyDatasets = false;
|
|
@@ -264,6 +372,9 @@ export interface IntrospectedSchema {
|
|
|
264
372
|
outputPath: datasetsPath,
|
|
265
373
|
includeTables: options.allTables ? undefined : datasetTables,
|
|
266
374
|
excludeTables: excludedDatasetTables,
|
|
375
|
+
...(database === 'chdb'
|
|
376
|
+
? { client: getChdbTypeGenerationClient(chdbPath) }
|
|
377
|
+
: {}),
|
|
267
378
|
});
|
|
268
379
|
generatedAnyDatasets = true;
|
|
269
380
|
generatedSelectedDataset = selectedTable !== null && (options.allTables === true ||
|
|
@@ -295,20 +406,24 @@ export interface IntrospectedSchema {
|
|
|
295
406
|
// Step 12: Update .gitignore
|
|
296
407
|
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
297
408
|
const gitignoreExists = await hasGitignore();
|
|
409
|
+
const chdbGitignoreEntry = database === 'chdb'
|
|
410
|
+
? getChdbGitignoreEntry(chdbPath)
|
|
411
|
+
: undefined;
|
|
412
|
+
const gitignoreEntries = chdbGitignoreEntry ? [chdbGitignoreEntry] : [];
|
|
298
413
|
if (gitignoreExists) {
|
|
299
414
|
const existingGitignore = await readFile(gitignorePath, 'utf-8');
|
|
300
|
-
const newGitignore = appendToGitignore(existingGitignore);
|
|
415
|
+
const newGitignore = appendToGitignore(existingGitignore, gitignoreEntries);
|
|
301
416
|
if (newGitignore !== existingGitignore) {
|
|
302
417
|
await writeFile(gitignorePath, newGitignore);
|
|
303
418
|
logger.success('Updated .gitignore');
|
|
304
419
|
}
|
|
305
420
|
}
|
|
306
421
|
else {
|
|
307
|
-
await writeFile(gitignorePath, appendToGitignore(''));
|
|
422
|
+
await writeFile(gitignorePath, appendToGitignore('', gitignoreEntries));
|
|
308
423
|
logger.success('Created .gitignore');
|
|
309
424
|
}
|
|
310
425
|
// Step 13: Ensure required hypequery packages are installed
|
|
311
|
-
await installScaffoldDependencies(style);
|
|
426
|
+
await installScaffoldDependencies(style, database);
|
|
312
427
|
// Step 14: Success message
|
|
313
428
|
logger.newline();
|
|
314
429
|
logger.header('Setup complete!');
|
|
@@ -342,6 +457,24 @@ export interface IntrospectedSchema {
|
|
|
342
457
|
logger.newline();
|
|
343
458
|
}
|
|
344
459
|
}
|
|
460
|
+
else if (database === 'chdb') {
|
|
461
|
+
logger.info('Next steps:');
|
|
462
|
+
logger.newline();
|
|
463
|
+
// chdb is normally installed by the scaffold itself — only tell the user
|
|
464
|
+
// to install when that is actually what failed, not when an installed
|
|
465
|
+
// engine could not run (unsupported platform, locked session directory).
|
|
466
|
+
const firstStep = options.skipConnection
|
|
467
|
+
? '1. Verify the embedded engine and create your tables'
|
|
468
|
+
: chdbFailureReason === 'engine-error'
|
|
469
|
+
? '1. Resolve the engine error shown above'
|
|
470
|
+
: '1. Install the embedded engine: npm install chdb';
|
|
471
|
+
logger.indent(firstStep);
|
|
472
|
+
logger.indent(chdbPath
|
|
473
|
+
? `2. Run: npx hypequery generate --database chdb --chdb-path ${chdbPath}`
|
|
474
|
+
: '2. Re-run init with --chdb-path <dir> if later generate commands must see your tables');
|
|
475
|
+
logger.indent('3. Run: npx hypequery dev (to start dev server)');
|
|
476
|
+
logger.newline();
|
|
477
|
+
}
|
|
345
478
|
else {
|
|
346
479
|
logger.info('Next steps:');
|
|
347
480
|
logger.newline();
|
|
@@ -350,6 +483,12 @@ export interface IntrospectedSchema {
|
|
|
350
483
|
logger.indent('3. Run: npx hypequery dev (to start dev server)');
|
|
351
484
|
logger.newline();
|
|
352
485
|
}
|
|
486
|
+
if (database === 'chdb' && hasValidConnection) {
|
|
487
|
+
logger.indent(chdbPath
|
|
488
|
+
? `hypequery generate --database chdb --chdb-path ${chdbPath} Refresh types after creating tables`
|
|
489
|
+
: 'In-memory chDB is process-local; use --chdb-path <dir> for later type generation');
|
|
490
|
+
logger.newline();
|
|
491
|
+
}
|
|
353
492
|
logger.info('Docs: https://hypequery.com/docs');
|
|
354
493
|
logger.newline();
|
|
355
494
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface ChdbGeneratorOptions {
|
|
2
|
+
outputPath: string;
|
|
3
|
+
includeTables?: string[];
|
|
4
|
+
excludeTables?: string[];
|
|
5
|
+
chdbPath?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Same generator as ClickHouse — the introspection SQL (SHOW TABLES,
|
|
9
|
+
* DESCRIBE TABLE) is identical on the embedded engine — but the client seam
|
|
10
|
+
* is backed by a chDB session instead of an HTTP connection.
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateChdbTypes(options: ChdbGeneratorOptions): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=chdb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chdb.d.ts","sourceRoot":"","sources":["../../src/generators/chdb.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,iBAUpE"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { generateTypes } from '@hypequery/clickhouse/cli';
|
|
2
|
+
import { getChdbTypeGenerationClient } from '../utils/chdb-client.js';
|
|
3
|
+
/**
|
|
4
|
+
* Same generator as ClickHouse — the introspection SQL (SHOW TABLES,
|
|
5
|
+
* DESCRIBE TABLE) is identical on the embedded engine — but the client seam
|
|
6
|
+
* is backed by a chDB session instead of an HTTP connection.
|
|
7
|
+
*/
|
|
8
|
+
export async function generateChdbTypes(options) {
|
|
9
|
+
const generatorOptions = {
|
|
10
|
+
client: getChdbTypeGenerationClient(options.chdbPath),
|
|
11
|
+
generatedBy: 'hypequery',
|
|
12
|
+
includeUsageExample: false,
|
|
13
|
+
...(options.includeTables ? { includeTables: options.includeTables } : {}),
|
|
14
|
+
...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
|
|
15
|
+
};
|
|
16
|
+
await generateTypes(options.outputPath, generatorOptions);
|
|
17
|
+
}
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
* Generates dataset DSL code from ClickHouse schema introspection.
|
|
5
5
|
* This reduces quickstart friction by auto-scaffolding dataset definitions.
|
|
6
6
|
*/
|
|
7
|
+
import type { TypeGenerationClickHouseClient } from '@hypequery/clickhouse/cli';
|
|
7
8
|
export interface DatasetGeneratorOptions {
|
|
8
9
|
outputPath: string;
|
|
9
10
|
includeTables?: string[];
|
|
10
11
|
excludeTables?: string[];
|
|
12
|
+
client?: TypeGenerationClickHouseClient;
|
|
11
13
|
}
|
|
12
14
|
/**
|
|
13
15
|
* Generate dataset definitions from ClickHouse schema
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,2BAA2B,CAAC;AAGhF,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;CACzC;AAuPD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,iBAyDtE"}
|
|
@@ -199,7 +199,7 @@ ${configLines.join('\n')}
|
|
|
199
199
|
* Generate dataset definitions from ClickHouse schema
|
|
200
200
|
*/
|
|
201
201
|
export async function generateDatasets(options) {
|
|
202
|
-
const client = getClickHouseClient();
|
|
202
|
+
const client = options.client ?? getClickHouseClient();
|
|
203
203
|
// Get all tables
|
|
204
204
|
const tablesQuery = await client.query({
|
|
205
205
|
query: 'SHOW TABLES',
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DatabaseType } from '../utils/detect-database.js';
|
|
2
2
|
import { type ClickHouseGeneratorOptions } from './clickhouse.js';
|
|
3
|
-
|
|
3
|
+
import { type ChdbGeneratorOptions } from './chdb.js';
|
|
4
|
+
export type TypeGeneratorOptions = ClickHouseGeneratorOptions & ChdbGeneratorOptions;
|
|
4
5
|
type GeneratorFn = (options: TypeGeneratorOptions) => Promise<void>;
|
|
5
6
|
export declare function getTypeGenerator(dbType: DatabaseType): GeneratorFn;
|
|
6
7
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAA2B,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/generators/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,EAA2B,KAAK,0BAA0B,EAAE,MAAM,iBAAiB,CAAC;AAC3F,OAAO,EAAqB,KAAK,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAEzE,MAAM,MAAM,oBAAoB,GAAG,0BAA0B,GAAG,oBAAoB,CAAC;AAErF,KAAK,WAAW,GAAG,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAOpE,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAYlE"}
|
package/dist/generators/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { generateClickHouseTypes } from './clickhouse.js';
|
|
2
|
+
import { generateChdbTypes } from './chdb.js';
|
|
2
3
|
const generators = {
|
|
3
4
|
clickhouse: generateClickHouseTypes,
|
|
5
|
+
chdb: generateChdbTypes,
|
|
4
6
|
};
|
|
5
7
|
export function getTypeGenerator(dbType) {
|
|
6
8
|
const generator = generators[dbType];
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
export interface ClientTemplateOptions {
|
|
2
|
+
database?: 'clickhouse' | 'chdb';
|
|
3
|
+
/** On-disk chDB session directory; omit for an in-memory session. */
|
|
4
|
+
chdbPath?: string;
|
|
5
|
+
}
|
|
1
6
|
/**
|
|
2
|
-
* Generate client.ts file for ClickHouse
|
|
7
|
+
* Generate client.ts file for ClickHouse (HTTP) or embedded chDB
|
|
3
8
|
*/
|
|
4
|
-
export declare function generateClientTemplate(): string;
|
|
9
|
+
export declare function generateClientTemplate(options?: ClientTemplateOptions): string;
|
|
5
10
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/templates/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/templates/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,YAAY,GAAG,MAAM,CAAC;IACjC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,qBAA0B,GAAG,MAAM,CAiClF"}
|
package/dist/templates/client.js
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generate client.ts file for ClickHouse
|
|
2
|
+
* Generate client.ts file for ClickHouse (HTTP) or embedded chDB
|
|
3
3
|
*/
|
|
4
|
-
export function generateClientTemplate() {
|
|
4
|
+
export function generateClientTemplate(options = {}) {
|
|
5
|
+
if (options.database === 'chdb') {
|
|
6
|
+
// JSON.stringify produces a valid JS string literal for any path
|
|
7
|
+
// (quotes, backslashes) — raw interpolation would break the scaffold.
|
|
8
|
+
const sessionArg = options.chdbPath ? JSON.stringify(options.chdbPath) : '';
|
|
9
|
+
const safePathForComment = options.chdbPath?.replace(/[\r\n\u2028\u2029]/g, '') ?? '';
|
|
10
|
+
const storageComment = options.chdbPath
|
|
11
|
+
? `// Embedded ClickHouse — data persists in ${safePathForComment}`
|
|
12
|
+
: '// Embedded ClickHouse — in-memory session (data is discarded on exit).\n// Pass a directory path to new Session(...) to persist between runs.';
|
|
13
|
+
return `import { createQueryBuilder } from '@hypequery/clickhouse';
|
|
14
|
+
import { Session } from 'chdb';
|
|
15
|
+
import { chdbAdapter } from 'chdb/hypequery';
|
|
16
|
+
import type { IntrospectedSchema } from './schema.js';
|
|
17
|
+
|
|
18
|
+
${storageComment}
|
|
19
|
+
export const session = new Session(${sessionArg});
|
|
20
|
+
|
|
21
|
+
export const db = createQueryBuilder<IntrospectedSchema>({
|
|
22
|
+
adapter: chdbAdapter({ session }),
|
|
23
|
+
});
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
5
26
|
return `import { createQueryBuilder } from '@hypequery/clickhouse';
|
|
6
27
|
import type { IntrospectedSchema } from './schema.js';
|
|
7
28
|
|
|
@@ -9,5 +9,5 @@ export declare function hasHypequeryEntries(content: string): boolean;
|
|
|
9
9
|
/**
|
|
10
10
|
* Append hypequery entries to .gitignore
|
|
11
11
|
*/
|
|
12
|
-
export declare function appendToGitignore(existingContent: string): string;
|
|
12
|
+
export declare function appendToGitignore(existingContent: string, additionalEntries?: readonly string[]): string;
|
|
13
13
|
//# sourceMappingURL=gitignore.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/templates/gitignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,iBAAiB,0BAG7B,CAAC;
|
|
1
|
+
{"version":3,"file":"gitignore.d.ts","sourceRoot":"","sources":["../../src/templates/gitignore.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,iBAAiB,0BAG7B,CAAC;AAQF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,eAAe,EAAE,MAAM,EACvB,iBAAiB,GAAE,SAAS,MAAM,EAAO,GACxC,MAAM,CAuBR"}
|
|
@@ -5,21 +5,35 @@ export const GITIGNORE_CONTENT = `
|
|
|
5
5
|
# Hypequery
|
|
6
6
|
.env
|
|
7
7
|
`;
|
|
8
|
+
function hasEntry(content, entry) {
|
|
9
|
+
return content
|
|
10
|
+
.split(/\r?\n/)
|
|
11
|
+
.some((line) => line.trim() === entry);
|
|
12
|
+
}
|
|
8
13
|
/**
|
|
9
14
|
* Check if .gitignore already has hypequery entries
|
|
10
15
|
*/
|
|
11
16
|
export function hasHypequeryEntries(content) {
|
|
12
|
-
return content
|
|
17
|
+
return hasEntry(content, '# Hypequery') || hasEntry(content, '.env');
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Append hypequery entries to .gitignore
|
|
16
21
|
*/
|
|
17
|
-
export function appendToGitignore(existingContent) {
|
|
18
|
-
|
|
22
|
+
export function appendToGitignore(existingContent, additionalEntries = []) {
|
|
23
|
+
const entries = ['.env', ...additionalEntries];
|
|
24
|
+
const missingEntries = entries.filter((entry, index) => entry.length > 0 && entries.indexOf(entry) === index && !hasEntry(existingContent, entry));
|
|
25
|
+
if (missingEntries.length === 0) {
|
|
19
26
|
return existingContent;
|
|
20
27
|
}
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
let updatedContent = existingContent;
|
|
29
|
+
if (updatedContent.length > 0 && !updatedContent.endsWith('\n')) {
|
|
30
|
+
updatedContent += '\n';
|
|
31
|
+
}
|
|
32
|
+
if (!hasHypequeryEntries(existingContent)) {
|
|
33
|
+
if (updatedContent.length > 0 && !updatedContent.endsWith('\n\n')) {
|
|
34
|
+
updatedContent += '\n';
|
|
35
|
+
}
|
|
36
|
+
updatedContent += '# Hypequery\n';
|
|
23
37
|
}
|
|
24
|
-
return
|
|
38
|
+
return `${updatedContent}${missingEntries.join('\n')}\n`;
|
|
25
39
|
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { TypeGenerationClickHouseClient } from '@hypequery/clickhouse/cli';
|
|
2
|
+
interface ChdbSession {
|
|
3
|
+
queryAsync(sql: string, options?: {
|
|
4
|
+
format?: string;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
text(): string;
|
|
7
|
+
}>;
|
|
8
|
+
close(): void;
|
|
9
|
+
}
|
|
10
|
+
export declare class ChdbNotInstalledError extends Error {
|
|
11
|
+
readonly code = "CHDB_NOT_INSTALLED";
|
|
12
|
+
constructor();
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Get (or create) the embedded chDB session. `path` selects the on-disk
|
|
16
|
+
* database directory; omit it for an ephemeral in-memory session. The session
|
|
17
|
+
* is cached per path — chDB allows one active data directory per process.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getChdbSession(dbPath?: string): Promise<ChdbSession>;
|
|
20
|
+
/**
|
|
21
|
+
* The type generator only needs `query({ query, format }) → { json() }` —
|
|
22
|
+
* the `TypeGenerationClickHouseClient` seam — so the embedded session can
|
|
23
|
+
* stand in for an HTTP client without touching the generator itself.
|
|
24
|
+
*/
|
|
25
|
+
export declare function getChdbTypeGenerationClient(dbPath?: string): TypeGenerationClickHouseClient;
|
|
26
|
+
/**
|
|
27
|
+
* Throws the actionable install-hint error when `chdb` is missing. Init calls
|
|
28
|
+
* this before the connection test so "package not installed" surfaces as its
|
|
29
|
+
* own message instead of a generic connection failure.
|
|
30
|
+
*/
|
|
31
|
+
export declare function ensureChdbInstalled(): Promise<void>;
|
|
32
|
+
export declare function validateChdb(dbPath?: string): Promise<boolean>;
|
|
33
|
+
export declare function getChdbTables(dbPath?: string): Promise<string[]>;
|
|
34
|
+
export declare function closeChdbSessionForTesting(): Promise<void>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=chdb-client.d.ts.map
|
|
@@ -0,0 +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"}
|