@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
@@ -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
+ },`;
@@ -2,5 +2,14 @@
2
2
  * Generate client.ts file for ClickHouse
3
3
  */
4
4
  export function generateClientTemplate() {
5
- return "import { createQueryBuilder } from '@hypequery/clickhouse';\nimport type { IntrospectedSchema } from './schema.js';\n\nexport const db = createQueryBuilder<IntrospectedSchema>({\n url: process.env.CLICKHOUSE_URL ?? process.env.CLICKHOUSE_HOST!,\n database: process.env.CLICKHOUSE_DATABASE!,\n username: process.env.CLICKHOUSE_USERNAME!,\n password: process.env.CLICKHOUSE_PASSWORD,\n});\n";
5
+ return `import { createQueryBuilder } from '@hypequery/clickhouse';
6
+ import type { IntrospectedSchema } from './schema.js';
7
+
8
+ export const db = createQueryBuilder<IntrospectedSchema>({
9
+ url: process.env.CLICKHOUSE_URL ?? process.env.CLICKHOUSE_HOST!,
10
+ database: process.env.CLICKHOUSE_DATABASE!,
11
+ username: process.env.CLICKHOUSE_USERNAME!,
12
+ password: process.env.CLICKHOUSE_PASSWORD,
13
+ });
14
+ `;
6
15
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generate placeholder datasets.ts file for no-connection init.
3
+ */
4
+ export declare function generateDatasetsPlaceholderTemplate(): string;
5
+ //# sourceMappingURL=datasets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"datasets.d.ts","sourceRoot":"","sources":["../../src/templates/datasets.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,mCAAmC,IAAI,MAAM,CAmB5D"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Generate placeholder datasets.ts file for no-connection init.
3
+ */
4
+ export function generateDatasetsPlaceholderTemplate() {
5
+ return `import { dataset, dimension, measure } from '@hypequery/datasets';
6
+
7
+ const exampleEvents = dataset('example_events', {
8
+ source: 'example_events',
9
+ timeKey: 'created_at',
10
+ dimensions: {
11
+ id: dimension.string(),
12
+ createdAt: dimension.timestamp({ column: 'created_at' }),
13
+ },
14
+ measures: {
15
+ eventCount: measure.count('id', { label: 'Event Count' }),
16
+ },
17
+ });
18
+
19
+ export const datasets = {
20
+ exampleEvents,
21
+ };
22
+ `;
23
+ }
@@ -2,13 +2,24 @@
2
2
  * Generate .env file content for ClickHouse
3
3
  */
4
4
  export function generateEnvTemplate(config) {
5
- var _a, _b;
6
- var connectionUrl = (_b = (_a = config.url) !== null && _a !== void 0 ? _a : config.host) !== null && _b !== void 0 ? _b : '';
7
- var isPlaceholder = connectionUrl.includes('YOUR_');
5
+ const connectionUrl = config.url ?? config.host ?? '';
6
+ const isPlaceholder = connectionUrl.includes('YOUR_');
8
7
  if (isPlaceholder) {
9
- return "# Hypequery Configuration\n# Replace these placeholder values with your actual ClickHouse credentials\n\nCLICKHOUSE_URL=".concat(connectionUrl, "\nCLICKHOUSE_DATABASE=").concat(config.database, "\nCLICKHOUSE_USERNAME=").concat(config.username, "\nCLICKHOUSE_PASSWORD=").concat(config.password, "\n");
8
+ return `# Hypequery Configuration
9
+ # Replace these placeholder values with your actual ClickHouse credentials
10
+
11
+ CLICKHOUSE_URL=${connectionUrl}
12
+ CLICKHOUSE_DATABASE=${config.database}
13
+ CLICKHOUSE_USERNAME=${config.username}
14
+ CLICKHOUSE_PASSWORD=${config.password}
15
+ `;
10
16
  }
11
- return "# Hypequery Configuration\nCLICKHOUSE_URL=".concat(connectionUrl, "\nCLICKHOUSE_DATABASE=").concat(config.database, "\nCLICKHOUSE_USERNAME=").concat(config.username, "\nCLICKHOUSE_PASSWORD=").concat(config.password, "\n");
17
+ return `# Hypequery Configuration
18
+ CLICKHOUSE_URL=${connectionUrl}
19
+ CLICKHOUSE_DATABASE=${config.database}
20
+ CLICKHOUSE_USERNAME=${config.username}
21
+ CLICKHOUSE_PASSWORD=${config.password}
22
+ `;
12
23
  }
13
24
  /**
14
25
  * Append to existing .env file
@@ -17,11 +28,11 @@ export function appendToEnv(existingContent, newContent) {
17
28
  // Check if hypequery section already exists
18
29
  if (existingContent.includes('# Hypequery Configuration')) {
19
30
  // Replace existing section
20
- var lines = existingContent.split('\n');
21
- var startIndex = lines.findIndex(function (l) { return l.includes('# Hypequery Configuration'); });
31
+ const lines = existingContent.split('\n');
32
+ const startIndex = lines.findIndex(l => l.includes('# Hypequery Configuration'));
22
33
  if (startIndex !== -1) {
23
34
  // Find the end of the hypequery section
24
- var endIndex = startIndex + 1;
35
+ let endIndex = startIndex + 1;
25
36
  while (endIndex < lines.length &&
26
37
  (lines[endIndex].startsWith('CLICKHOUSE_') || lines[endIndex].trim() === '')) {
27
38
  endIndex++;
@@ -1,7 +1,10 @@
1
1
  /**
2
2
  * Content to append to .gitignore
3
3
  */
4
- export var GITIGNORE_CONTENT = "\n# Hypequery\n.env\n";
4
+ export const GITIGNORE_CONTENT = `
5
+ # Hypequery
6
+ .env
7
+ `;
5
8
  /**
6
9
  * Check if .gitignore already has hypequery entries
7
10
  */
@@ -1,8 +1,11 @@
1
+ import { type AuthTemplateMode } from './auth-scaffold.js';
2
+ export type { AuthTemplateMode };
1
3
  /**
2
4
  * Generate queries.ts file
3
5
  */
4
6
  export declare function generateQueriesTemplate(options: {
5
7
  hasExample: boolean;
6
8
  tableName?: string;
9
+ auth?: AuthTemplateMode;
7
10
  }): string;
8
11
  //# sourceMappingURL=queries.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/templates/queries.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,MAAM,CAiET"}
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/templates/queries.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAGtB,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,gBAAgB,EAAE,CAAC;AAEjC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE;IAC/C,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB,GAAG,MAAM,CAwET"}
@@ -1,28 +1,84 @@
1
+ import { hostUserHelpers, contextAuthConfig, } from './auth-scaffold.js';
1
2
  /**
2
3
  * Generate queries.ts file
3
4
  */
4
5
  export function generateQueriesTemplate(options) {
5
- var hasExample = options.hasExample, tableName = options.tableName;
6
- var metricKey = hasExample && tableName ? "".concat(camelCase(tableName), "Query") : 'exampleMetric';
7
- var typeAlias = "".concat(pascalCase(metricKey), "Result");
8
- var routePath = "/metrics/".concat(metricKey);
9
- var template = "import { initServe } from '@hypequery/serve';\nimport type { InferApiType } from '@hypequery/serve';\nimport { z } from 'zod';\nimport { db } from './client.js';\n\nconst { query, serve } = initServe({\n context: () => ({ db }),\n});\n\n";
6
+ const { hasExample, tableName, auth = 'none' } = options;
7
+ const metricKey = hasExample && tableName ? `${camelCase(tableName)}Query` : 'exampleMetric';
8
+ const typeAlias = `${pascalCase(metricKey)}Result`;
9
+ const routePath = `/metrics/${metricKey}`;
10
+ const serveImports = auth === 'context' ? 'fromContext, initServe' : 'initServe';
11
+ const authHelpers = auth === 'context'
12
+ ? `\n${hostUserHelpers}\n`
13
+ : '';
14
+ const authConfig = auth === 'context'
15
+ ? `${contextAuthConfig}\n`
16
+ : '';
17
+ let template = `import { ${serveImports} } from '@hypequery/serve';
18
+ import type { InferApiType } from '@hypequery/serve';
19
+ import { z } from 'zod';
20
+ import { db } from './client.js';
21
+ ${authHelpers}
22
+ const { query, serve } = initServe({
23
+ context: () => ({ db }),
24
+ ${authConfig}});
25
+
26
+ `;
10
27
  if (hasExample && tableName) {
11
- template += "\nconst ".concat(camelCase(tableName), "Query = query({\n description: 'Example query using the ").concat(tableName, " table',\n query: async ({ ctx }) =>\n ctx.db\n .table('").concat(tableName, "')\n .select('*')\n .limit(10)\n .execute(),\n});");
28
+ template += `
29
+ const ${camelCase(tableName)}Query = query({
30
+ description: 'Example query using the ${tableName} table',
31
+ query: async ({ ctx }) =>
32
+ ctx.db
33
+ .table('${tableName}')
34
+ .select('*')
35
+ .limit(10)
36
+ .execute(),
37
+ });`;
12
38
  }
13
39
  else {
14
- template += "\nconst exampleMetric = query({\n description: 'Example metric that returns a simple value',\n output: z.object({ ok: z.boolean() }),\n query: async () => ({ ok: true }),\n});";
40
+ template += `
41
+ const exampleMetric = query({
42
+ description: 'Example metric that returns a simple value',
43
+ output: z.object({ ok: z.boolean() }),
44
+ query: async () => ({ ok: true }),
45
+ });`;
15
46
  }
16
- template += "\nexport const api = serve({\n queries: {\n ".concat(metricKey, ",\n },\n});\napi.route('").concat(routePath, "', api.queries.").concat(metricKey, ");\n\nexport type ApiDefinition = InferApiType<typeof api>;\n\n/**\n * Inline usage example:\n *\n * const result = await api.execute('").concat(metricKey, "');\n * console.log(result);\n *\n * // import type { InferQueryResult } from '@hypequery/serve';\n * type ").concat(typeAlias, " = InferQueryResult<typeof api, '").concat(metricKey, "'>;\n *\n * HTTP route:\n * ").concat(routePath, "\n *\n * Dev server:\n * npx hypequery dev analytics/queries.ts\n */\n");
47
+ template += `
48
+ export const api = serve({
49
+ queries: {
50
+ ${metricKey},
51
+ },
52
+ });
53
+ api.route('${routePath}', api.queries.${metricKey});
54
+
55
+ export type ApiDefinition = InferApiType<typeof api>;
56
+
57
+ /**
58
+ * Inline usage example:
59
+ *
60
+ * const result = await api.execute('${metricKey}');
61
+ * console.log(result);
62
+ *
63
+ * // import type { InferQueryResult } from '@hypequery/serve';
64
+ * type ${typeAlias} = InferQueryResult<typeof api, '${metricKey}'>;
65
+ *
66
+ * HTTP route:
67
+ * ${routePath}
68
+ *
69
+ * Dev server:
70
+ * npx hypequery dev analytics/queries.ts
71
+ */
72
+ `;
17
73
  return template;
18
74
  }
19
75
  /**
20
76
  * Convert table name to camelCase
21
77
  */
22
78
  function camelCase(str) {
23
- return str.replace(/_([a-z])/g, function (_, letter) { return letter.toUpperCase(); });
79
+ return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
24
80
  }
25
81
  function pascalCase(str) {
26
- var camel = camelCase(str);
82
+ const camel = camelCase(str);
27
83
  return camel.charAt(0).toUpperCase() + camel.slice(1);
28
84
  }
@@ -1,75 +1,4 @@
1
1
  import type { Mock } from 'vitest';
2
- /**
3
- * Mock prompts module for testing
4
- */
5
- export declare function createMockPrompts(): {
6
- promptClickHouseConnection: Mock<(...args: any[]) => any>;
7
- promptOutputDirectory: Mock<(...args: any[]) => any>;
8
- promptGenerateExample: Mock<(...args: any[]) => any>;
9
- promptTableSelection: Mock<(...args: any[]) => any>;
10
- confirmOverwrite: Mock<(...args: any[]) => any>;
11
- promptRetry: Mock<(...args: any[]) => any>;
12
- promptContinueWithoutDb: Mock<(...args: any[]) => any>;
13
- };
14
- /**
15
- * Mock database detection utilities
16
- */
17
- export declare function createMockDatabaseUtils(): {
18
- validateConnection: Mock<(...args: any[]) => any>;
19
- getTableCount: Mock<(...args: any[]) => any>;
20
- getTables: Mock<(...args: any[]) => any>;
21
- };
22
- /**
23
- * Mock file system utilities
24
- */
25
- export declare function createMockFileUtils(): {
26
- hasEnvFile: Mock<(...args: any[]) => any>;
27
- hasGitignore: Mock<(...args: any[]) => any>;
28
- findQueriesFile: Mock<(...args: any[]) => any>;
29
- findSchemaFile: Mock<(...args: any[]) => any>;
30
- };
31
- /**
32
- * Mock logger to capture output
33
- */
34
- export declare function createMockLogger(): {
35
- success: Mock<(...args: any[]) => any>;
36
- error: Mock<(...args: any[]) => any>;
37
- warn: Mock<(...args: any[]) => any>;
38
- info: Mock<(...args: any[]) => any>;
39
- reload: Mock<(...args: any[]) => any>;
40
- header: Mock<(...args: any[]) => any>;
41
- newline: Mock<(...args: any[]) => any>;
42
- indent: Mock<(...args: any[]) => any>;
43
- box: Mock<(...args: any[]) => any>;
44
- table: Mock<(...args: any[]) => any>;
45
- raw: Mock<(...args: any[]) => any>;
46
- };
47
- /**
48
- * Mock ora spinner
49
- */
50
- export declare function createMockSpinner(): Mock<() => {
51
- start: Mock<(...args: any[]) => any>;
52
- succeed: Mock<(...args: any[]) => any>;
53
- fail: Mock<(...args: any[]) => any>;
54
- stop: Mock<(...args: any[]) => any>;
55
- }>;
56
- /**
57
- * Mock fs/promises for file operations
58
- */
59
- export declare function createMockFs(): {
60
- mkdir: Mock<(...args: any[]) => any>;
61
- writeFile: Mock<(...args: any[]) => any>;
62
- readFile: Mock<(...args: any[]) => any>;
63
- access: Mock<(...args: any[]) => any>;
64
- };
65
- /**
66
- * Capture console output
67
- */
68
- export declare function captureConsole(): {
69
- logs: string[];
70
- errors: string[];
71
- restore: () => void;
72
- };
73
2
  /**
74
3
  * Mock process.exit to prevent tests from exiting
75
4
  * Throws a special error to stop execution
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC;;GAEG;AACH,wBAAgB,iBAAiB;;;;;;;;EAUhC;AAED;;GAEG;AACH,wBAAgB,uBAAuB;;;;EAMtC;AAED;;GAEG;AACH,wBAAgB,mBAAmB;;;;;EAOlC;AAED;;GAEG;AACH,wBAAgB,gBAAgB;;;;;;;;;;;;EAc/B;AAED;;GAEG;AACH,wBAAgB,iBAAiB;;;;;GAQhC;AAED;;GAEG;AACH,wBAAgB,YAAY;;;;;EAO3B;AAED;;GAEG;AACH,wBAAgB,cAAc;;;;EAuB7B;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACtB,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIhC;AAED,wBAAgB,eAAe;;;EAc9B"}
1
+ {"version":3,"file":"test-utils.d.ts","sourceRoot":"","sources":["../src/test-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEnC;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,KAAK;IACtB,IAAI,EAAE,MAAM;gBAAZ,IAAI,EAAE,MAAM;CAIhC;AAED,wBAAgB,eAAe;;;EAc9B"}
@@ -1,150 +1,25 @@
1
- var __extends = (this && this.__extends) || (function () {
2
- var extendStatics = function (d, b) {
3
- extendStatics = Object.setPrototypeOf ||
4
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
5
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
6
- return extendStatics(d, b);
7
- };
8
- return function (d, b) {
9
- if (typeof b !== "function" && b !== null)
10
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
11
- extendStatics(d, b);
12
- function __() { this.constructor = d; }
13
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
14
- };
15
- })();
16
1
  import { vi } from 'vitest';
17
- /**
18
- * Mock prompts module for testing
19
- */
20
- export function createMockPrompts() {
21
- return {
22
- promptClickHouseConnection: vi.fn(),
23
- promptOutputDirectory: vi.fn(),
24
- promptGenerateExample: vi.fn(),
25
- promptTableSelection: vi.fn(),
26
- confirmOverwrite: vi.fn(),
27
- promptRetry: vi.fn(),
28
- promptContinueWithoutDb: vi.fn(),
29
- };
30
- }
31
- /**
32
- * Mock database detection utilities
33
- */
34
- export function createMockDatabaseUtils() {
35
- return {
36
- validateConnection: vi.fn(),
37
- getTableCount: vi.fn(),
38
- getTables: vi.fn(),
39
- };
40
- }
41
- /**
42
- * Mock file system utilities
43
- */
44
- export function createMockFileUtils() {
45
- return {
46
- hasEnvFile: vi.fn(),
47
- hasGitignore: vi.fn(),
48
- findQueriesFile: vi.fn(),
49
- findSchemaFile: vi.fn(),
50
- };
51
- }
52
- /**
53
- * Mock logger to capture output
54
- */
55
- export function createMockLogger() {
56
- return {
57
- success: vi.fn(),
58
- error: vi.fn(),
59
- warn: vi.fn(),
60
- info: vi.fn(),
61
- reload: vi.fn(),
62
- header: vi.fn(),
63
- newline: vi.fn(),
64
- indent: vi.fn(),
65
- box: vi.fn(),
66
- table: vi.fn(),
67
- raw: vi.fn(),
68
- };
69
- }
70
- /**
71
- * Mock ora spinner
72
- */
73
- export function createMockSpinner() {
74
- var spinner = {
75
- start: vi.fn().mockReturnThis(),
76
- succeed: vi.fn().mockReturnThis(),
77
- fail: vi.fn().mockReturnThis(),
78
- stop: vi.fn().mockReturnThis(),
79
- };
80
- return vi.fn(function () { return spinner; });
81
- }
82
- /**
83
- * Mock fs/promises for file operations
84
- */
85
- export function createMockFs() {
86
- return {
87
- mkdir: vi.fn().mockResolvedValue(undefined),
88
- writeFile: vi.fn().mockResolvedValue(undefined),
89
- readFile: vi.fn().mockResolvedValue(''),
90
- access: vi.fn().mockResolvedValue(undefined),
91
- };
92
- }
93
- /**
94
- * Capture console output
95
- */
96
- export function captureConsole() {
97
- var logs = [];
98
- var errors = [];
99
- var originalLog = console.log;
100
- var originalError = console.error;
101
- console.log = vi.fn(function () {
102
- var args = [];
103
- for (var _i = 0; _i < arguments.length; _i++) {
104
- args[_i] = arguments[_i];
105
- }
106
- logs.push(args.join(' '));
107
- });
108
- console.error = vi.fn(function () {
109
- var args = [];
110
- for (var _i = 0; _i < arguments.length; _i++) {
111
- args[_i] = arguments[_i];
112
- }
113
- errors.push(args.join(' '));
114
- });
115
- return {
116
- logs: logs,
117
- errors: errors,
118
- restore: function () {
119
- console.log = originalLog;
120
- console.error = originalError;
121
- },
122
- };
123
- }
124
2
  /**
125
3
  * Mock process.exit to prevent tests from exiting
126
4
  * Throws a special error to stop execution
127
5
  */
128
- var ProcessExitError = /** @class */ (function (_super) {
129
- __extends(ProcessExitError, _super);
130
- function ProcessExitError(code) {
131
- var _this = _super.call(this, "process.exit called with code ".concat(code)) || this;
132
- _this.code = code;
133
- _this.name = 'ProcessExitError';
134
- return _this;
6
+ export class ProcessExitError extends Error {
7
+ code;
8
+ constructor(code) {
9
+ super(`process.exit called with code ${code}`);
10
+ this.code = code;
11
+ this.name = 'ProcessExitError';
135
12
  }
136
- return ProcessExitError;
137
- }(Error));
138
- export { ProcessExitError };
13
+ }
139
14
  export function mockProcessExit() {
140
- var originalExit = process.exit;
141
- var exitMock = vi.fn(function (code) {
142
- throw new ProcessExitError(code !== null && code !== void 0 ? code : 0);
15
+ const originalExit = process.exit;
16
+ const exitMock = vi.fn((code) => {
17
+ throw new ProcessExitError(code ?? 0);
143
18
  });
144
19
  process.exit = exitMock;
145
20
  return {
146
- exitMock: exitMock,
147
- restore: function () {
21
+ exitMock,
22
+ restore: () => {
148
23
  process.exit = originalExit;
149
24
  },
150
25
  };
@@ -1,53 +1,16 @@
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 { createClient } from '@clickhouse/client';
38
- var client = null;
2
+ let client = null;
39
3
  function readEnv(name, fallback) {
40
- var value = process.env[name];
4
+ const value = process.env[name];
41
5
  return value === undefined || value === '' ? fallback : value;
42
6
  }
43
7
  export function getClickHouseConfigFromEnv() {
44
- var _a;
45
- var url = (_a = readEnv('CLICKHOUSE_URL')) !== null && _a !== void 0 ? _a : readEnv('CLICKHOUSE_HOST');
8
+ const url = readEnv('CLICKHOUSE_URL') ?? readEnv('CLICKHOUSE_HOST');
46
9
  if (!url) {
47
10
  return null;
48
11
  }
49
12
  return {
50
- url: url,
13
+ url,
51
14
  username: readEnv('CLICKHOUSE_USERNAME', readEnv('CLICKHOUSE_USER', 'default')),
52
15
  password: readEnv('CLICKHOUSE_PASSWORD', readEnv('CLICKHOUSE_PASS', '')),
53
16
  database: readEnv('CLICKHOUSE_DATABASE', 'default'),
@@ -55,7 +18,7 @@ export function getClickHouseConfigFromEnv() {
55
18
  }
56
19
  export function getClickHouseClient() {
57
20
  if (!client) {
58
- var config = getClickHouseConfigFromEnv();
21
+ const config = getClickHouseConfigFromEnv();
59
22
  if (!config) {
60
23
  throw new Error('ClickHouse connection details are missing. Set CLICKHOUSE_URL (or CLICKHOUSE_HOST), CLICKHOUSE_DATABASE, CLICKHOUSE_USERNAME, and CLICKHOUSE_PASSWORD.');
61
24
  }
@@ -68,19 +31,9 @@ export function getClickHouseClient() {
68
31
  }
69
32
  return client;
70
33
  }
71
- export function resetClickHouseClientForTesting() {
72
- return __awaiter(this, void 0, void 0, function () {
73
- return __generator(this, function (_a) {
74
- switch (_a.label) {
75
- case 0:
76
- if (!client) return [3 /*break*/, 2];
77
- return [4 /*yield*/, client.close()];
78
- case 1:
79
- _a.sent();
80
- client = null;
81
- _a.label = 2;
82
- case 2: return [2 /*return*/];
83
- }
84
- });
85
- });
34
+ export async function resetClickHouseClientForTesting() {
35
+ if (client) {
36
+ await client.close();
37
+ client = null;
38
+ }
86
39
  }
@@ -0,0 +1,6 @@
1
+ export declare function quoteIdentifier(identifier: string): string;
2
+ export declare function assertValidIdentifier(identifier: string): void;
3
+ export declare function sqlString(value: string): string;
4
+ export declare function sqlStringArray(values: string[]): string;
5
+ export declare function sqlDateTime(value: Date): string;
6
+ //# sourceMappingURL=clickhouse-sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clickhouse-sql.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-sql.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,QAIvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,UAEtC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,UAE9C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,IAAI,UAEtC"}
@@ -0,0 +1,18 @@
1
+ export function quoteIdentifier(identifier) {
2
+ assertValidIdentifier(identifier);
3
+ return `\`${identifier.replace(/`/g, '``')}\``;
4
+ }
5
+ export function assertValidIdentifier(identifier) {
6
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
7
+ throw new Error(`Invalid ClickHouse identifier: ${identifier}`);
8
+ }
9
+ }
10
+ export function sqlString(value) {
11
+ return `'${value.replace(/\\/g, '\\\\').replace(/'/g, "\\'")}'`;
12
+ }
13
+ export function sqlStringArray(values) {
14
+ return `[${values.map(sqlString).join(', ')}]`;
15
+ }
16
+ export function sqlDateTime(value) {
17
+ return `parseDateTime64BestEffort(${sqlString(value.toISOString())}, 3)`;
18
+ }
@@ -0,0 +1,6 @@
1
+ export declare function splitTopLevelArgs(value: string): string[];
2
+ export declare function unwrapType(type: string, wrapperName: string): string | null;
3
+ export declare function matchTypeCall(type: string, name: string): string[] | null;
4
+ export declare function unquoteClickHouseString(value: string): string;
5
+ export declare function isIntegerLiteral(value: string): boolean;
6
+ //# sourceMappingURL=clickhouse-type-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clickhouse-type-utils.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-type-utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CA+CzD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG3E;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,CAGzE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,UASpD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,WAE7C"}