@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.
- package/README.md +26 -2
- package/dist/bin/cli.js +21 -81
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +72 -116
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +180 -255
- package/dist/commands/generate-datasets.d.ts +14 -0
- package/dist/commands/generate-datasets.d.ts.map +1 -0
- package/dist/commands/generate-datasets.js +96 -0
- package/dist/commands/generate.d.ts +2 -0
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +112 -167
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +337 -370
- package/dist/generators/clickhouse.d.ts +1 -1
- package/dist/generators/clickhouse.d.ts.map +1 -1
- package/dist/generators/clickhouse.js +10 -269
- package/dist/generators/dataset-generator.d.ts +16 -0
- package/dist/generators/dataset-generator.d.ts.map +1 -0
- package/dist/generators/dataset-generator.js +246 -0
- package/dist/generators/index.js +3 -3
- package/dist/templates/api.d.ts +9 -0
- package/dist/templates/api.d.ts.map +1 -0
- package/dist/templates/api.js +26 -0
- package/dist/templates/auth-scaffold.d.ts +17 -0
- package/dist/templates/auth-scaffold.d.ts.map +1 -0
- package/dist/templates/auth-scaffold.js +41 -0
- package/dist/templates/client.js +10 -1
- package/dist/templates/datasets.d.ts +5 -0
- package/dist/templates/datasets.d.ts.map +1 -0
- package/dist/templates/datasets.js +23 -0
- package/dist/templates/env.js +19 -8
- package/dist/templates/gitignore.js +4 -1
- package/dist/templates/queries.d.ts +3 -0
- package/dist/templates/queries.d.ts.map +1 -1
- package/dist/templates/queries.js +66 -10
- package/dist/test-utils.d.ts +0 -71
- package/dist/test-utils.d.ts.map +1 -1
- package/dist/test-utils.js +12 -137
- package/dist/utils/clickhouse-client.js +10 -57
- package/dist/utils/clickhouse-sql.d.ts +6 -0
- package/dist/utils/clickhouse-sql.d.ts.map +1 -0
- package/dist/utils/clickhouse-sql.js +18 -0
- package/dist/utils/clickhouse-type-utils.d.ts +6 -0
- package/dist/utils/clickhouse-type-utils.d.ts.map +1 -0
- package/dist/utils/clickhouse-type-utils.js +59 -0
- package/dist/utils/dependency-installer.d.ts +4 -2
- package/dist/utils/dependency-installer.d.ts.map +1 -1
- package/dist/utils/dependency-installer.js +93 -170
- package/dist/utils/detect-database.js +84 -195
- package/dist/utils/find-files.d.ts +3 -1
- package/dist/utils/find-files.d.ts.map +1 -1
- package/dist/utils/find-files.js +82 -148
- package/dist/utils/load-api.d.ts.map +1 -1
- package/dist/utils/load-api.js +207 -396
- package/dist/utils/logger.js +38 -42
- package/dist/utils/prompts.d.ts +14 -0
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +170 -219
- package/dist/utils/runtime-guards.d.ts +4 -0
- package/dist/utils/runtime-guards.d.ts.map +1 -0
- package/dist/utils/runtime-guards.js +9 -0
- package/dist/utils/sha256.d.ts +2 -0
- package/dist/utils/sha256.d.ts.map +1 -0
- package/dist/utils/sha256.js +4 -0
- package/package.json +3 -8
- package/dist/utils/error-messages.d.ts +0 -6
- package/dist/utils/error-messages.d.ts.map +0 -1
- package/dist/utils/error-messages.js +0 -19
- package/dist/utils/load-hypequery-config.d.ts +0 -7
- package/dist/utils/load-hypequery-config.d.ts.map +0 -1
- package/dist/utils/load-hypequery-config.js +0 -89
package/dist/commands/dev.js
CHANGED
|
@@ -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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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;
|
|
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"}
|