@hypequery/cli 1.1.2 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +26 -2
  2. package/dist/bin/cli.js +21 -81
  3. package/dist/cli.d.ts.map +1 -1
  4. package/dist/cli.js +72 -116
  5. package/dist/commands/dev.d.ts +1 -0
  6. package/dist/commands/dev.d.ts.map +1 -1
  7. package/dist/commands/dev.js +180 -255
  8. package/dist/commands/generate-datasets.d.ts +14 -0
  9. package/dist/commands/generate-datasets.d.ts.map +1 -0
  10. package/dist/commands/generate-datasets.js +96 -0
  11. package/dist/commands/generate.d.ts +2 -0
  12. package/dist/commands/generate.d.ts.map +1 -1
  13. package/dist/commands/generate.js +112 -167
  14. package/dist/commands/init.d.ts +7 -0
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +337 -370
  17. package/dist/generators/clickhouse.d.ts +1 -1
  18. package/dist/generators/clickhouse.d.ts.map +1 -1
  19. package/dist/generators/clickhouse.js +10 -269
  20. package/dist/generators/dataset-generator.d.ts +16 -0
  21. package/dist/generators/dataset-generator.d.ts.map +1 -0
  22. package/dist/generators/dataset-generator.js +246 -0
  23. package/dist/generators/index.js +3 -3
  24. package/dist/templates/api.d.ts +9 -0
  25. package/dist/templates/api.d.ts.map +1 -0
  26. package/dist/templates/api.js +26 -0
  27. package/dist/templates/auth-scaffold.d.ts +17 -0
  28. package/dist/templates/auth-scaffold.d.ts.map +1 -0
  29. package/dist/templates/auth-scaffold.js +41 -0
  30. package/dist/templates/client.js +10 -1
  31. package/dist/templates/datasets.d.ts +5 -0
  32. package/dist/templates/datasets.d.ts.map +1 -0
  33. package/dist/templates/datasets.js +23 -0
  34. package/dist/templates/env.js +19 -8
  35. package/dist/templates/gitignore.js +4 -1
  36. package/dist/templates/queries.d.ts +3 -0
  37. package/dist/templates/queries.d.ts.map +1 -1
  38. package/dist/templates/queries.js +66 -10
  39. package/dist/test-utils.d.ts +0 -71
  40. package/dist/test-utils.d.ts.map +1 -1
  41. package/dist/test-utils.js +12 -137
  42. package/dist/utils/clickhouse-client.js +10 -57
  43. package/dist/utils/clickhouse-sql.d.ts +6 -0
  44. package/dist/utils/clickhouse-sql.d.ts.map +1 -0
  45. package/dist/utils/clickhouse-sql.js +18 -0
  46. package/dist/utils/clickhouse-type-utils.d.ts +6 -0
  47. package/dist/utils/clickhouse-type-utils.d.ts.map +1 -0
  48. package/dist/utils/clickhouse-type-utils.js +59 -0
  49. package/dist/utils/dependency-installer.d.ts +4 -2
  50. package/dist/utils/dependency-installer.d.ts.map +1 -1
  51. package/dist/utils/dependency-installer.js +93 -170
  52. package/dist/utils/detect-database.js +84 -195
  53. package/dist/utils/find-files.d.ts +3 -1
  54. package/dist/utils/find-files.d.ts.map +1 -1
  55. package/dist/utils/find-files.js +82 -148
  56. package/dist/utils/load-api.d.ts.map +1 -1
  57. package/dist/utils/load-api.js +207 -396
  58. package/dist/utils/logger.js +38 -42
  59. package/dist/utils/prompts.d.ts +14 -0
  60. package/dist/utils/prompts.d.ts.map +1 -1
  61. package/dist/utils/prompts.js +170 -219
  62. package/dist/utils/runtime-guards.d.ts +4 -0
  63. package/dist/utils/runtime-guards.d.ts.map +1 -0
  64. package/dist/utils/runtime-guards.js +9 -0
  65. package/dist/utils/sha256.d.ts +2 -0
  66. package/dist/utils/sha256.d.ts.map +1 -0
  67. package/dist/utils/sha256.js +4 -0
  68. package/package.json +3 -8
  69. package/dist/utils/error-messages.d.ts +0 -6
  70. package/dist/utils/error-messages.d.ts.map +0 -1
  71. package/dist/utils/error-messages.js +0 -19
  72. package/dist/utils/load-hypequery-config.d.ts +0 -7
  73. package/dist/utils/load-hypequery-config.d.ts.map +0 -1
  74. package/dist/utils/load-hypequery-config.js +0 -89
