@hypequery/cli 1.1.1 → 1.2.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 (77) hide show
  1. package/README.md +71 -149
  2. package/dist/bin/cli.js +21 -81
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +74 -105
  6. package/dist/commands/dev.d.ts +1 -0
  7. package/dist/commands/dev.d.ts.map +1 -1
  8. package/dist/commands/dev.js +180 -255
  9. package/dist/commands/generate-datasets.d.ts +14 -0
  10. package/dist/commands/generate-datasets.d.ts.map +1 -0
  11. package/dist/commands/generate-datasets.js +96 -0
  12. package/dist/commands/generate.d.ts +2 -0
  13. package/dist/commands/generate.d.ts.map +1 -1
  14. package/dist/commands/generate.js +112 -167
  15. package/dist/commands/init.d.ts +7 -1
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +337 -390
  18. package/dist/generators/clickhouse.d.ts +1 -1
  19. package/dist/generators/clickhouse.d.ts.map +1 -1
  20. package/dist/generators/clickhouse.js +10 -269
  21. package/dist/generators/dataset-generator.d.ts +16 -0
  22. package/dist/generators/dataset-generator.d.ts.map +1 -0
  23. package/dist/generators/dataset-generator.js +213 -0
  24. package/dist/generators/index.js +4 -4
  25. package/dist/templates/api.d.ts +9 -0
  26. package/dist/templates/api.d.ts.map +1 -0
  27. package/dist/templates/api.js +26 -0
  28. package/dist/templates/auth-scaffold.d.ts +17 -0
  29. package/dist/templates/auth-scaffold.d.ts.map +1 -0
  30. package/dist/templates/auth-scaffold.js +41 -0
  31. package/dist/templates/client.js +10 -1
  32. package/dist/templates/datasets.d.ts +5 -0
  33. package/dist/templates/datasets.d.ts.map +1 -0
  34. package/dist/templates/datasets.js +23 -0
  35. package/dist/templates/env.d.ts +2 -1
  36. package/dist/templates/env.d.ts.map +1 -1
  37. package/dist/templates/env.js +19 -6
  38. package/dist/templates/gitignore.js +4 -1
  39. package/dist/templates/queries.d.ts +3 -0
  40. package/dist/templates/queries.d.ts.map +1 -1
  41. package/dist/templates/queries.js +66 -9
  42. package/dist/test-utils.d.ts +0 -72
  43. package/dist/test-utils.d.ts.map +1 -1
  44. package/dist/test-utils.js +12 -138
  45. package/dist/utils/clickhouse-client.js +11 -58
  46. package/dist/utils/clickhouse-sql.d.ts +6 -0
  47. package/dist/utils/clickhouse-sql.d.ts.map +1 -0
  48. package/dist/utils/clickhouse-sql.js +18 -0
  49. package/dist/utils/clickhouse-type-utils.d.ts +6 -0
  50. package/dist/utils/clickhouse-type-utils.d.ts.map +1 -0
  51. package/dist/utils/clickhouse-type-utils.js +59 -0
  52. package/dist/utils/dependency-installer.d.ts +5 -1
  53. package/dist/utils/dependency-installer.d.ts.map +1 -1
  54. package/dist/utils/dependency-installer.js +117 -134
  55. package/dist/utils/detect-database.js +84 -195
  56. package/dist/utils/find-files.d.ts +3 -1
  57. package/dist/utils/find-files.d.ts.map +1 -1
  58. package/dist/utils/find-files.js +82 -148
  59. package/dist/utils/load-api.d.ts.map +1 -1
  60. package/dist/utils/load-api.js +207 -396
  61. package/dist/utils/logger.js +38 -42
  62. package/dist/utils/prompts.d.ts +14 -5
  63. package/dist/utils/prompts.d.ts.map +1 -1
  64. package/dist/utils/prompts.js +170 -244
  65. package/dist/utils/runtime-guards.d.ts +4 -0
  66. package/dist/utils/runtime-guards.d.ts.map +1 -0
  67. package/dist/utils/runtime-guards.js +9 -0
  68. package/dist/utils/sha256.d.ts +2 -0
  69. package/dist/utils/sha256.d.ts.map +1 -0
  70. package/dist/utils/sha256.js +4 -0
  71. package/package.json +4 -4
  72. package/dist/utils/error-messages.d.ts +0 -6
  73. package/dist/utils/error-messages.d.ts.map +0 -1
  74. package/dist/utils/error-messages.js +0 -19
  75. package/dist/utils/load-hypequery-config.d.ts +0 -7
  76. package/dist/utils/load-hypequery-config.d.ts.map +0 -1
  77. package/dist/utils/load-hypequery-config.js +0 -89
