@hypequery/cli 1.22.0 → 1.23.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/dist/bin/cli.js +77 -12
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +20 -15
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +25 -5
- package/dist/commands/generate-datasets.d.ts +0 -6
- package/dist/commands/generate-datasets.d.ts.map +1 -1
- package/dist/commands/generate-datasets.js +17 -1
- package/dist/commands/generate-manifest.d.ts.map +1 -1
- package/dist/commands/generate-manifest.js +4 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +14 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +80 -55
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +47 -10
- package/dist/commands/telemetry.d.ts +3 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +19 -0
- package/dist/generators/chdb.d.ts +3 -0
- package/dist/generators/chdb.d.ts.map +1 -1
- package/dist/generators/chdb.js +2 -0
- package/dist/generators/clickhouse.d.ts +3 -1
- package/dist/generators/clickhouse.d.ts.map +1 -1
- package/dist/generators/clickhouse.js +2 -0
- package/dist/typegen/generate-types.d.ts +3 -0
- package/dist/typegen/generate-types.d.ts.map +1 -1
- package/dist/typegen/generate-types.js +5 -1
- package/dist/typegen/type-parsing.d.ts +1 -1
- package/dist/typegen/type-parsing.d.ts.map +1 -1
- package/dist/typegen/type-parsing.js +11 -7
- package/dist/utils/chdb-gitignore-entry.d.ts +2 -0
- package/dist/utils/chdb-gitignore-entry.d.ts.map +1 -0
- package/dist/utils/chdb-gitignore-entry.js +17 -0
- package/dist/utils/cli-version.d.ts +2 -0
- package/dist/utils/cli-version.d.ts.map +1 -0
- package/dist/utils/cli-version.js +11 -0
- package/dist/utils/cloud-credential-store.d.ts.map +1 -1
- package/dist/utils/cloud-credential-store.js +2 -13
- package/dist/utils/command-exit.d.ts +13 -0
- package/dist/utils/command-exit.d.ts.map +1 -0
- package/dist/utils/command-exit.js +31 -0
- package/dist/utils/config-directory.d.ts +3 -0
- package/dist/utils/config-directory.d.ts.map +1 -0
- package/dist/utils/config-directory.js +14 -0
- package/dist/utils/detect-database-environment.d.ts +3 -0
- package/dist/utils/detect-database-environment.d.ts.map +1 -0
- package/dist/utils/detect-database-environment.js +8 -0
- package/dist/utils/detect-database.d.ts.map +1 -1
- package/dist/utils/detect-database.js +4 -8
- package/dist/utils/init-options.d.ts +11 -0
- package/dist/utils/init-options.d.ts.map +1 -0
- package/dist/utils/init-options.js +28 -0
- package/dist/utils/load-api.d.ts +2 -0
- package/dist/utils/load-api.d.ts.map +1 -1
- package/dist/utils/load-api.js +50 -51
- package/dist/utils/load-env-files.d.ts +10 -0
- package/dist/utils/load-env-files.d.ts.map +1 -0
- package/dist/utils/load-env-files.js +27 -0
- package/dist/utils/project-root.d.ts +14 -0
- package/dist/utils/project-root.d.ts.map +1 -0
- package/dist/utils/project-root.js +41 -0
- package/dist/utils/telemetry/buckets.d.ts +5 -0
- package/dist/utils/telemetry/buckets.d.ts.map +1 -0
- package/dist/utils/telemetry/buckets.js +23 -0
- package/dist/utils/telemetry/catalog-docs.d.ts +3 -0
- package/dist/utils/telemetry/catalog-docs.d.ts.map +1 -0
- package/dist/utils/telemetry/catalog-docs.js +52 -0
- package/dist/utils/telemetry/catalog.d.ts +1124 -0
- package/dist/utils/telemetry/catalog.d.ts.map +1 -0
- package/dist/utils/telemetry/catalog.js +154 -0
- package/dist/utils/telemetry/command-context.d.ts +17 -0
- package/dist/utils/telemetry/command-context.d.ts.map +1 -0
- package/dist/utils/telemetry/command-context.js +30 -0
- package/dist/utils/telemetry/config-schema.d.ts +12 -0
- package/dist/utils/telemetry/config-schema.d.ts.map +1 -0
- package/dist/utils/telemetry/config-schema.js +33 -0
- package/dist/utils/telemetry/config-store.d.ts +35 -0
- package/dist/utils/telemetry/config-store.d.ts.map +1 -0
- package/dist/utils/telemetry/config-store.js +147 -0
- package/dist/utils/telemetry/delivery.d.ts +13 -0
- package/dist/utils/telemetry/delivery.d.ts.map +1 -0
- package/dist/utils/telemetry/delivery.js +26 -0
- package/dist/utils/telemetry/detached-dispatch.d.ts +12 -0
- package/dist/utils/telemetry/detached-dispatch.d.ts.map +1 -0
- package/dist/utils/telemetry/detached-dispatch.js +33 -0
- package/dist/utils/telemetry/detached-sender.d.ts +2 -0
- package/dist/utils/telemetry/detached-sender.d.ts.map +1 -0
- package/dist/utils/telemetry/detached-sender.js +31 -0
- package/dist/utils/telemetry/dev-session.d.ts +15 -0
- package/dist/utils/telemetry/dev-session.d.ts.map +1 -0
- package/dist/utils/telemetry/dev-session.js +25 -0
- package/dist/utils/telemetry/domains.d.ts +69 -0
- package/dist/utils/telemetry/domains.d.ts.map +1 -0
- package/dist/utils/telemetry/domains.js +64 -0
- package/dist/utils/telemetry/endpoint.d.ts +3 -0
- package/dist/utils/telemetry/endpoint.d.ts.map +1 -0
- package/dist/utils/telemetry/endpoint.js +15 -0
- package/dist/utils/telemetry/environment.d.ts +27 -0
- package/dist/utils/telemetry/environment.d.ts.map +1 -0
- package/dist/utils/telemetry/environment.js +95 -0
- package/dist/utils/telemetry/error-code.d.ts +4 -0
- package/dist/utils/telemetry/error-code.d.ts.map +1 -0
- package/dist/utils/telemetry/error-code.js +57 -0
- package/dist/utils/telemetry/flags.d.ts +5 -0
- package/dist/utils/telemetry/flags.d.ts.map +1 -0
- package/dist/utils/telemetry/flags.js +41 -0
- package/dist/utils/telemetry/generation-metrics.d.ts +15 -0
- package/dist/utils/telemetry/generation-metrics.d.ts.map +1 -0
- package/dist/utils/telemetry/generation-metrics.js +35 -0
- package/dist/utils/telemetry/identity.d.ts +12 -0
- package/dist/utils/telemetry/identity.d.ts.map +1 -0
- package/dist/utils/telemetry/identity.js +42 -0
- package/dist/utils/telemetry/init-funnel.d.ts +15 -0
- package/dist/utils/telemetry/init-funnel.d.ts.map +1 -0
- package/dist/utils/telemetry/init-funnel.js +25 -0
- package/dist/utils/telemetry/invocation.d.ts +22 -0
- package/dist/utils/telemetry/invocation.d.ts.map +1 -0
- package/dist/utils/telemetry/invocation.js +46 -0
- package/dist/utils/telemetry/lifecycle.d.ts +32 -0
- package/dist/utils/telemetry/lifecycle.d.ts.map +1 -0
- package/dist/utils/telemetry/lifecycle.js +99 -0
- package/dist/utils/telemetry/mcp-session.d.ts +10 -0
- package/dist/utils/telemetry/mcp-session.d.ts.map +1 -0
- package/dist/utils/telemetry/mcp-session.js +36 -0
- package/dist/utils/telemetry/notice.d.ts +11 -0
- package/dist/utils/telemetry/notice.d.ts.map +1 -0
- package/dist/utils/telemetry/notice.js +24 -0
- package/dist/utils/telemetry/opt-out.d.ts +16 -0
- package/dist/utils/telemetry/opt-out.d.ts.map +1 -0
- package/dist/utils/telemetry/opt-out.js +32 -0
- package/dist/utils/telemetry/post-json.d.ts +13 -0
- package/dist/utils/telemetry/post-json.d.ts.map +1 -0
- package/dist/utils/telemetry/post-json.js +32 -0
- package/dist/utils/telemetry/project-context.d.ts +15 -0
- package/dist/utils/telemetry/project-context.d.ts.map +1 -0
- package/dist/utils/telemetry/project-context.js +86 -0
- package/dist/utils/telemetry/schema.d.ts +72 -0
- package/dist/utils/telemetry/schema.d.ts.map +1 -0
- package/dist/utils/telemetry/schema.js +17 -0
- package/dist/utils/telemetry/session-config.d.ts +6 -0
- package/dist/utils/telemetry/session-config.d.ts.map +1 -0
- package/dist/utils/telemetry/session-config.js +17 -0
- package/dist/utils/telemetry/settings-lock.d.ts +36 -0
- package/dist/utils/telemetry/settings-lock.d.ts.map +1 -0
- package/dist/utils/telemetry/settings-lock.js +102 -0
- package/dist/utils/telemetry/transport.d.ts +25 -0
- package/dist/utils/telemetry/transport.d.ts.map +1 -0
- package/dist/utils/telemetry/transport.js +76 -0
- package/dist/utils/telemetry/validation.d.ts +10 -0
- package/dist/utils/telemetry/validation.d.ts.map +1 -0
- package/dist/utils/telemetry/validation.js +77 -0
- package/dist/utils/telemetry/value-formats.d.ts +30 -0
- package/dist/utils/telemetry/value-formats.d.ts.map +1 -0
- package/dist/utils/telemetry/value-formats.js +13 -0
- package/package.json +12 -8
package/dist/commands/init.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import { countBucket } from '../utils/telemetry/buckets.js';
|
|
2
|
+
import { normalizeInitDatabase, normalizeInitStyle, normalizeAuthMode, parseTableList } from '../utils/init-options.js';
|
|
3
|
+
import { getChdbGitignoreEntry } from '../utils/chdb-gitignore-entry.js';
|
|
4
|
+
import { InitFunnel } from '../utils/telemetry/init-funnel.js';
|
|
5
|
+
import { exitWith } from '../utils/command-exit.js';
|
|
6
|
+
import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
|
|
1
7
|
import { mkdir, writeFile, readFile, access } from 'node:fs/promises';
|
|
2
8
|
import path from 'node:path';
|
|
3
9
|
import ora from 'ora';
|
|
@@ -19,50 +25,6 @@ import { generateDatasets } from '../generators/dataset-generator.js';
|
|
|
19
25
|
import { installScaffoldDependencies } from '../utils/dependency-installer.js';
|
|
20
26
|
import { logDatasetGenerationWarnings } from '../utils/dataset-generation-warnings.js';
|
|
21
27
|
import { formatRegenerateDatasetsCommand } from '../utils/regenerate-datasets-command.js';
|
|
22
|
-
function normalizeInitDatabase(database) {
|
|
23
|
-
if (!database || database === 'clickhouse') {
|
|
24
|
-
return 'clickhouse';
|
|
25
|
-
}
|
|
26
|
-
if (database === 'chdb') {
|
|
27
|
-
return 'chdb';
|
|
28
|
-
}
|
|
29
|
-
throw new Error(`Unsupported database "${database}". Use "clickhouse" or "chdb".`);
|
|
30
|
-
}
|
|
31
|
-
function normalizeInitStyle(style) {
|
|
32
|
-
return style === 'datasets' ? 'datasets' : 'queries';
|
|
33
|
-
}
|
|
34
|
-
function normalizeAuthMode(auth) {
|
|
35
|
-
if (!auth || auth === 'none') {
|
|
36
|
-
return 'none';
|
|
37
|
-
}
|
|
38
|
-
if (auth === 'context') {
|
|
39
|
-
return 'context';
|
|
40
|
-
}
|
|
41
|
-
throw new Error(`Unsupported auth mode "${auth}". Use "none" or "context".`);
|
|
42
|
-
}
|
|
43
|
-
function parseTableList(value) {
|
|
44
|
-
const parsed = value
|
|
45
|
-
?.split(',')
|
|
46
|
-
.map((table) => table.trim())
|
|
47
|
-
.filter(Boolean);
|
|
48
|
-
return parsed && parsed.length > 0 ? parsed : undefined;
|
|
49
|
-
}
|
|
50
|
-
function getChdbGitignoreEntry(chdbPath) {
|
|
51
|
-
if (!chdbPath || /[\0\r\n]/.test(chdbPath)) {
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
54
|
-
const cwd = path.resolve(process.cwd());
|
|
55
|
-
const relativePath = path.relative(cwd, path.resolve(cwd, chdbPath));
|
|
56
|
-
if (relativePath.length === 0 ||
|
|
57
|
-
relativePath === '..' ||
|
|
58
|
-
relativePath.startsWith(`..${path.sep}`) ||
|
|
59
|
-
path.isAbsolute(relativePath)) {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
const normalizedPath = relativePath.split(path.sep).join('/');
|
|
63
|
-
const escapedPath = normalizedPath.replace(/[\\*?[\]]/g, '\\$&');
|
|
64
|
-
return `/${escapedPath}/`;
|
|
65
|
-
}
|
|
66
28
|
async function resolveConnectionConfig(options) {
|
|
67
29
|
if (options.noInteractive) {
|
|
68
30
|
const required = (keys) => {
|
|
@@ -117,7 +79,7 @@ async function testChdbConnection(chdbPath) {
|
|
|
117
79
|
const tableCount = await getTableCount('chdb', { chdbPath });
|
|
118
80
|
spinner.succeed(`Embedded chDB ready (${chdbPath ? `${tableCount} tables in ${chdbPath}` : 'in-memory session'})`);
|
|
119
81
|
logger.newline();
|
|
120
|
-
return { ok: true };
|
|
82
|
+
return { ok: true, tableCount };
|
|
121
83
|
}
|
|
122
84
|
async function testConnection(connectionConfig) {
|
|
123
85
|
const spinner = ora('Testing connection...').start();
|
|
@@ -146,19 +108,40 @@ async function testConnection(connectionConfig) {
|
|
|
146
108
|
return { hasValidConnection: true, tableCount };
|
|
147
109
|
}
|
|
148
110
|
export async function initCommand(options = {}) {
|
|
111
|
+
const funnel = new InitFunnel({
|
|
112
|
+
interactive: !(options.noInteractive === true || options.interactive === false),
|
|
113
|
+
force: options.force === true,
|
|
114
|
+
skip_connection: options.skipConnection === true,
|
|
115
|
+
});
|
|
116
|
+
try {
|
|
117
|
+
await runInit(options, funnel);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
funnel.fail(error);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function runInit(options, funnel) {
|
|
149
125
|
const noInteractive = options.noInteractive === true || options.interactive === false;
|
|
150
126
|
logger.newline();
|
|
151
127
|
logger.header('Welcome to hypequery!');
|
|
152
|
-
|
|
128
|
+
const packageJsonPresent = await hasProjectPackageJson();
|
|
129
|
+
funnel.update({ package_json_present: packageJsonPresent });
|
|
130
|
+
if (!noInteractive && !packageJsonPresent) {
|
|
153
131
|
logger.warn(`package.json not found in ${process.cwd()}`);
|
|
154
132
|
const shouldContinue = await confirmWithoutPackageJson(process.cwd());
|
|
155
133
|
if (!shouldContinue) {
|
|
156
134
|
logger.info('Setup cancelled. Run init from your project directory.');
|
|
135
|
+
funnel.cancel();
|
|
157
136
|
return;
|
|
158
137
|
}
|
|
159
138
|
logger.newline();
|
|
160
139
|
}
|
|
140
|
+
funnel.attempt('database_selected');
|
|
161
141
|
const database = normalizeInitDatabase(options.database ?? (noInteractive ? undefined : await promptInitDatabase()));
|
|
142
|
+
funnel.update({ database });
|
|
143
|
+
funnel.reach('database_selected');
|
|
144
|
+
funnel.attempt('connection_tested');
|
|
162
145
|
logger.info(database === 'chdb'
|
|
163
146
|
? "Let's set up your analytics layer on embedded ClickHouse (chDB)."
|
|
164
147
|
: "Let's set up your analytics layer.");
|
|
@@ -179,28 +162,31 @@ export async function initCommand(options = {}) {
|
|
|
179
162
|
connectionConfig = await resolveConnectionConfig(options);
|
|
180
163
|
// Handle user skipping connection details
|
|
181
164
|
if (!connectionConfig) {
|
|
165
|
+
funnel.update({ connection_result: 'skipped' });
|
|
182
166
|
logger.info('Skipping database connection for now.');
|
|
183
167
|
logger.newline();
|
|
184
168
|
}
|
|
185
169
|
else if (options.skipConnection) {
|
|
170
|
+
funnel.update({ connection_result: 'skipped' });
|
|
186
171
|
logger.info('Skipping database connection test (requested).');
|
|
187
172
|
logger.newline();
|
|
188
173
|
}
|
|
189
174
|
else {
|
|
190
|
-
const { hasValidConnection: valid } = await testConnection(connectionConfig);
|
|
175
|
+
const { hasValidConnection: valid, tableCount } = await testConnection(connectionConfig);
|
|
191
176
|
hasValidConnection = valid;
|
|
177
|
+
funnel.update({ connection_result: valid ? 'ok' : 'failed', ...(valid ? { table_count_bucket: countBucket(tableCount) } : {}) });
|
|
192
178
|
if (!hasValidConnection) {
|
|
193
179
|
if (noInteractive) {
|
|
194
180
|
throw new Error('Failed to connect to ClickHouse in non-interactive mode. Check your environment variables or use interactive setup.');
|
|
195
181
|
}
|
|
196
182
|
const retry = await promptRetry('Try again?');
|
|
197
183
|
if (retry) {
|
|
198
|
-
return
|
|
184
|
+
return runInit({ ...options, database }, funnel);
|
|
199
185
|
}
|
|
200
186
|
const continueWithout = await promptContinueWithoutDb();
|
|
201
187
|
if (!continueWithout) {
|
|
202
188
|
logger.info('Setup cancelled');
|
|
203
|
-
|
|
189
|
+
exitWith(0, 'cancelled');
|
|
204
190
|
}
|
|
205
191
|
logger.newline();
|
|
206
192
|
logger.info('Continuing without database connection.');
|
|
@@ -210,6 +196,9 @@ export async function initCommand(options = {}) {
|
|
|
210
196
|
}
|
|
211
197
|
}
|
|
212
198
|
}
|
|
199
|
+
if (database === 'clickhouse')
|
|
200
|
+
funnel.reach('connection_tested');
|
|
201
|
+
funnel.attempt('style_selected');
|
|
213
202
|
// Step 4: Get output directory
|
|
214
203
|
let outputDir = options.path;
|
|
215
204
|
if (!outputDir && !noInteractive) {
|
|
@@ -219,6 +208,13 @@ export async function initCommand(options = {}) {
|
|
|
219
208
|
outputDir = 'analytics';
|
|
220
209
|
}
|
|
221
210
|
const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
|
|
211
|
+
try {
|
|
212
|
+
await access(resolvedOutputDir);
|
|
213
|
+
funnel.update({ analytics_directory_present: true });
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
funnel.update({ analytics_directory_present: false });
|
|
217
|
+
}
|
|
222
218
|
let style = normalizeInitStyle(options.style);
|
|
223
219
|
if (!options.style && !noInteractive) {
|
|
224
220
|
style = await promptInitStyle();
|
|
@@ -227,6 +223,9 @@ export async function initCommand(options = {}) {
|
|
|
227
223
|
if (!options.auth && !noInteractive) {
|
|
228
224
|
auth = normalizeAuthMode(await promptInitAuthMode());
|
|
229
225
|
}
|
|
226
|
+
funnel.update({ style, auth });
|
|
227
|
+
funnel.reach('style_selected');
|
|
228
|
+
funnel.attempt('files_written');
|
|
230
229
|
// Step 5: Check for existing files
|
|
231
230
|
const filesToCreate = [
|
|
232
231
|
path.join(resolvedOutputDir, 'client.ts'),
|
|
@@ -257,21 +256,25 @@ export async function initCommand(options = {}) {
|
|
|
257
256
|
const shouldOverwrite = noInteractive ? false : await confirmOverwrite(existingFiles);
|
|
258
257
|
if (!shouldOverwrite) {
|
|
259
258
|
logger.info('Setup cancelled');
|
|
260
|
-
|
|
259
|
+
exitWith(0, 'cancelled');
|
|
261
260
|
}
|
|
262
261
|
logger.newline();
|
|
263
262
|
}
|
|
264
263
|
if (database === 'chdb') {
|
|
265
264
|
// All prompts and overwrite checks are complete, so installing packages
|
|
266
265
|
// here cannot leave a cancelled scaffold with unexpected dependencies.
|
|
266
|
+
funnel.attempt('dependencies_installed');
|
|
267
267
|
await installScaffoldDependencies(style, 'chdb');
|
|
268
|
+
funnel.attempt('connection_tested');
|
|
268
269
|
if (options.skipConnection) {
|
|
270
|
+
funnel.update({ connection_result: 'skipped' });
|
|
269
271
|
logger.info('Skipping embedded chDB test (requested).');
|
|
270
272
|
logger.newline();
|
|
271
273
|
}
|
|
272
274
|
else {
|
|
273
275
|
const chdbTest = await testChdbConnection(chdbPath);
|
|
274
276
|
hasValidConnection = chdbTest.ok;
|
|
277
|
+
funnel.update({ connection_result: chdbTest.ok ? 'ok' : 'failed', ...(chdbTest.ok ? { table_count_bucket: countBucket(chdbTest.tableCount) } : { chdb_failure_reason: chdbTest.reason === 'not-installed' ? 'not_installed' : 'engine_error' }) });
|
|
275
278
|
if (!chdbTest.ok) {
|
|
276
279
|
chdbFailureReason = chdbTest.reason;
|
|
277
280
|
}
|
|
@@ -284,7 +287,7 @@ export async function initCommand(options = {}) {
|
|
|
284
287
|
const continueWithout = await promptContinueWithoutDb();
|
|
285
288
|
if (!continueWithout) {
|
|
286
289
|
logger.info('Setup cancelled');
|
|
287
|
-
|
|
290
|
+
exitWith(0, 'cancelled');
|
|
288
291
|
}
|
|
289
292
|
logger.newline();
|
|
290
293
|
logger.info('Continuing without a working embedded engine.');
|
|
@@ -292,8 +295,11 @@ export async function initCommand(options = {}) {
|
|
|
292
295
|
}
|
|
293
296
|
}
|
|
294
297
|
}
|
|
298
|
+
if (database === 'chdb')
|
|
299
|
+
funnel.reach('connection_tested');
|
|
300
|
+
funnel.attempt('files_written');
|
|
295
301
|
// Step 6: Ask about example query (only if we have a valid connection)
|
|
296
|
-
let generateExample = !options.noExample && hasValidConnection;
|
|
302
|
+
let generateExample = !(options.noExample || options.example === false) && hasValidConnection;
|
|
297
303
|
let selectedTable = null;
|
|
298
304
|
let discoveredTables = null;
|
|
299
305
|
if (generateExample && !noInteractive && hasValidConnection) {
|
|
@@ -304,6 +310,8 @@ export async function initCommand(options = {}) {
|
|
|
304
310
|
generateExample = selectedTable !== null;
|
|
305
311
|
}
|
|
306
312
|
}
|
|
313
|
+
// Undefined when no selection was made: non-interactive runs then write a placeholder.
|
|
314
|
+
let tableSelection = options.allTables ? 'all' : options.tables ? 'list' : options.excludeTables ? 'exclude' : undefined;
|
|
307
315
|
let datasetTables = parseTableList(options.tables);
|
|
308
316
|
const excludedDatasetTables = parseTableList(options.excludeTables);
|
|
309
317
|
if (style === 'datasets' &&
|
|
@@ -312,16 +320,18 @@ export async function initCommand(options = {}) {
|
|
|
312
320
|
!datasetTables &&
|
|
313
321
|
!noInteractive) {
|
|
314
322
|
discoveredTables ??= await getTables(database, { chdbPath });
|
|
323
|
+
tableSelection = 'prompt';
|
|
315
324
|
datasetTables = await promptDatasetTableSelection(discoveredTables, selectedTable ? [selectedTable] : []);
|
|
316
325
|
}
|
|
317
326
|
logger.newline();
|
|
327
|
+
funnel.update({ example: generateExample, ...(style === 'datasets' && tableSelection ? { table_selection: tableSelection } : {}) });
|
|
318
328
|
// Step 7: Create directory
|
|
319
329
|
await mkdir(resolvedOutputDir, { recursive: true });
|
|
320
330
|
// Step 8: Save credentials to .env (if we have connection config).
|
|
321
331
|
// Embedded chDB has no credentials — the storage path lives in client.ts —
|
|
322
332
|
// so the chdb scaffold writes no .env at all.
|
|
323
333
|
if (database === 'chdb') {
|
|
324
|
-
|
|
334
|
+
funnel.update({ env_file: 'skipped' });
|
|
325
335
|
}
|
|
326
336
|
else if (connectionConfig) {
|
|
327
337
|
const envPath = path.join(process.cwd(), '.env');
|
|
@@ -330,10 +340,12 @@ export async function initCommand(options = {}) {
|
|
|
330
340
|
const existingEnv = await readFile(envPath, 'utf-8');
|
|
331
341
|
const newEnv = appendToEnv(existingEnv, generateEnvTemplate(connectionConfig));
|
|
332
342
|
await writeFile(envPath, newEnv);
|
|
343
|
+
funnel.update({ env_file: newEnv === existingEnv ? 'unchanged' : 'updated' });
|
|
333
344
|
logger.success('Updated .env');
|
|
334
345
|
}
|
|
335
346
|
else {
|
|
336
347
|
await writeFile(envPath, generateEnvTemplate(connectionConfig));
|
|
348
|
+
funnel.update({ env_file: 'created' });
|
|
337
349
|
logger.success('Created .env');
|
|
338
350
|
}
|
|
339
351
|
}
|
|
@@ -347,8 +359,10 @@ export async function initCommand(options = {}) {
|
|
|
347
359
|
username: 'YOUR_USERNAME',
|
|
348
360
|
password: 'YOUR_PASSWORD',
|
|
349
361
|
};
|
|
362
|
+
funnel.update({ env_file: 'unchanged' });
|
|
350
363
|
if (!envExists) {
|
|
351
364
|
await writeFile(envPath, generateEnvTemplate(placeholderConfig));
|
|
365
|
+
funnel.update({ env_file: 'created' });
|
|
352
366
|
logger.success('Created .env (configure your credentials)');
|
|
353
367
|
}
|
|
354
368
|
}
|
|
@@ -364,7 +378,7 @@ export async function initCommand(options = {}) {
|
|
|
364
378
|
catch (error) {
|
|
365
379
|
typeSpinner.fail('Failed to generate types');
|
|
366
380
|
logger.error(error instanceof Error ? error.message : String(error));
|
|
367
|
-
|
|
381
|
+
exitWith(1, 'failure', telemetryErrorCode(error));
|
|
368
382
|
}
|
|
369
383
|
}
|
|
370
384
|
else {
|
|
@@ -406,6 +420,8 @@ export interface IntrospectedSchema {
|
|
|
406
420
|
? { client: getChdbTypeGenerationClient(chdbPath) }
|
|
407
421
|
: {}),
|
|
408
422
|
});
|
|
423
|
+
if (generated)
|
|
424
|
+
funnel.update({ datasets_generated_bucket: countBucket(generated.tables.length) });
|
|
409
425
|
logDatasetGenerationWarnings(generated?.warnings);
|
|
410
426
|
generatedAnyDatasets = true;
|
|
411
427
|
generatedSelectedDataset = selectedTable !== null && (options.allTables === true ||
|
|
@@ -413,6 +429,7 @@ export interface IntrospectedSchema {
|
|
|
413
429
|
}
|
|
414
430
|
else {
|
|
415
431
|
await writeFile(datasetsPath, generateDatasetsPlaceholderTemplate({ auth }));
|
|
432
|
+
funnel.update({ datasets_generated_bucket: '0' });
|
|
416
433
|
if (hasValidConnection) {
|
|
417
434
|
logger.info('Skipped dataset generation. Run `'
|
|
418
435
|
+ formatRegenerateDatasetsCommand({ outputDir, auth, tables: 'table1,table2' })
|
|
@@ -443,23 +460,30 @@ export interface IntrospectedSchema {
|
|
|
443
460
|
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
444
461
|
const gitignoreExists = await hasGitignore();
|
|
445
462
|
const chdbGitignoreEntry = database === 'chdb'
|
|
446
|
-
? getChdbGitignoreEntry(chdbPath)
|
|
463
|
+
? getChdbGitignoreEntry(chdbPath, process.cwd())
|
|
447
464
|
: undefined;
|
|
448
465
|
const gitignoreEntries = chdbGitignoreEntry ? [chdbGitignoreEntry] : [];
|
|
449
466
|
if (gitignoreExists) {
|
|
450
467
|
const existingGitignore = await readFile(gitignorePath, 'utf-8');
|
|
451
468
|
const newGitignore = appendToGitignore(existingGitignore, gitignoreEntries);
|
|
469
|
+
funnel.update({ gitignore: 'unchanged' });
|
|
452
470
|
if (newGitignore !== existingGitignore) {
|
|
453
471
|
await writeFile(gitignorePath, newGitignore);
|
|
472
|
+
funnel.update({ gitignore: 'updated' });
|
|
454
473
|
logger.success('Updated .gitignore');
|
|
455
474
|
}
|
|
456
475
|
}
|
|
457
476
|
else {
|
|
458
477
|
await writeFile(gitignorePath, appendToGitignore('', gitignoreEntries));
|
|
478
|
+
funnel.update({ gitignore: 'created' });
|
|
459
479
|
logger.success('Created .gitignore');
|
|
460
480
|
}
|
|
481
|
+
funnel.reach('files_written');
|
|
482
|
+
funnel.attempt('dependencies_installed');
|
|
461
483
|
// Step 13: Ensure required hypequery packages are installed
|
|
462
484
|
await installScaffoldDependencies(style, database);
|
|
485
|
+
funnel.reach('dependencies_installed');
|
|
486
|
+
funnel.attempt('completed');
|
|
463
487
|
// Step 14: Success message
|
|
464
488
|
logger.newline();
|
|
465
489
|
logger.header('Setup complete!');
|
|
@@ -525,6 +549,7 @@ export interface IntrospectedSchema {
|
|
|
525
549
|
: 'In-memory chDB is process-local; use --chdb-path <dir> for later type generation');
|
|
526
550
|
logger.newline();
|
|
527
551
|
}
|
|
552
|
+
funnel.reach('completed');
|
|
528
553
|
logger.info('Docs: https://hypequery.com/docs');
|
|
529
554
|
logger.newline();
|
|
530
555
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AASvF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAMnF,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AAoED,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CA0Ef"}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { exitWith } from '../utils/command-exit.js';
|
|
1
2
|
import { runMcpUntilSignal } from '../utils/mcp-lifecycle.js';
|
|
2
3
|
import { routeConsoleOutputToStderr } from '../utils/mcp-console.js';
|
|
3
4
|
import { readServeMcpSource, tenantScopedDatasets } from '../utils/mcp-source.js';
|
|
4
5
|
import path from 'node:path';
|
|
5
6
|
import { findApiFileForPath, findQueriesFile } from '../utils/find-files.js';
|
|
6
7
|
import { loadApiModule } from '../utils/load-api.js';
|
|
8
|
+
import { importFromProject } from '../utils/project-root.js';
|
|
7
9
|
import { logger } from '../utils/logger.js';
|
|
8
10
|
import { MCP_API_KEY_VARIABLE } from '../utils/hosted-endpoints.js';
|
|
9
11
|
import { checkRemoteMcp } from '../utils/mcp-remote-check.js';
|
|
12
|
+
import { McpSession } from '../utils/telemetry/mcp-session.js';
|
|
13
|
+
import { countBucket } from '../utils/telemetry/buckets.js';
|
|
14
|
+
import { entryType } from '../utils/telemetry/session-config.js';
|
|
15
|
+
import { updateCommandTelemetry, setCommandTelemetryError } from '../utils/telemetry/command-context.js';
|
|
10
16
|
function fail(message, hints = []) {
|
|
11
17
|
logger.error(message);
|
|
12
18
|
if (hints.length > 0) {
|
|
@@ -15,7 +21,7 @@ function fail(message, hints = []) {
|
|
|
15
21
|
logger.indent(hint);
|
|
16
22
|
}
|
|
17
23
|
logger.newline();
|
|
18
|
-
|
|
24
|
+
exitWith(1, 'failure', 'validation_failed');
|
|
19
25
|
}
|
|
20
26
|
/** `--self-test --url`: initialize and list tools against a hosted endpoint. */
|
|
21
27
|
async function remoteSelfTest(url, dependencies) {
|
|
@@ -48,7 +54,7 @@ function entrypointNotFound() {
|
|
|
48
54
|
logger.info('Or specify the file explicitly:');
|
|
49
55
|
logger.indent('hypequery mcp ./path/to/api.ts');
|
|
50
56
|
logger.newline();
|
|
51
|
-
|
|
57
|
+
exitWith(1, 'failure', 'entrypoint_not_found');
|
|
52
58
|
}
|
|
53
59
|
function noDatasets(file) {
|
|
54
60
|
logger.error(`${path.relative(process.cwd(), file)} does not register any datasets`);
|
|
@@ -56,7 +62,7 @@ function noDatasets(file) {
|
|
|
56
62
|
logger.info('MCP exposes datasets and named metrics, so add them to defineServe:');
|
|
57
63
|
logger.indent('defineServe({ queryBuilder: db, datasets: { orders: Orders } })');
|
|
58
64
|
logger.newline();
|
|
59
|
-
|
|
65
|
+
exitWith(1, 'failure', 'no_datasets');
|
|
60
66
|
}
|
|
61
67
|
async function resolveEntrypoint(file, options) {
|
|
62
68
|
const resolved = file
|
|
@@ -70,6 +76,8 @@ function resolveSource(api, file) {
|
|
|
70
76
|
return readServeMcpSource(api) ?? noDatasets(file);
|
|
71
77
|
}
|
|
72
78
|
export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
79
|
+
const telemetry = new McpSession();
|
|
80
|
+
updateCommandTelemetry('mcp', { mode: options.selfTest ? 'self_test' : 'serve', hosted: !!options.url, tenant_used: !!options.tenant });
|
|
73
81
|
if (options.url && !options.selfTest) {
|
|
74
82
|
fail('--url is only used with --self-test.', [
|
|
75
83
|
'MCP clients connect to the hosted endpoint directly; this command serves a local project.',
|
|
@@ -83,12 +91,21 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
|
83
91
|
}
|
|
84
92
|
const entrypoint = await resolveEntrypoint(file, options);
|
|
85
93
|
const loadApi = dependencies.loadApi ?? loadApiModule;
|
|
86
|
-
|
|
94
|
+
let api;
|
|
95
|
+
try {
|
|
96
|
+
api = await loadApi(entrypoint);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
setCommandTelemetryError('load_error');
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
const source = resolveSource(api, entrypoint);
|
|
87
103
|
const datasets = source.datasets;
|
|
88
104
|
const names = Object.keys(datasets).sort();
|
|
89
105
|
if (names.length === 0)
|
|
90
106
|
noDatasets(entrypoint);
|
|
91
107
|
const scoped = tenantScopedDatasets(datasets);
|
|
108
|
+
updateCommandTelemetry('mcp', { dataset_count_bucket: countBucket(names.length), tenant_dataset_count_bucket: countBucket(scoped.length) });
|
|
92
109
|
if (scoped.length > 0 && !options.tenant) {
|
|
93
110
|
// Fail closed rather than serving a tenant-scoped dataset unscoped.
|
|
94
111
|
logger.error(`--tenant is required for tenant-scoped datasets: ${scoped.join(', ')}`);
|
|
@@ -96,14 +113,14 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
|
96
113
|
logger.info('MCP has no request to resolve a tenant from, so it must be given one:');
|
|
97
114
|
logger.indent('hypequery mcp --tenant acme');
|
|
98
115
|
logger.newline();
|
|
99
|
-
|
|
116
|
+
exitWith(1, 'failure', 'tenant_required');
|
|
100
117
|
}
|
|
101
118
|
const analytics = source.resolveAnalytics();
|
|
102
119
|
if (options.selfTest) {
|
|
103
120
|
// Serving needs the optional `@hypequery/mcp` peer. Without this check
|
|
104
121
|
// the self-test passed and the real command then failed on import.
|
|
105
122
|
try {
|
|
106
|
-
await (dependencies.loadMcp ?? (() =>
|
|
123
|
+
await (dependencies.loadMcp ?? (() => loadMcpPackage(entrypoint)))();
|
|
107
124
|
}
|
|
108
125
|
catch {
|
|
109
126
|
fail('@hypequery/mcp is not installed, so this project cannot be served over MCP.', [
|
|
@@ -119,13 +136,15 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
|
119
136
|
logger.info('The entrypoint is ready to serve over MCP.');
|
|
120
137
|
return;
|
|
121
138
|
}
|
|
122
|
-
const start = dependencies.start ?? defaultStart;
|
|
139
|
+
const start = dependencies.start ?? ((config) => defaultStart(entrypoint, config, telemetry));
|
|
123
140
|
await runMcpUntilSignal(async () => {
|
|
124
141
|
const server = await start({
|
|
125
142
|
datasets,
|
|
126
143
|
analytics,
|
|
127
144
|
...(options.tenant ? { tenantId: options.tenant } : {}),
|
|
128
145
|
});
|
|
146
|
+
telemetry.start({ entry_type: entryType(entrypoint, file), mode: 'serve', dataset_count_bucket: countBucket(names.length),
|
|
147
|
+
tenant_used: !!options.tenant, tenant_dataset_count_bucket: countBucket(scoped.length) });
|
|
129
148
|
process.stderr.write(`hypequery MCP serving ${names.length} dataset(s): ${names.join(', ')}\n`);
|
|
130
149
|
return server;
|
|
131
150
|
});
|
|
@@ -134,9 +153,27 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
|
134
153
|
restoreConsole();
|
|
135
154
|
}
|
|
136
155
|
}
|
|
137
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Prefers the project's own `@hypequery/mcp`, so `npx -y @hypequery/cli mcp`
|
|
158
|
+
* run from another directory still finds the optional peer.
|
|
159
|
+
*/
|
|
160
|
+
function loadMcpPackage(entrypoint) {
|
|
161
|
+
return importFromProject('@hypequery/mcp', entrypoint, () => import('@hypequery/mcp'));
|
|
162
|
+
}
|
|
163
|
+
async function defaultStart(entrypoint, config, telemetry) {
|
|
138
164
|
// Imported lazily so `--self-test` and the argument errors above do not pay
|
|
139
165
|
// for the MCP SDK, and so the CLI still loads when it is not installed.
|
|
140
|
-
|
|
141
|
-
|
|
166
|
+
// The project's own copy, so a client launching from another directory
|
|
167
|
+
// still finds the optional peer; the executor stays telemetry-instrumented.
|
|
168
|
+
const { createMCPExecutor, createMCPProtocolServer, connectMCPServerStdio } = await loadMcpPackage(entrypoint);
|
|
169
|
+
const executor = createMCPExecutor(config);
|
|
170
|
+
const server = createMCPProtocolServer({ executor: telemetry.instrument(executor) });
|
|
171
|
+
try {
|
|
172
|
+
await connectMCPServerStdio(server);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
await server.close().catch(() => undefined);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
return server;
|
|
142
179
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/commands/telemetry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAyB,KAAK,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAEvG,wBAAsB,gBAAgB,CACpC,MAAM,SAAW,EACjB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,IAAI,CAAC,CAef"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { TelemetryConfigStore } from '../utils/telemetry/config-store.js';
|
|
2
|
+
import { TELEMETRY_DOCS_URL } from '../utils/telemetry/config-schema.js';
|
|
3
|
+
import { resolveTelemetryState } from '../utils/telemetry/opt-out.js';
|
|
4
|
+
export async function telemetryCommand(action = 'status', options = {}) {
|
|
5
|
+
if (!['status', 'enable', 'disable'].includes(action)) {
|
|
6
|
+
throw new Error('Usage: hypequery telemetry [status|enable|disable]');
|
|
7
|
+
}
|
|
8
|
+
if (action !== 'status') {
|
|
9
|
+
const config = await new TelemetryConfigStore(options).setEnabled(action === 'enable');
|
|
10
|
+
if (!config) {
|
|
11
|
+
throw new Error('Could not save telemetry settings. To disable for this run, set HYPEQUERY_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1, or pass --no-telemetry.');
|
|
12
|
+
}
|
|
13
|
+
console.log(`Telemetry ${config.enabled ? 'enabled' : 'disabled'} in saved settings.`);
|
|
14
|
+
}
|
|
15
|
+
const state = await resolveTelemetryState(options);
|
|
16
|
+
console.log(`Telemetry: ${state.enabled ? 'enabled' : 'disabled'} (source: ${state.source})`);
|
|
17
|
+
console.log(`Install ID: ${state.config?.install_id ?? 'unavailable'}`);
|
|
18
|
+
console.log(`Learn more: ${TELEMETRY_DOCS_URL}`);
|
|
19
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { type GenerateTypesOptions } from '../typegen/index.js';
|
|
1
2
|
export interface ChdbGeneratorOptions {
|
|
2
3
|
outputPath: string;
|
|
3
4
|
includeTables?: string[];
|
|
4
5
|
excludeTables?: string[];
|
|
5
6
|
chdbPath?: string;
|
|
7
|
+
onColumn?: GenerateTypesOptions['onColumn'];
|
|
8
|
+
onUnsupportedType?: GenerateTypesOptions['onUnsupportedType'];
|
|
6
9
|
}
|
|
7
10
|
/**
|
|
8
11
|
* Same generator as ClickHouse — the introspection SQL (SHOW TABLES,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chdb.d.ts","sourceRoot":"","sources":["../../src/generators/chdb.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chdb.d.ts","sourceRoot":"","sources":["../../src/generators/chdb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAG/E,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;IAClB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;CAC/D;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,oBAAoB,qBAYpE"}
|
package/dist/generators/chdb.js
CHANGED
|
@@ -10,6 +10,8 @@ export async function generateChdbTypes(options) {
|
|
|
10
10
|
client: getChdbTypeGenerationClient(options.chdbPath),
|
|
11
11
|
generatedBy: 'hypequery',
|
|
12
12
|
includeUsageExample: false,
|
|
13
|
+
...(options.onColumn ? { onColumn: options.onColumn } : {}),
|
|
14
|
+
...(options.onUnsupportedType ? { onUnsupportedType: options.onUnsupportedType } : {}),
|
|
13
15
|
...(options.includeTables ? { includeTables: options.includeTables } : {}),
|
|
14
16
|
...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
|
|
15
17
|
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { clickhouseToTsType } from '../typegen/index.js';
|
|
1
|
+
import { clickhouseToTsType, type GenerateTypesOptions } from '../typegen/index.js';
|
|
2
2
|
export interface ClickHouseGeneratorOptions {
|
|
3
3
|
outputPath: string;
|
|
4
4
|
includeTables?: string[];
|
|
5
5
|
excludeTables?: string[];
|
|
6
|
+
onColumn?: GenerateTypesOptions['onColumn'];
|
|
7
|
+
onUnsupportedType?: GenerateTypesOptions['onUnsupportedType'];
|
|
6
8
|
}
|
|
7
9
|
export { clickhouseToTsType };
|
|
8
10
|
export declare function generateClickHouseTypes(options: ClickHouseGeneratorOptions): Promise<string[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,
|
|
1
|
+
{"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAElB,KAAK,oBAAoB,EAC1B,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;IACzB,QAAQ,CAAC,EAAE,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC5C,iBAAiB,CAAC,EAAE,oBAAoB,CAAC,mBAAmB,CAAC,CAAC;CAC/D;AAED,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAE9B,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,0BAA0B,qBAYhF"}
|
|
@@ -6,6 +6,8 @@ export async function generateClickHouseTypes(options) {
|
|
|
6
6
|
client: getClickHouseClient(),
|
|
7
7
|
generatedBy: 'hypequery',
|
|
8
8
|
includeUsageExample: false,
|
|
9
|
+
...(options.onColumn ? { onColumn: options.onColumn } : {}),
|
|
10
|
+
...(options.onUnsupportedType ? { onUnsupportedType: options.onUnsupportedType } : {}),
|
|
9
11
|
...(options.includeTables ? { includeTables: options.includeTables } : {}),
|
|
10
12
|
...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
|
|
11
13
|
};
|
|
@@ -33,6 +33,9 @@ export interface GenerateTypesOptions {
|
|
|
33
33
|
generatedBy?: string;
|
|
34
34
|
/** Append the historical usage-example comment block. */
|
|
35
35
|
includeUsageExample?: boolean;
|
|
36
|
+
/** Optional aggregate-observation hooks; errors never affect generation. */
|
|
37
|
+
onColumn?: () => void;
|
|
38
|
+
onUnsupportedType?: (type: string) => void;
|
|
36
39
|
}
|
|
37
40
|
/**
|
|
38
41
|
* Generates the TypeScript type definition source from a ClickHouse client.
|
|
@@ -1 +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;
|
|
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;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5C;AA2HD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,8BAA8B,EACtC,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,MAAM,CAAC,CAGjB;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,EAAE,CAAC,CAYnB"}
|
|
@@ -72,7 +72,11 @@ export interface IntrospectedSchema {`;
|
|
|
72
72
|
const columns = await describeTable(client, table.name);
|
|
73
73
|
typeDefinitions += `export interface ${capitalizeFirstLetter(table.name)}Record {`;
|
|
74
74
|
for (const column of columns) {
|
|
75
|
-
|
|
75
|
+
try {
|
|
76
|
+
options.onColumn?.();
|
|
77
|
+
}
|
|
78
|
+
catch { /* Preserve type output. */ }
|
|
79
|
+
const tsType = clickhouseToTsType(column.type, options.onUnsupportedType);
|
|
76
80
|
typeDefinitions += `\n '${column.name}': ${tsType};`;
|
|
77
81
|
}
|
|
78
82
|
typeDefinitions += '\n}\n\n';
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* Converts a ClickHouse type string, as returned by `DESCRIBE TABLE`, into the
|
|
3
3
|
* equivalent TypeScript type string.
|
|
4
4
|
*/
|
|
5
|
-
export declare const clickhouseToTsType: (type: string) => string;
|
|
5
|
+
export declare const clickhouseToTsType: (type: string, onUnsupported?: (type: string) => void) => string;
|
|
6
6
|
//# sourceMappingURL=type-parsing.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-parsing.d.ts","sourceRoot":"","sources":["../../src/typegen/type-parsing.ts"],"names":[],"mappings":"AAwLA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,
|
|
1
|
+
{"version":3,"file":"type-parsing.d.ts","sourceRoot":"","sources":["../../src/typegen/type-parsing.ts"],"names":[],"mappings":"AAwLA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,EAAE,gBAAgB,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,KAAG,MA8CzF,CAAC"}
|