@@ -1,265 +1,190 @@
1
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
- return new (P || (P = Promise))(function (resolve, reject) {
4
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
- step((generator = generator.apply(thisArg, _arguments || [])).next());
8
- });
9
- };
10
- var __generator = (this && this.__generator) || function (thisArg, body) {
11
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
- return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
- function verb(n) { return function (v) { return step([n, v]); }; }
14
- function step(op) {
15
- if (f) throw new TypeError("Generator is already executing.");
16
- while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
- if (y = 0, t) op = [op[0] & 2, t.value];
19
- switch (op[0]) {
20
- case 0: case 1: t = op; break;
21
- case 4: _.label++; return { value: op[1], done: false };
22
- case 5: _.label++; y = op[1]; op = [0]; continue;
23
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
- default:
25
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
- if (t[2]) _.ops.pop();
30
- _.trys.pop(); continue;
31
- }
32
- op = body.call(thisArg, _);
33
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
- }
36
- };
37
1
  import { watch } from 'node:fs';
38
2
  import path from 'node:path';
39
3
  import ora from 'ora';
40
4
  import { logger } from '../utils/logger.js';
41
- import { findQueriesFile } from '../utils/find-files.js';
5
+ import { findApiFileForPath, findQueriesFile } from '../utils/find-files.js';
42
6
  import { getTableCount } from '../utils/detect-database.js';
43
7
  import { loadApiModule } from '../utils/load-api.js';