@@ -1,9 +1,9 @@
1
+ import { clickhouseToTsType } from '@hypequery/clickhouse/cli';
1
2
  export interface ClickHouseGeneratorOptions {
2
3
  outputPath: string;
3
4
  includeTables?: string[];
4
5
  excludeTables?: string[];
5
6
  }
6
- declare const clickhouseToTsType: (type: string) => string;
7
7
  export { clickhouseToTsType };
8
8
  export declare function generateClickHouseTypes(options: ClickHouseGeneratorOptions): Promise<void>;
9
9
  //# sourceMappingURL=clickhouse.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAmGD,QAAA,MAAM,kBAAkB,GAAI,MAAM,MAAM,KAAG,MAsC1C,CAAC;AAEF,OAAO,EAAE,kBAAkB,EAAE,CAAC;AAgC9B,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,0BAA0B,iBA0ChF"}
1
+ {"version":3,"file":"clickhouse.d.ts","sourceRoot":"","sources":["../../src/generators/clickhouse.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAGnB,MAAM,2BAA2B,CAAC;AAGnC,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,iBAUhF"}
@@ -1,272 +1,13 @@
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
- import fs from 'node:fs/promises';
38
- import path from 'node:path';
1
+ import { clickhouseToTsType, generateTypes, } from '@hypequery/clickhouse/cli';
39
2
  import { getClickHouseClient } from '../utils/clickhouse-client.js';
40
- var DEFAULT_WARNING = 'Warning: No tables match the filter criteria. Check your include/exclude options.';
41
- var capitalizeFirstLetter = function (value) { return value.charAt(0).toUpperCase() + value.slice(1); };
42
- function splitTopLevelArgs(value) {
43
- var parts = [];
44
- var current = '';
45
- var depth = 0;
46
- for (var _i = 0, value_1 = value; _i < value_1.length; _i++) {
47
- var char = value_1[_i];
48
- if (char === '(') {
49
- depth += 1;
50
- current += char;
51
- continue;
52
- }
53
- if (char === ')') {
54
- depth -= 1;
55
- current += char;
56
- continue;
57
- }
58
- if (char === ',' && depth === 0) {
59
- parts.push(current.trim());
60
- current = '';
61
- continue;
62
- }
63
- current += char;
64
- }
65
- if (current.trim()) {
66
- parts.push(current.trim());
67
- }
68
- return parts;
69
- }
70
- function unwrapType(type, wrapperName) {
71
- var prefix = "".concat(wrapperName, "(");
72
- return type.startsWith(prefix) && type.endsWith(')') ? type.slice(prefix.length, -1) : null;
73
- }
74
- function getPrimitiveTsType(type) {
75
- var lowerType = type.toLowerCase();
76
- switch (lowerType) {
77
- case 'string':
78
- case 'uuid':
79
- return 'string';
80
- case 'int8':
81
- case 'int16':
82
- case 'int32':
83
- case 'uint8':
84
- case 'uint16':
85
- case 'uint32':
86
- return 'number';
87
- case 'int64':
88
- case 'uint64':
89
- case 'uint128':
90
- case 'uint256':
91
- case 'int128':
92
- case 'int256':
93
- return 'string';
94
- case 'float32':
95
- case 'float64':
96
- case 'decimal':
97
- return 'number';
98
- case 'datetime':
99
- case 'datetime64':
100
- case 'date':
101
- case 'date32':
102
- return 'string';
103
- case 'bool':
104
- case 'boolean':
105
- return 'boolean';
106
- default:
107
- if (type.startsWith('FixedString('))
108
- return 'string';
109
- if (type.startsWith('Decimal('))
110
- return 'number';
111
- if (type.startsWith('DateTime64('))
112
- return 'string';
113
- if (type.startsWith('DateTime('))
114
- return 'string';
115
- if (type.startsWith('Enum8('))
116
- return 'string';
117
- if (type.startsWith('Enum16('))
118
- return 'string';
119
- return null;
120
- }
121
- }
122
- var clickhouseToTsType = function (type) {
123
- var wrappedArrayType = unwrapType(type, 'Array');
124
- if (wrappedArrayType) {
125
- return "Array<".concat(clickhouseToTsType(wrappedArrayType), ">");
126
- }
127
- var wrappedNullableType = unwrapType(type, 'Nullable');
128
- if (wrappedNullableType) {
129
- return "".concat(clickhouseToTsType(wrappedNullableType), " | null");
130
- }
131
- var wrappedLowCardinalityType = unwrapType(type, 'LowCardinality');
132
- if (wrappedLowCardinalityType) {
133
- return clickhouseToTsType(wrappedLowCardinalityType);
134
- }
135
- var wrappedTupleType = unwrapType(type, 'Tuple');
136
- if (wrappedTupleType) {
137
- var tupleParts = splitTopLevelArgs(wrappedTupleType);
138
- return "[".concat(tupleParts.map(clickhouseToTsType).join(', '), "]");
139
- }
140
- var wrappedMapType = unwrapType(type, 'Map');
141
- if (wrappedMapType) {
142
- var mapParts = splitTopLevelArgs(wrappedMapType);
143
- if (mapParts.length === 2) {
144
- var valueType = mapParts[1];
145
- // JSON object keys are strings even when ClickHouse map keys are numeric.
146
- return "Record<string, ".concat(clickhouseToTsType(valueType), ">");
147
- }
148
- return 'Record<string, unknown>';
149
- }
150
- var primitiveType = getPrimitiveTsType(type);
151
- if (primitiveType)
152
- return primitiveType;
153
- // Unsupported or more complex ClickHouse types currently preserve the historical fallback.
154
- return 'string';
155
- };
156
3
  export { clickhouseToTsType };
157
- function fetchTables(includeTables, excludeTables) {
158
- return __awaiter(this, void 0, void 0, function () {
159
- var client, tablesQuery, tables;
160
- return __generator(this, function (_a) {
161
- switch (_a.label) {
162
- case 0:
163
- client = getClickHouseClient();
164
- return [4 /*yield*/, client.query({
165
- query: 'SHOW TABLES',
166
- format: 'JSONEachRow',
167
- })];
168
- case 1:
169
- tablesQuery = _a.sent();
170
- return [4 /*yield*/, tablesQuery.json()];
171
- case 2:
172
- tables = (_a.sent());
173
- if (includeTables === null || includeTables === void 0 ? void 0 : includeTables.length) {
174
- tables = tables.filter(function (table) { return includeTables.includes(table.name); });
175
- }
176
- if (excludeTables === null || excludeTables === void 0 ? void 0 : excludeTables.length) {
177
- tables = tables.filter(function (table) { return !excludeTables.includes(table.name); });
178
- }
179
- return [2 /*return*/, tables];
180
- }
181
- });
182
- });
183
- }
184
- function fetchColumns(table) {
185
- return __awaiter(this, void 0, void 0, function () {
186
- var client, columnsQuery;
187
- return __generator(this, function (_a) {
188
- switch (_a.label) {
189
- case 0:
190
- client = getClickHouseClient();
191
- return [4 /*yield*/, client.query({
192
- query: "DESCRIBE TABLE ".concat(table),
193
- format: 'JSONEachRow',
194
- })];
195
- case 1:
196
- columnsQuery = _a.sent();
197
- return [4 /*yield*/, columnsQuery.json()];
198
- case 2: return [2 /*return*/, (_a.sent())];
199
- }
200
- });
201
- });
202
- }
203
- export function generateClickHouseTypes(options) {
204
- return __awaiter(this, void 0, void 0, function () {
205
- var outputPath, includeTables, excludeTables, tables, typeDefinitions, _i, tables_1, table, columns, _a, columns_1, column, clickHouseType, _b, tables_2, table, columns, _c, columns_2, column, tsType, outputDir;
206
- return __generator(this, function (_d) {
207
- switch (_d.label) {
208
- case 0:
209
- outputPath = options.outputPath, includeTables = options.includeTables, excludeTables = options.excludeTables;
210
- return [4 /*yield*/, fetchTables(includeTables, excludeTables)];
211
- case 1:
212
- tables = _d.sent();
213
- if (tables.length === 0) {
214
- console.warn(DEFAULT_WARNING);
215
- }
216
- typeDefinitions = "// Generated by hypequery\n" +
217
- "// This file defines TypeScript types based on your ClickHouse database schema\n\n" +
218
- "export interface IntrospectedSchema {";
219
- _i = 0, tables_1 = tables;
220
- _d.label = 2;
221
- case 2:
222
- if (!(_i < tables_1.length)) return [3 /*break*/, 5];
223
- table = tables_1[_i];
224
- return [4 /*yield*/, fetchColumns(table.name)];
225
- case 3:
226
- columns = _d.sent();
227
- typeDefinitions += "\n ".concat(table.name, ": {");
228
- for (_a = 0, columns_1 = columns; _a < columns_1.length; _a++) {
229
- column = columns_1[_a];
230
- clickHouseType = column.type.replace(/'/g, "\\'");
231
- typeDefinitions += "\n '".concat(column.name, "': '").concat(clickHouseType, "';");
232
- }
233
- typeDefinitions += '\n };';
234
- _d.label = 4;
235
- case 4:
236
- _i++;
237
- return [3 /*break*/, 2];
238
- case 5:
239
- typeDefinitions += '\n}\n';
240
- typeDefinitions += "\n// Type-safe record types for each table\n";
241
- _b = 0, tables_2 = tables;
242
- _d.label = 6;
243
- case 6:
244
- if (!(_b < tables_2.length)) return [3 /*break*/, 9];
245
- table = tables_2[_b];
246
- return [4 /*yield*/, fetchColumns(table.name)];
247
- case 7:
248
- columns = _d.sent();
249
- typeDefinitions += "export interface ".concat(capitalizeFirstLetter(table.name), "Record {");
250
- for (_c = 0, columns_2 = columns; _c < columns_2.length; _c++) {
251
- column = columns_2[_c];
252
- tsType = clickhouseToTsType(column.type).replace(/'/g, '');
253
- typeDefinitions += "\n '".concat(column.name, "': ").concat(tsType, ";");
254
- }
255
- typeDefinitions += '\n}\n\n';
256
- _d.label = 8;
257
- case 8:
258
- _b++;
259
- return [3 /*break*/, 6];
260
- case 9:
261
- outputDir = path.dirname(path.resolve(outputPath));
262
- return [4 /*yield*/, fs.mkdir(outputDir, { recursive: true })];
263
- case 10:
264
- _d.sent();
265
- return [4 /*yield*/, fs.writeFile(path.resolve(outputPath), typeDefinitions)];
266
- case 11:
267
- _d.sent();
268
- return [2 /*return*/];
269
- }
270
- });
271
- });
4
+ export async function generateClickHouseTypes(options) {
5
+ const generatorOptions = {
6
+ client: getClickHouseClient(),
7
+ generatedBy: 'hypequery',
8
+ includeUsageExample: false,
9
+ ...(options.includeTables ? { includeTables: options.includeTables } : {}),
10
+ ...(options.excludeTables ? { excludeTables: options.excludeTables } : {}),
11
+ };
12
+ await generateTypes(options.outputPath, generatorOptions);
272
13
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Dataset Generator
3
+ *
4
+ * Generates dataset DSL code from ClickHouse schema introspection.
5
+ * This reduces quickstart friction by auto-scaffolding dataset definitions.
6
+ */
7
+ export interface DatasetGeneratorOptions {
8
+ outputPath: string;
9
+ includeTables?: string[];
10
+ excludeTables?: string[];
11
+ }
12
+ /**
13
+ * Generate dataset definitions from ClickHouse schema
14
+ */
15
+ export declare function generateDatasets(options: DatasetGeneratorOptions): Promise<void>;
16
+ //# sourceMappingURL=dataset-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AA6MD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB,iBAyDtE"}
@@ -0,0 +1,213 @@
1
+ /**
2
+ * Dataset Generator
3
+ *
4
+ * Generates dataset DSL code from ClickHouse schema introspection.
5
+ * This reduces quickstart friction by auto-scaffolding dataset definitions.
6
+ */
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import { getClickHouseClient } from '../utils/clickhouse-client.js';
10
+ /**
11
+ * Determine the dataset dimension type from a ClickHouse type
12
+ */
13
+ function clickhouseToDimensionType(chType) {
14
+ const normalized = chType.toLowerCase();
15
+ // String types
16
+ if (normalized.includes('string') || normalized.includes('enum')) {
17
+ return 'string';
18
+ }
19
+ // Numeric types
20
+ if (normalized.includes('int') ||
21
+ normalized.includes('float') ||
22
+ normalized.includes('double') ||
23
+ normalized.includes('decimal')) {
24
+ return 'number';
25
+ }
26
+ // Date/Time types
27
+ if (normalized.includes('date') || normalized.includes('datetime')) {
28
+ return 'timestamp';
29
+ }
30
+ // Boolean
31
+ if (normalized.includes('bool')) {
32
+ return 'boolean';
33
+ }
34
+ // Default to string for unknown types
35
+ return 'string';
36
+ }
37
+ /**
38
+ * Check if a column is likely a timestamp column
39
+ */
40
+ function isTimestampColumn(column) {
41
+ const name = column.name.toLowerCase();
42
+ const type = column.type.toLowerCase();
43
+ return (type.includes('date') ||
44
+ type.includes('datetime') ||
45
+ name.includes('timestamp') ||
46
+ name.includes('_at') ||
47
+ name === 'created' ||
48
+ name === 'updated');
49
+ }
50
+ /**
51
+ * Check if a column is likely a tenant ID column
52
+ */
53
+ function isTenantColumn(column) {
54
+ const name = column.name.toLowerCase();
55
+ return (name === 'tenant_id' ||
56
+ name === 'organization_id' ||
57
+ name === 'org_id' ||
58
+ name === 'account_id' ||
59
+ name === 'customer_id');
60
+ }
61
+ /**
62
+ * Check if a column is numeric and suitable for measures
63
+ */
64
+ function isNumericColumn(column) {
65
+ const type = column.type.toLowerCase();
66
+ return (type.includes('int') ||
67
+ type.includes('float') ||
68
+ type.includes('double') ||
69
+ type.includes('decimal'));
70
+ }
71
+ /**
72
+ * Convert table name to PascalCase for dataset variable name
73
+ */
74
+ function tableToPascalCase(tableName) {
75
+ return tableName
76
+ .split('_')
77
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
78
+ .join('');
79
+ }
80
+ /**
81
+ * Convert column name to camelCase for field name
82
+ */
83
+ function columnToCamelCase(columnName) {
84
+ const parts = columnName.split('_');
85
+ return parts[0] + parts.slice(1).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join('');
86
+ }
87
+ /**
88
+ * Generate a human-readable label from a column name
89
+ */
90
+ function generateLabel(columnName) {
91
+ return columnName
92
+ .split('_')
93
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
94
+ .join(' ');
95
+ }
96
+ /**
97
+ * Generate dataset definition code for a single table
98
+ */
99
+ async function generateDatasetForTable(client, tableName) {
100
+ // Get column information
101
+ const columnsQuery = await client.query({
102
+ query: `DESCRIBE TABLE ${tableName}`,
103
+ format: 'JSONEachRow',
104
+ });
105
+ const columns = await columnsQuery.json();
106
+ // Determine timeKey (prefer created_at, then any timestamp column)
107
+ const timestampColumns = columns.filter(isTimestampColumn);
108
+ const timeKeyColumn = timestampColumns.find((c) => c.name === 'created_at') || timestampColumns[0];
109
+ // Determine tenantKey
110
+ const tenantColumn = columns.find(isTenantColumn);
111
+ // Generate dimensions
112
+ const dimensionLines = [];
113
+ for (const column of columns) {
114
+ const fieldName = columnToCamelCase(column.name);
115
+ const dimensionType = clickhouseToDimensionType(column.type);
116
+ const label = generateLabel(column.name);
117
+ // Skip if column name === fieldName (no transformation needed)
118
+ if (column.name === fieldName) {
119
+ dimensionLines.push(` ${fieldName}: dimension.${dimensionType}({ label: '${label}' }),`);
120
+ }
121
+ else {
122
+ dimensionLines.push(` ${fieldName}: dimension.${dimensionType}({ column: '${column.name}', label: '${label}' }),`);
123
+ }
124
+ }
125
+ // Generate measures (for numeric columns only)
126
+ const numericColumns = columns.filter(isNumericColumn).filter((c) => !isTenantColumn(c));
127
+ const measureLines = [];
128
+ if (numericColumns.length > 0) {
129
+ measureLines.push(` // Count measures`);
130
+ measureLines.push(` totalCount: measure.count('${columnToCamelCase(columns[0].name)}', { label: 'Total Count' }),`);
131
+ measureLines.push(``);
132
+ for (const column of numericColumns) {
133
+ const fieldName = columnToCamelCase(column.name);
134
+ const label = generateLabel(column.name);
135
+ measureLines.push(` // ${label} measures`);
136
+ measureLines.push(` total${tableToPascalCase(column.name)}: measure.sum('${fieldName}', { label: 'Total ${label}' }),`);
137
+ measureLines.push(` avg${tableToPascalCase(column.name)}: measure.avg('${fieldName}', { label: 'Average ${label}' }),`);
138
+ measureLines.push(``);
139
+ }
140
+ }
141
+ else {
142
+ measureLines.push(` totalCount: measure.count('${columnToCamelCase(columns[0].name)}', { label: 'Total Count' }),`);
143
+ }
144
+ // Build the dataset definition
145
+ const datasetName = tableToPascalCase(tableName);
146
+ const configLines = [];
147
+ configLines.push(` source: '${tableName}',`);
148
+ if (timeKeyColumn) {
149
+ configLines.push(` timeKey: '${timeKeyColumn.name}',`);
150
+ }
151
+ if (tenantColumn) {
152
+ configLines.push(` tenantKey: '${tenantColumn.name}', // Auto-detected tenant isolation column`);
153
+ }
154
+ configLines.push(` dimensions: {`);
155
+ configLines.push(...dimensionLines);
156
+ configLines.push(` },`);
157
+ configLines.push(` measures: {`);
158
+ configLines.push(...measureLines);
159
+ configLines.push(` },`);
160
+ return `export const ${datasetName}Dataset = dataset('${tableName}', {
161
+ ${configLines.join('\n')}
162
+ });
163
+ `;
164
+ }
165
+ /**
166
+ * Generate dataset definitions from ClickHouse schema
167
+ */
168
+ export async function generateDatasets(options) {
169
+ const client = getClickHouseClient();
170
+ // Get all tables
171
+ const tablesQuery = await client.query({
172
+ query: 'SHOW TABLES',
173
+ format: 'JSONEachRow',
174
+ });
175
+ let tables = await tablesQuery.json();
176
+ // Filter tables
177
+ if (options.includeTables && options.includeTables.length > 0) {
178
+ tables = tables.filter((table) => options.includeTables.includes(table.name));
179
+ }
180
+ if (options.excludeTables && options.excludeTables.length > 0) {
181
+ tables = tables.filter((table) => !options.excludeTables.includes(table.name));
182
+ }
183
+ // Filter out system tables
184
+ tables = tables.filter((table) => !table.name.startsWith('system.') && !table.name.startsWith('.inner'));
185
+ if (tables.length === 0) {
186
+ throw new Error('No tables found matching the specified criteria');
187
+ }
188
+ // Generate dataset definitions
189
+ const datasetDefinitions = [];
190
+ for (const table of tables) {
191
+ const datasetCode = await generateDatasetForTable(client, table.name);
192
+ datasetDefinitions.push(datasetCode);
193
+ }
194
+ // Build the complete output file
195
+ const header = `// Generated by @hypequery/cli
196
+ // This file defines dataset definitions based on your ClickHouse database schema
197
+
198
+ import { dataset, dimension, measure } from '@hypequery/datasets';
199
+
200
+ `;
201
+ const footer = `
202
+ // Export all datasets as a registry
203
+ export const datasets = {
204
+ ${tables.map((table) => ` ${table.name}: ${tableToPascalCase(table.name)}Dataset,`).join('\n')}
205
+ };
206
+ `;
207
+ const output = header + datasetDefinitions.join('\n') + footer;
208
+ // Ensure output directory exists
209
+ const outputDir = path.dirname(path.resolve(options.outputPath));
210
+ await fs.mkdir(outputDir, { recursive: true });
211
+ // Write the file
212
+ await fs.writeFile(path.resolve(options.outputPath), output);
213
+ }
@@ -1,13 +1,13 @@
1
1
  import { generateClickHouseTypes } from './clickhouse.js';
2
- var generators = {
2
+ const generators = {
3
3
  clickhouse: generateClickHouseTypes,
4
4
  };
5
5
  export function getTypeGenerator(dbType) {
6
- var generator = generators[dbType];
6
+ const generator = generators[dbType];
7
7
  if (!generator) {
8
8
  throw new Error(dbType === 'unknown'
9
- ? 'Unable to detect database type. Re-run `hypequery init --database <type>` or pass `--database` explicitly.'
10
- : "Type generation for ".concat(dbType, " is not supported yet."));
9
+ ? 'Unable to detect database type. Re-run `hypequery generate --database <type>` or pass `--database` explicitly.'
10
+ : `Type generation for ${dbType} is not supported yet.`);
11
11
  }
12
12
  return generator;
13
13
  }
@@ -0,0 +1,9 @@
1
+ import { type AuthTemplateMode } from './auth-scaffold.js';
2
+ export type { AuthTemplateMode };
3
+ /**
4
+ * Generate api.ts file for the semantic datasets API.
5
+ */
6
+ export declare function generateApiTemplate(options?: {
7
+ auth?: AuthTemplateMode;
8
+ }): string;
9
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/templates/api.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,gBAAgB,CAAA;CAAO,GAAG,MAAM,CAsBrF"}
@@ -0,0 +1,26 @@
1
+ import { hostUserHelpers, contextAuthConfig, } from './auth-scaffold.js';
2
+ /**
3
+ * Generate api.ts file for the semantic datasets API.
4
+ */
5
+ export function generateApiTemplate(options = {}) {
6
+ const authMode = options.auth ?? 'none';
7
+ const serveImports = authMode === 'context' ? 'createAPI, fromContext' : 'createAPI';
8
+ const authHelpers = authMode === 'context'
9
+ ? `\n${hostUserHelpers}\n`
10
+ : '';
11
+ const authConfig = authMode === 'context'
12
+ ? `${contextAuthConfig}\n`
13
+ : '';
14
+ return `import { ${serveImports} } from '@hypequery/serve';
15
+ import type { InferApiType } from '@hypequery/serve';
16
+ import { db } from './client.js';
17
+ import { datasets } from './datasets.js';
18
+ ${authHelpers}
19
+ export const api = createAPI({
20
+ queryBuilder: db,
21
+ datasets,
22
+ ${authConfig}});
23
+
24
+ export type ApiDefinition = InferApiType<typeof api>;
25
+ `;
26
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Shared auth scaffolding snippets used by the `api.ts` and `queries.ts`
3
+ * templates so the generated code stays identical across scaffold styles.
4
+ */
5
+ export type AuthTemplateMode = 'none' | 'context';
6
+ /**
7
+ * Type guards that pull a trusted `HostUser` off the underlying request. The
8
+ * host framework is expected to have already authenticated the request and
9
+ * attached the user to `request.raw.user`.
10
+ */
11
+ export declare const hostUserHelpers = "type HostUser = {\n id: string;\n orgId: string;\n roles?: string[];\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null;\n\nconst isHostUser = (value: unknown): value is HostUser => {\n if (!isRecord(value)) return false;\n const roles = value.roles;\n return typeof value.id === 'string' &&\n typeof value.orgId === 'string' &&\n (roles === undefined || (Array.isArray(roles) && roles.every((role) => typeof role === 'string')));\n};\n\nconst getUserFromRequest = (raw: unknown): HostUser | null => {\n const user = isRecord(raw) ? raw.user : null;\n return isHostUser(user) ? user : null;\n};";
12
+ /**
13
+ * The `auth` + `tenant` config block wired to {@link hostUserHelpers}.
14
+ * Indented two spaces so it drops straight into the config object literal.
15
+ */
16
+ export declare const contextAuthConfig = " auth: fromContext(({ request }) => {\n const user = getUserFromRequest(request.raw);\n return user\n ? { userId: user.id, tenantId: user.orgId, roles: user.roles }\n : null;\n }),\n tenant: {\n extract: (auth: { tenantId?: string }) => auth.tenantId,\n column: 'tenant_id',\n mode: 'auto-inject',\n },";
17
+ //# sourceMappingURL=auth-scaffold.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-scaffold.d.ts","sourceRoot":"","sources":["../../src/templates/auth-scaffold.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAAC;AAElD;;;;GAIG;AACH,eAAO,MAAM,eAAe,+qBAoBzB,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,iBAAiB,gVAUzB,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Type guards that pull a trusted `HostUser` off the underlying request. The
3
+ * host framework is expected to have already authenticated the request and
4
+ * attached the user to `request.raw.user`.
5
+ */
6
+ export const hostUserHelpers = `type HostUser = {
7
+ id: string;
8
+ orgId: string;
9
+ roles?: string[];
10
+ };
11
+
12
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
13
+ typeof value === 'object' && value !== null;
14
+
15
+ const isHostUser = (value: unknown): value is HostUser => {
16
+ if (!isRecord(value)) return false;
17
+ const roles = value.roles;
18
+ return typeof value.id === 'string' &&
19
+ typeof value.orgId === 'string' &&
20
+ (roles === undefined || (Array.isArray(roles) && roles.every((role) => typeof role === 'string')));
21
+ };
22
+
23
+ const getUserFromRequest = (raw: unknown): HostUser | null => {
24
+ const user = isRecord(raw) ? raw.user : null;
25
+ return isHostUser(user) ? user : null;
26
+ };`;
27
+ /**
28
+ * The `auth` + `tenant` config block wired to {@link hostUserHelpers}.
29
+ * Indented two spaces so it drops straight into the config object literal.
30
+ */
31
+ export const contextAuthConfig = ` auth: fromContext(({ request }) => {
32
+ const user = getUserFromRequest(request.raw);
33
+ return user
34
+ ? { userId: user.id, tenantId: user.orgId, roles: user.roles }
35
+ : null;
36
+ }),
37
+ tenant: {
38
+ extract: (auth: { tenantId?: string }) => auth.tenantId,
39
+ column: 'tenant_id',
40
+ mode: 'auto-inject',
41
+ },`;