@hypequery/cli 1.22.0 → 1.24.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.
Files changed (156) hide show
  1. package/dist/bin/cli.js +77 -12
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +20 -15
  4. package/dist/commands/dev.d.ts.map +1 -1
  5. package/dist/commands/dev.js +25 -5
  6. package/dist/commands/generate-datasets.d.ts +0 -6
  7. package/dist/commands/generate-datasets.d.ts.map +1 -1
  8. package/dist/commands/generate-datasets.js +17 -1
  9. package/dist/commands/generate-manifest.d.ts.map +1 -1
  10. package/dist/commands/generate-manifest.js +4 -0
  11. package/dist/commands/generate.d.ts.map +1 -1
  12. package/dist/commands/generate.js +14 -1
  13. package/dist/commands/init.d.ts.map +1 -1
  14. package/dist/commands/init.js +80 -55
  15. package/dist/commands/mcp.d.ts.map +1 -1
  16. package/dist/commands/mcp.js +47 -10
  17. package/dist/commands/telemetry.d.ts +3 -0
  18. package/dist/commands/telemetry.d.ts.map +1 -0
  19. package/dist/commands/telemetry.js +19 -0
  20. package/dist/generators/chdb.d.ts +3 -0
  21. package/dist/generators/chdb.d.ts.map +1 -1
  22. package/dist/generators/chdb.js +2 -0
  23. package/dist/generators/clickhouse.d.ts +3 -1
  24. package/dist/generators/clickhouse.d.ts.map +1 -1
  25. package/dist/generators/clickhouse.js +2 -0
  26. package/dist/typegen/generate-types.d.ts +3 -0
  27. package/dist/typegen/generate-types.d.ts.map +1 -1
  28. package/dist/typegen/generate-types.js +5 -1
  29. package/dist/typegen/type-parsing.d.ts +1 -1
  30. package/dist/typegen/type-parsing.d.ts.map +1 -1
  31. package/dist/typegen/type-parsing.js +11 -7
  32. package/dist/utils/chdb-gitignore-entry.d.ts +2 -0
  33. package/dist/utils/chdb-gitignore-entry.d.ts.map +1 -0
  34. package/dist/utils/chdb-gitignore-entry.js +17 -0
  35. package/dist/utils/cli-version.d.ts +2 -0
  36. package/dist/utils/cli-version.d.ts.map +1 -0
  37. package/dist/utils/cli-version.js +11 -0
  38. package/dist/utils/cloud-credential-store.d.ts.map +1 -1
  39. package/dist/utils/cloud-credential-store.js +2 -13
  40. package/dist/utils/command-exit.d.ts +13 -0
  41. package/dist/utils/command-exit.d.ts.map +1 -0
  42. package/dist/utils/command-exit.js +31 -0
  43. package/dist/utils/config-directory.d.ts +3 -0
  44. package/dist/utils/config-directory.d.ts.map +1 -0
  45. package/dist/utils/config-directory.js +14 -0
  46. package/dist/utils/detect-database-environment.d.ts +3 -0
  47. package/dist/utils/detect-database-environment.d.ts.map +1 -0
  48. package/dist/utils/detect-database-environment.js +8 -0
  49. package/dist/utils/detect-database.d.ts.map +1 -1
  50. package/dist/utils/detect-database.js +4 -8
  51. package/dist/utils/init-options.d.ts +11 -0
  52. package/dist/utils/init-options.d.ts.map +1 -0
  53. package/dist/utils/init-options.js +28 -0
  54. package/dist/utils/load-api.d.ts +2 -0
  55. package/dist/utils/load-api.d.ts.map +1 -1
  56. package/dist/utils/load-api.js +50 -51
  57. package/dist/utils/load-env-files.d.ts +10 -0
  58. package/dist/utils/load-env-files.d.ts.map +1 -0
  59. package/dist/utils/load-env-files.js +27 -0
  60. package/dist/utils/project-root.d.ts +14 -0
  61. package/dist/utils/project-root.d.ts.map +1 -0
  62. package/dist/utils/project-root.js +41 -0
  63. package/dist/utils/telemetry/buckets.d.ts +5 -0
  64. package/dist/utils/telemetry/buckets.d.ts.map +1 -0
  65. package/dist/utils/telemetry/buckets.js +23 -0
  66. package/dist/utils/telemetry/catalog-docs.d.ts +3 -0
  67. package/dist/utils/telemetry/catalog-docs.d.ts.map +1 -0
  68. package/dist/utils/telemetry/catalog-docs.js +52 -0
  69. package/dist/utils/telemetry/catalog.d.ts +1124 -0
  70. package/dist/utils/telemetry/catalog.d.ts.map +1 -0
  71. package/dist/utils/telemetry/catalog.js +154 -0
  72. package/dist/utils/telemetry/command-context.d.ts +17 -0
  73. package/dist/utils/telemetry/command-context.d.ts.map +1 -0
  74. package/dist/utils/telemetry/command-context.js +30 -0
  75. package/dist/utils/telemetry/config-schema.d.ts +14 -0
  76. package/dist/utils/telemetry/config-schema.d.ts.map +1 -0
  77. package/dist/utils/telemetry/config-schema.js +35 -0
  78. package/dist/utils/telemetry/config-store.d.ts +35 -0
  79. package/dist/utils/telemetry/config-store.d.ts.map +1 -0
  80. package/dist/utils/telemetry/config-store.js +147 -0
  81. package/dist/utils/telemetry/delivery.d.ts +13 -0
  82. package/dist/utils/telemetry/delivery.d.ts.map +1 -0
  83. package/dist/utils/telemetry/delivery.js +26 -0
  84. package/dist/utils/telemetry/detached-dispatch.d.ts +12 -0
  85. package/dist/utils/telemetry/detached-dispatch.d.ts.map +1 -0
  86. package/dist/utils/telemetry/detached-dispatch.js +33 -0
  87. package/dist/utils/telemetry/detached-sender.d.ts +2 -0
  88. package/dist/utils/telemetry/detached-sender.d.ts.map +1 -0
  89. package/dist/utils/telemetry/detached-sender.js +31 -0
  90. package/dist/utils/telemetry/dev-session.d.ts +15 -0
  91. package/dist/utils/telemetry/dev-session.d.ts.map +1 -0
  92. package/dist/utils/telemetry/dev-session.js +25 -0
  93. package/dist/utils/telemetry/domains.d.ts +69 -0
  94. package/dist/utils/telemetry/domains.d.ts.map +1 -0
  95. package/dist/utils/telemetry/domains.js +64 -0
  96. package/dist/utils/telemetry/endpoint.d.ts +3 -0
  97. package/dist/utils/telemetry/endpoint.d.ts.map +1 -0
  98. package/dist/utils/telemetry/endpoint.js +15 -0
  99. package/dist/utils/telemetry/environment.d.ts +27 -0
  100. package/dist/utils/telemetry/environment.d.ts.map +1 -0
  101. package/dist/utils/telemetry/environment.js +95 -0
  102. package/dist/utils/telemetry/error-code.d.ts +4 -0
  103. package/dist/utils/telemetry/error-code.d.ts.map +1 -0
  104. package/dist/utils/telemetry/error-code.js +57 -0
  105. package/dist/utils/telemetry/flags.d.ts +5 -0
  106. package/dist/utils/telemetry/flags.d.ts.map +1 -0
  107. package/dist/utils/telemetry/flags.js +41 -0
  108. package/dist/utils/telemetry/generation-metrics.d.ts +15 -0
  109. package/dist/utils/telemetry/generation-metrics.d.ts.map +1 -0
  110. package/dist/utils/telemetry/generation-metrics.js +35 -0
  111. package/dist/utils/telemetry/identity.d.ts +12 -0
  112. package/dist/utils/telemetry/identity.d.ts.map +1 -0
  113. package/dist/utils/telemetry/identity.js +42 -0
  114. package/dist/utils/telemetry/init-funnel.d.ts +15 -0
  115. package/dist/utils/telemetry/init-funnel.d.ts.map +1 -0
  116. package/dist/utils/telemetry/init-funnel.js +25 -0
  117. package/dist/utils/telemetry/invocation.d.ts +22 -0
  118. package/dist/utils/telemetry/invocation.d.ts.map +1 -0
  119. package/dist/utils/telemetry/invocation.js +44 -0
  120. package/dist/utils/telemetry/lifecycle.d.ts +32 -0
  121. package/dist/utils/telemetry/lifecycle.d.ts.map +1 -0
  122. package/dist/utils/telemetry/lifecycle.js +99 -0
  123. package/dist/utils/telemetry/mcp-session.d.ts +10 -0
  124. package/dist/utils/telemetry/mcp-session.d.ts.map +1 -0
  125. package/dist/utils/telemetry/mcp-session.js +36 -0
  126. package/dist/utils/telemetry/notice.d.ts +11 -0
  127. package/dist/utils/telemetry/notice.d.ts.map +1 -0
  128. package/dist/utils/telemetry/notice.js +24 -0
  129. package/dist/utils/telemetry/opt-out.d.ts +16 -0
  130. package/dist/utils/telemetry/opt-out.d.ts.map +1 -0
  131. package/dist/utils/telemetry/opt-out.js +32 -0
  132. package/dist/utils/telemetry/post-json.d.ts +13 -0
  133. package/dist/utils/telemetry/post-json.d.ts.map +1 -0
  134. package/dist/utils/telemetry/post-json.js +32 -0
  135. package/dist/utils/telemetry/project-context.d.ts +15 -0
  136. package/dist/utils/telemetry/project-context.d.ts.map +1 -0
  137. package/dist/utils/telemetry/project-context.js +86 -0
  138. package/dist/utils/telemetry/schema.d.ts +72 -0
  139. package/dist/utils/telemetry/schema.d.ts.map +1 -0
  140. package/dist/utils/telemetry/schema.js +17 -0
  141. package/dist/utils/telemetry/session-config.d.ts +6 -0
  142. package/dist/utils/telemetry/session-config.d.ts.map +1 -0
  143. package/dist/utils/telemetry/session-config.js +17 -0
  144. package/dist/utils/telemetry/settings-lock.d.ts +36 -0
  145. package/dist/utils/telemetry/settings-lock.d.ts.map +1 -0
  146. package/dist/utils/telemetry/settings-lock.js +102 -0
  147. package/dist/utils/telemetry/transport.d.ts +25 -0
  148. package/dist/utils/telemetry/transport.d.ts.map +1 -0
  149. package/dist/utils/telemetry/transport.js +76 -0
  150. package/dist/utils/telemetry/validation.d.ts +10 -0
  151. package/dist/utils/telemetry/validation.d.ts.map +1 -0
  152. package/dist/utils/telemetry/validation.js +77 -0
  153. package/dist/utils/telemetry/value-formats.d.ts +30 -0
  154. package/dist/utils/telemetry/value-formats.d.ts.map +1 -0
  155. package/dist/utils/telemetry/value-formats.js +13 -0
  156. package/package.json +12 -8
@@ -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
- if (!noInteractive && !(await hasProjectPackageJson())) {
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 initCommand({ ...options, database });
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
- process.exit(0);
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
- process.exit(0);
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
- process.exit(0);
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
- // nothing to persist
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
- process.exit(1);
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":"AAAA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAQvF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAEnF,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,CAkEf"}
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"}
@@ -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
- process.exit(1);
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
- process.exit(1);
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
- process.exit(1);
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
- const source = resolveSource(await loadApi(entrypoint), entrypoint);
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
- process.exit(1);
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 ?? (() => import('@hypequery/mcp')))();
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
- async function defaultStart(config) {
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
- const { startStdioMCPServer } = await import('@hypequery/mcp');
141
- return startStdioMCPServer(config);
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,3 @@
1
+ import { type TelemetryResolutionOptions } from '../utils/telemetry/opt-out.js';
2
+ export declare function telemetryCommand(action?: string, options?: TelemetryResolutionOptions): Promise<void>;
3
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -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":"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,qBAUpE"}
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"}
@@ -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,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,qBAUhF"}
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;CAC/B;AA0HD;;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"}
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
- const tsType = clickhouseToTsType(column.type);
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,MA6CjD,CAAC"}
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"}