44
- import { displayQueriesFileNotFoundError } from '../utils/error-messages.js';
45
- export function devCommand(file_1) {
46
- return __awaiter(this, arguments, void 0, function (file, options) {
47
- var queriesFile, currentServer, shouldWatch, startServer, restartServer, shutdown, watchDir, debounceTimer_1, watcher_1;
48
- var _this = this;
49
- if (options === void 0) { options = {}; }
50
- return __generator(this, function (_a) {
51
- switch (_a.label) {
52
- case 0: return [4 /*yield*/, findQueriesFile(file)];
53
- case 1:
54
- queriesFile = _a.sent();
55
- if (!queriesFile) {
56
- displayQueriesFileNotFoundError('dev');
57
- process.exit(1);
58
- }
59
- logger.info("Found: ".concat(path.relative(process.cwd(), queriesFile)));
8
+ /**
9
+ * Display error when queries file cannot be found
10
+ */
11
+ function displayQueriesFileNotFoundError(commandName) {
12
+ logger.error('Could not find hypequery API file');
13
+ logger.newline();
14
+ logger.info('Expected one of:');
15
+ logger.indent('• hypequery.ts');
16
+ logger.indent('• analytics/api.ts');
17
+ logger.indent('• src/analytics/api.ts');
18
+ logger.indent('• api.ts');
19
+ logger.indent('• src/api.ts');
20
+ logger.indent('• analytics/queries.ts');
21
+ logger.indent('• src/analytics/queries.ts');
22
+ logger.newline();
23
+ logger.info("Did you run 'hypequery init'?");
24
+ logger.newline();
25
+ logger.info('Or specify the file explicitly:');
26
+ logger.indent(`hypequery ${commandName} ./path/to/api.ts`);
27
+ logger.newline();
28
+ }
29
+ export async function devCommand(file, options = {}) {
30
+ // Step 1: Find queries file
31
+ const queriesFile = file
32
+ ? await findQueriesFile(file)
33
+ : options.path
34
+ ? await findApiFileForPath(options.path)
35
+ : await findQueriesFile();
36
+ if (!queriesFile) {
37
+ displayQueriesFileNotFoundError('dev');
38
+ process.exit(1);
39
+ }
40
+ logger.info(`Found: ${path.relative(process.cwd(), queriesFile)}`);
41
+ logger.newline();
42
+ let currentServer = null;
43
+ const shouldWatch = options.watch !== false; // Default to true
44
+ const startServer = async () => {
45
+ // Loading spinners for slow operations
46
+ const compileSpinner = ora('Compiling queries...').start();
47
+ const dbSpinner = ora('Connecting to ClickHouse...').start();
48
+ try {
49
+ // Load the API module
50
+ const api = await loadApiModule(queriesFile);
51
+ compileSpinner.succeed('Compiled queries');
52
+ // Get table count for display
53
+ let tableCount = 0;
54
+ try {
55
+ tableCount = await getTableCount('clickhouse');
56
+ dbSpinner.succeed(`Connected to ClickHouse (${tableCount} tables)`);
57
+ }
58
+ catch (error) {
59
+ // Log but don't fail - table count is optional
60
+ dbSpinner.warn('Could not connect to ClickHouse');
61
+ if (error instanceof Error) {
62
+ logger.indent(`Reason: ${error.message}`);
63
+ }
64
+ }
65
+ // Count queries
66
+ const queryCount = Object.keys(api.queries || {}).length;
67
+ logger.header('hypequery dev');
68
+ logger.success(`Registered ${queryCount} ${queryCount === 1 ? 'query' : 'queries'}`);
69
+ logger.newline();
70
+ // Start the server
71
+ const { serveDev } = await import('@hypequery/serve');
72
+ currentServer = await serveDev(api, {
73
+ port: options.port,
74
+ hostname: options.hostname,
75
+ quiet: true,
76
+ });
77
+ const address = currentServer.server.address();
78
+ const port = typeof address === 'object' && address ? address.port : options.port || 4000;
79
+ const hostname = options.hostname || 'localhost';
80
+ const baseUrl = `http://${hostname}:${port}`;
81
+ const docsPath = api.docs?.path ?? '/docs';
82
+ const openapiPath = api.openapi?.path ?? '/openapi.json';
83
+ logger.box([
84
+ `Docs: ${baseUrl}${api.basePath ?? ''}${docsPath}`,
85
+ `OpenAPI: ${baseUrl}${api.basePath ?? ''}${openapiPath}`,
86
+ ]);
87
+ logger.newline();
88
+ logger.success(`Ready in ${process.uptime().toFixed(0)}ms`);
89
+ logger.newline();
90
+ // Query execution stats are logged automatically
91
+ if (!options.quiet) {
92
+ logger.info('Query execution stats will appear below as requests are made');
93
+ logger.newline();
94
+ }
95
+ if (shouldWatch) {
96
+ logger.info('Watching for changes...');
97
+ }
98
+ // Open browser if requested
99
+ if (options.open) {
100
+ try {
101
+ const open = (await import('open')).default;
102
+ await open(baseUrl);
103
+ logger.success(`Opened ${baseUrl} in browser`);
104
+ }
105
+ catch {
106
+ // Log but don't fail - browser open is optional
107
+ logger.warn('Could not open browser automatically');
108
+ logger.indent(`Visit: ${baseUrl}`);
109
+ }
110
+ }
111
+ }
112
+ catch (error) {
113
+ // Stop spinners if they're still running
114
+ if (compileSpinner.isSpinning) {
115
+ compileSpinner.fail('Failed to compile queries');
116
+ }
117
+ if (dbSpinner.isSpinning) {
118
+ dbSpinner.stop();
119
+ }
120
+ logger.error('Failed to start server');
121
+ logger.newline();
122
+ if (error instanceof Error) {
123
+ logger.info(error.message);
124
+ if (error.stack) {
60
125
  logger.newline();
61
- currentServer = null;
62
- shouldWatch = options.watch !== false;
63
- startServer = function () { return __awaiter(_this, void 0, void 0, function () {
64
- var compileSpinner, dbSpinner, api, tableCount, error_1, queryCount, serveDev, address, port, hostname, baseUrl, docsPath, openapiPath, open_1, _a, error_2;
65
- var _b, _c, _d, _e, _f, _g;
66
- return __generator(this, function (_h) {
67
- switch (_h.label) {
68
- case 0:
69
- compileSpinner = ora('Compiling queries...').start();
70
- dbSpinner = ora('Connecting to ClickHouse...').start();
71
- _h.label = 1;
72
- case 1:
73
- _h.trys.push([1, 14, , 15]);
74
- return [4 /*yield*/, loadApiModule(queriesFile)];
75
- case 2:
76
- api = _h.sent();
77
- compileSpinner.succeed('Compiled queries');
78
- tableCount = 0;
79
- _h.label = 3;
80
- case 3:
81
- _h.trys.push([3, 5, , 6]);
82
- return [4 /*yield*/, getTableCount('clickhouse')];
83
- case 4:
84
- tableCount = _h.sent();
85
- dbSpinner.succeed("Connected to ClickHouse (".concat(tableCount, " tables)"));
86
- return [3 /*break*/, 6];
87
- case 5:
88
- error_1 = _h.sent();
89
- // Log but don't fail - table count is optional
90
- dbSpinner.warn('Could not connect to ClickHouse');
91
- if (error_1 instanceof Error) {
92
- logger.indent("Reason: ".concat(error_1.message));
93
- }
94
- return [3 /*break*/, 6];
95
- case 6:
96
- queryCount = Object.keys(api.queries || {}).length;
97
- logger.header('hypequery dev');
98
- logger.success("Registered ".concat(queryCount, " ").concat(queryCount === 1 ? 'query' : 'queries'));
99
- logger.newline();
100
- return [4 /*yield*/, import('@hypequery/serve')];
101
- case 7:
102
- serveDev = (_h.sent()).serveDev;
103
- return [4 /*yield*/, serveDev(api, {
104
- port: options.port,
105
- hostname: options.hostname,
106
- quiet: true,
107
- })];
108
- case 8:
109
- currentServer = _h.sent();
110
- address = currentServer.server.address();
111
- port = typeof address === 'object' && address ? address.port : options.port || 4000;
112
- hostname = options.hostname || 'localhost';
113
- baseUrl = "http://".concat(hostname, ":").concat(port);
114
- docsPath = (_c = (_b = api.docs) === null || _b === void 0 ? void 0 : _b.path) !== null && _c !== void 0 ? _c : '/docs';
115
- openapiPath = (_e = (_d = api.openapi) === null || _d === void 0 ? void 0 : _d.path) !== null && _e !== void 0 ? _e : '/openapi.json';
116
- logger.box([
117
- "Docs: ".concat(baseUrl).concat((_f = api.basePath) !== null && _f !== void 0 ? _f : '').concat(docsPath),
118
- "OpenAPI: ".concat(baseUrl).concat((_g = api.basePath) !== null && _g !== void 0 ? _g : '').concat(openapiPath),
119
- ]);
120
- logger.newline();
121
- logger.success("Ready in ".concat(process.uptime().toFixed(0), "ms"));
122
- logger.newline();
123
- // Query execution stats are logged automatically
124
- if (!options.quiet) {
125
- logger.info('Query execution stats will appear below as requests are made');
126
- logger.newline();
127
- }
128
- if (shouldWatch) {
129
- logger.info('Watching for changes...');
130
- }
131
- if (!options.open) return [3 /*break*/, 13];
132
- _h.label = 9;
133
- case 9:
134
- _h.trys.push([9, 12, , 13]);
135
- return [4 /*yield*/, import('open')];
136
- case 10:
137
- open_1 = (_h.sent()).default;
138
- return [4 /*yield*/, open_1(baseUrl)];
139
- case 11:
140
- _h.sent();
141
- logger.success("Opened ".concat(baseUrl, " in browser"));
142
- return [3 /*break*/, 13];
143
- case 12:
144
- _a = _h.sent();
145
- // Log but don't fail - browser open is optional
146
- logger.warn('Could not open browser automatically');
147
- logger.indent("Visit: ".concat(baseUrl));
148
- return [3 /*break*/, 13];
149
- case 13: return [3 /*break*/, 15];
150
- case 14:
151
- error_2 = _h.sent();
152
- // Stop spinners if they're still running
153
- if (compileSpinner.isSpinning) {
154
- compileSpinner.fail('Failed to compile queries');
155
- }
156
- if (dbSpinner.isSpinning) {
157
- dbSpinner.stop();
158
- }
159
- logger.error('Failed to start server');
160
- logger.newline();
161
- if (error_2 instanceof Error) {
162
- logger.info(error_2.message);
163
- if (error_2.stack) {
164
- logger.newline();
165
- logger.info('Stack trace:');
166
- logger.info(error_2.stack);
167
- }
168
- }
169
- else {
170
- logger.info(String(error_2));
171
- }
172
- logger.newline();
173
- if (!shouldWatch) {
174
- process.exit(1);
175
- }
176
- return [3 /*break*/, 15];
177
- case 15: return [2 /*return*/];
178
- }
179
- });
180
- }); };
181
- restartServer = function () { return __awaiter(_this, void 0, void 0, function () {
182
- return __generator(this, function (_a) {
183
- switch (_a.label) {
184
- case 0:
185
- if (!currentServer) return [3 /*break*/, 2];
186
- logger.newline();
187
- logger.reload('File changed, restarting...');
188
- logger.newline();
189
- return [4 /*yield*/, currentServer.stop()];
190
- case 1:
191
- _a.sent();
192
- _a.label = 2;
193
- case 2: return [4 /*yield*/, startServer()];
194
- case 3:
195
- _a.sent();
196
- return [2 /*return*/];
197
- }
198
- });
199
- }); };
200
- shutdown = function () { return __awaiter(_this, void 0, void 0, function () {
201
- return __generator(this, function (_a) {
202
- switch (_a.label) {
203
- case 0:
204
- logger.newline();
205
- logger.info('Shutting down dev server...');
206
- if (!currentServer) return [3 /*break*/, 2];
207
- return [4 /*yield*/, currentServer.stop()];
208
- case 1:
209
- _a.sent();
210
- _a.label = 2;
211
- case 2:
212
- process.exit(0);
213
- return [2 /*return*/];
214
- }
215
- });
216
- }); };
217
- // Start initial server
218
- return [4 /*yield*/, startServer()];
219
- case 2:
220
- // Start initial server
221
- _a.sent();
222
- // Watch for changes
223
- if (shouldWatch) {
224
- watchDir = path.dirname(queriesFile);
225
- debounceTimer_1 = null;
226
- watcher_1 = watch(watchDir, { recursive: true }, function (_eventType, filename) {
227
- if (!filename)
228
- return;
229
- // Only watch .ts and .js files
230
- if (!filename.endsWith('.ts') && !filename.endsWith('.js')) {
231
- return;
232
- }
233
- // Debounce file changes
234
- if (debounceTimer_1) {
235
- clearTimeout(debounceTimer_1);
236
- }
237
- debounceTimer_1 = setTimeout(function () { return __awaiter(_this, void 0, void 0, function () {
238
- return __generator(this, function (_a) {
239
- switch (_a.label) {
240
- case 0: return [4 /*yield*/, restartServer()];
241
- case 1:
242
- _a.sent();
243
- return [2 /*return*/];
244
- }
245
- });
246
- }); }, 100);
247
- });
248
- process.once('SIGINT', function () {
249
- watcher_1.close();
250
- shutdown();
251
- });
252
- process.once('SIGTERM', function () {
253
- watcher_1.close();
254
- shutdown();
255
- });
256
- }
257
- else {
258
- process.once('SIGINT', shutdown);
259
- process.once('SIGTERM', shutdown);
260
- }
261
- return [2 /*return*/];
126
+ logger.info('Stack trace:');
127
+ logger.info(error.stack);
128
+ }
129
+ }
130
+ else {
131
+ logger.info(String(error));
132
+ }
133
+ logger.newline();
134
+ if (!shouldWatch) {
135
+ process.exit(1);
136
+ }
137
+ }
138
+ };
139
+ const restartServer = async () => {
140
+ if (currentServer) {
141
+ logger.newline();
142
+ logger.reload('File changed, restarting...');
143
+ logger.newline();
144
+ await currentServer.stop();
145
+ }
146
+ await startServer();
147
+ };
148
+ const shutdown = async () => {
149
+ logger.newline();
150
+ logger.info('Shutting down dev server...');
151
+ if (currentServer) {
152
+ await currentServer.stop();
153
+ }
154
+ process.exit(0);
155
+ };
156
+ // Start initial server
157
+ await startServer();
158
+ // Watch for changes
159
+ if (shouldWatch) {
160
+ const watchDir = path.dirname(queriesFile);
161
+ let debounceTimer = null;
162
+ const watcher = watch(watchDir, { recursive: true }, (_eventType, filename) => {
163
+ if (!filename)
164
+ return;
165
+ // Only watch .ts and .js files
166
+ if (!filename.endsWith('.ts') && !filename.endsWith('.js')) {
167
+ return;
168
+ }
169
+ // Debounce file changes
170
+ if (debounceTimer) {
171
+ clearTimeout(debounceTimer);
262
172
  }
173
+ debounceTimer = setTimeout(async () => {
174
+ await restartServer();
175
+ }, 100);
263
176
  });
264
- });
177
+ process.once('SIGINT', () => {
178
+ watcher.close();
179
+ shutdown();
180
+ });
181
+ process.once('SIGTERM', () => {
182
+ watcher.close();
183
+ shutdown();
184
+ });
185
+ }
186
+ else {
187
+ process.once('SIGINT', shutdown);
188
+ process.once('SIGTERM', shutdown);
189
+ }
265
190
  }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Generate Datasets Command
3
+ *
4
+ * Auto-generates dataset definitions from ClickHouse schema.
5
+ * Reduces quickstart friction by scaffolding the semantic layer.
6
+ */
7
+ export interface GenerateDatasetsOptions {
8
+ output?: string;
9
+ path?: string;
10
+ tables?: string;
11
+ excludeTables?: string;
12
+ }
13
+ export declare function generateDatasetsCommand(options?: GenerateDatasetsOptions): Promise<void>;
14
+ //# sourceMappingURL=generate-datasets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAQH,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAWD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBAwFlF"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Generate Datasets Command
3
+ *
4
+ * Auto-generates dataset definitions from ClickHouse schema.
5
+ * Reduces quickstart friction by scaffolding the semantic layer.
6
+ */
7
+ import path from 'node:path';
8
+ import ora from 'ora';
9
+ import { logger } from '../utils/logger.js';
10
+ import { getTableCount } from '../utils/detect-database.js';
11
+ import { generateDatasets } from '../generators/dataset-generator.js';
12
+ function parseTableList(value) {
13
+ const parsed = value
14
+ ?.split(',')
15
+ .map((table) => table.trim())
16
+ .filter(Boolean);
17
+ return parsed && parsed.length > 0 ? parsed : undefined;
18
+ }
19
+ export async function generateDatasetsCommand(options = {}) {
20
+ // Determine output path
21
+ let outputPath;
22
+ if (options.output) {
23
+ outputPath = path.resolve(process.cwd(), options.output);
24
+ }
25
+ else if (options.path) {
26
+ outputPath = path.resolve(process.cwd(), options.path, 'datasets.ts');
27
+ }
28
+ else {
29
+ // Default to src/datasets/generated.ts
30
+ outputPath = path.join(process.cwd(), 'src', 'datasets', 'generated.ts');
31
+ }
32
+ const parsedTables = parseTableList(options.tables);
33
+ const excludedTables = parseTableList(options.excludeTables);
34
+ logger.newline();
35
+ logger.header('hypequery generate datasets');
36
+ const spinner = ora('Connecting to ClickHouse...').start();
37
+ try {
38
+ // Get table count
39
+ const tableCount = await getTableCount('clickhouse');
40
+ spinner.succeed('Connected to ClickHouse');
41
+ logger.success(`Found ${tableCount} tables`);
42
+ // Generate datasets
43
+ const datasetSpinner = ora('Generating dataset definitions...').start();
44
+ await generateDatasets({
45
+ outputPath,
46
+ includeTables: parsedTables,
47
+ excludeTables: excludedTables,
48
+ });
49
+ datasetSpinner.succeed(`Generated dataset definitions for ${parsedTables?.length || tableCount} tables`);
50
+ logger.success(`Created ${path.relative(process.cwd(), outputPath)}`);
51
+ logger.newline();
52
+ logger.header('Next steps:');
53
+ logger.indent('1. Review and customize the generated datasets');
54
+ logger.indent('2. Import datasets in your application code');
55
+ logger.indent('3. Create an analytics client and start querying!');
56
+ logger.newline();
57
+ logger.info('Example usage:');
58
+ logger.indent('import { datasets } from \'./datasets/generated\';');
59
+ logger.indent('import { createDatasetClient } from \'@hypequery/datasets\';');
60
+ logger.indent('import { createQueryBuilder } from \'@hypequery/clickhouse\';');
61
+ logger.indent('');
62
+ logger.indent('const db = createQueryBuilder({ url, username, password, database });');
63
+ logger.indent('const analytics = createDatasetClient({ queryBuilder: db });');
64
+ logger.indent('const rowCount = datasets.orders.metric(\'rowCount\', { measure: \'totalCount\' });');
65
+ logger.indent('const result = await analytics.execute(rowCount);');
66
+ logger.newline();
67
+ }
68
+ catch (error) {
69
+ spinner.fail('Failed to generate datasets');
70
+ logger.newline();
71
+ if (error instanceof Error) {
72
+ logger.error(error.message);
73
+ // Provide specific guidance
74
+ if (error.message.includes('ECONNREFUSED')) {
75
+ logger.newline();
76
+ logger.info('This usually means:');
77
+ logger.indent('• ClickHouse is not running');
78
+ logger.indent('• Wrong host/port in configuration');
79
+ logger.indent('• Firewall blocking connection');
80
+ logger.newline();
81
+ logger.info('Check your configuration:');
82
+ logger.indent('CLICKHOUSE_URL=' + (process.env.CLICKHOUSE_URL || process.env.CLICKHOUSE_HOST || 'not set'));
83
+ }
84
+ else if (error.message.includes('No tables found')) {
85
+ logger.newline();
86
+ logger.info('No tables match the specified criteria');
87
+ logger.indent('Try: hypequery generate datasets --tables table1,table2');
88
+ }
89
+ }
90
+ else {
91
+ logger.error(String(error));
92
+ }
93
+ logger.newline();
94
+ process.exit(1);
95
+ }
96
+ }
@@ -1,8 +1,10 @@
1
1
  import { type DatabaseType } from '../utils/detect-database.js';
2
2
  export interface GenerateOptions {
3
3
  output?: string;
4
+ path?: string;
4
5
  tables?: string;
5
6
  database?: DatabaseType;
7
+ commandName?: string;
6
8
  }
7
9
  export declare function generateCommand(options?: GenerateOptions): Promise<void>;
8
10
  //# sourceMappingURL=generate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG/F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,iBAwHlE"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAIA,OAAO,EAAiC,KAAK,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAG/F,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAsB,eAAe,CAAC,OAAO,GAAE,eAAoB,iBA0HlE"}