@hypequery/cli 1.18.0 → 1.18.2

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/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA4PD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgCpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAgQD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -142,6 +142,7 @@ program
142
142
  .option('--path <path>', 'Analytics directory (derives <path>/datasets.ts)')
143
143
  .option('--tables <names>', 'Only generate for specific tables (comma-separated)')
144
144
  .option('--exclude-tables <names>', 'Exclude specific tables (comma-separated)')
145
+ .option('--tenant-column <column>', 'Set tenantKey to this column on tables that have it (requires a trusted runtime tenant scope)')
145
146
  .action(runCommand(async (options) => {
146
147
  await generateDatasetsCommand(options);
147
148
  }));
@@ -9,6 +9,7 @@ export interface GenerateDatasetsOptions {
9
9
  path?: string;
10
10
  tables?: string;
11
11
  excludeTables?: string;
12
+ tenantColumn?: string;
12
13
  }
13
14
  export declare function generateDatasetsCommand(options?: GenerateDatasetsOptions): Promise<void>;
14
15
  //# sourceMappingURL=generate-datasets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,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;AAqBD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBA0GlF"}
1
+ {"version":3,"file":"generate-datasets.d.ts","sourceRoot":"","sources":["../../src/commands/generate-datasets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,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;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAqBD,wBAAsB,uBAAuB,CAAC,OAAO,GAAE,uBAA4B,iBA8GlF"}
@@ -10,6 +10,7 @@ import { logger } from '../utils/logger.js';
10
10
  import { getTableCount } from '../utils/detect-database.js';
11
11
  import { generateDatasets } from '../generators/dataset-generator.js';
12
12
  import { redactConnectionUrl } from '../utils/redact-connection-url.js';
13
+ import { logDatasetGenerationWarnings } from '../utils/dataset-generation-warnings.js';
13
14
  /**
14
15
  * Turns the written path into something pasteable into an import statement:
15
16
  * strips the `.ts` extension and makes it explicitly relative.
@@ -58,12 +59,16 @@ export async function generateDatasetsCommand(options = {}) {
58
59
  outputPath,
59
60
  includeTables: parsedTables,
60
61
  excludeTables: excludedTables,
62
+ // Regeneration replaces the whole file, so a tenant boundary configured at
63
+ // init has to be restated here or it is dropped on every refresh.
64
+ tenantColumn: options.tenantColumn,
61
65
  });
62
66
  const generatedTables = generated?.tables ?? parsedTables ?? [];
63
67
  const generatedCount = generatedTables.length || tableCount;
64
68
  datasetSpinner.succeed(`Generated dataset definitions for ${generatedCount} ${generatedCount === 1 ? 'table' : 'tables'}`);
65
69
  const relativeOutput = path.relative(process.cwd(), outputPath);
66
70
  logger.success(`Created ${relativeOutput}`);
71
+ logDatasetGenerationWarnings(generated?.warnings);
67
72
  logger.newline();
68
73
  logger.header('Next steps:');
69
74
  logger.indent('1. Review and customize the generated datasets');
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,EAcL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAQzF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAqKD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAya1D"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,OAAO,EAcL,KAAK,SAAS,EACf,MAAM,qBAAqB,CAAC;AAe7B,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAWzF,MAAM,WAAW,WAAW;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,IAAI,CAAC,EAAE,gBAAgB,CAAC;CACzB;AAqKD,wBAAsB,WAAW,CAAC,OAAO,GAAE,WAAgB,iBAkb1D"}
@@ -10,11 +10,14 @@ import { generateEnvTemplate, appendToEnv } from '../templates/env.js';
10
10
  import { generateClientTemplate } from '../templates/client.js';
11
11
  import { generateQueriesTemplate } from '../templates/queries.js';
12
12
  import { generateApiTemplate } from '../templates/api.js';
13
+ import { CONTEXT_AUTH_TENANT_COLUMN } from '../templates/auth-scaffold.js';
13
14
  import { generateDatasetsPlaceholderTemplate } from '../templates/datasets.js';
14
15
  import { appendToGitignore } from '../templates/gitignore.js';
15
16
  import { getTypeGenerator } from '../generators/index.js';
16
17
  import { generateDatasets } from '../generators/dataset-generator.js';
17
18
  import { installScaffoldDependencies } from '../utils/dependency-installer.js';
19
+ import { logDatasetGenerationWarnings } from '../utils/dataset-generation-warnings.js';
20
+ import { formatRegenerateDatasetsCommand } from '../utils/regenerate-datasets-command.js';
18
21
  function normalizeInitDatabase(database) {
19
22
  if (!database || database === 'clickhouse') {
20
23
  return 'clickhouse';
@@ -389,22 +392,29 @@ export interface IntrospectedSchema {
389
392
  const shouldGenerateDatasets = hasValidConnection && (options.allTables === true ||
390
393
  (datasetTables !== undefined && datasetTables.length > 0));
391
394
  if (shouldGenerateDatasets) {
392
- await generateDatasets({
395
+ const generated = await generateDatasets({
393
396
  outputPath: datasetsPath,
394
397
  includeTables: options.allTables ? undefined : datasetTables,
395
398
  excludeTables: excludedDatasetTables,
399
+ // Context auth scaffolds a trusted runtime tenant scope on that column,
400
+ // so datasets must declare the matching tenantKey or every tenant-scoped
401
+ // request fails. This is explicit configuration, not a name heuristic.
402
+ ...(auth === 'context' ? { tenantColumn: CONTEXT_AUTH_TENANT_COLUMN } : {}),
396
403
  ...(database === 'chdb'
397
404
  ? { client: getChdbTypeGenerationClient(chdbPath) }
398
405
  : {}),
399
406
  });
407
+ logDatasetGenerationWarnings(generated?.warnings);
400
408
  generatedAnyDatasets = true;
401
409
  generatedSelectedDataset = selectedTable !== null && (options.allTables === true ||
402
410
  datasetTables?.includes(selectedTable) === true);
403
411
  }
404
412
  else {
405
- await writeFile(datasetsPath, generateDatasetsPlaceholderTemplate());
413
+ await writeFile(datasetsPath, generateDatasetsPlaceholderTemplate({ auth }));
406
414
  if (hasValidConnection) {
407
- logger.info('Skipped dataset generation. Run `hypequery generate:datasets --path ' + outputDir + ' --tables table1,table2` when ready.');
415
+ logger.info('Skipped dataset generation. Run `'
416
+ + formatRegenerateDatasetsCommand({ outputDir, auth, tables: 'table1,table2' })
417
+ + '` when ready.');
408
418
  }
409
419
  }
410
420
  logger.success(`Created datasets file (${path.relative(process.cwd(), datasetsPath)})`);
@@ -451,7 +461,7 @@ export interface IntrospectedSchema {
451
461
  if (hasValidConnection) {
452
462
  if (style === 'datasets' && !generatedAnyDatasets) {
453
463
  logger.info('Next:');
454
- logger.indent(`hypequery generate:datasets --path ${outputDir} --tables table1,table2`);
464
+ logger.indent(formatRegenerateDatasetsCommand({ outputDir, auth, tables: 'table1,table2' }));
455
465
  logger.newline();
456
466
  }
457
467
  else if (style === 'datasets' && !generatedSelectedDataset) {
@@ -10,7 +10,27 @@ export interface DatasetGeneratorOptions {
10
10
  includeTables?: string[];
11
11
  excludeTables?: string[];
12
12
  client?: TypeGenerationClickHouseClient;
13
+ /**
14
+ * Tenant column the caller has already configured as a trusted runtime tenant
15
+ * scope (for example the `tenant.column` of a scaffolded context-auth API).
16
+ *
17
+ * Only an explicit value here activates `tenantKey`; column-name heuristics
18
+ * never do. Tables that lack the column are reported as warnings instead of
19
+ * being silently left unscoped.
20
+ */
21
+ tenantColumn?: string;
13
22
  }
23
+ export type DatasetGenerationWarning = {
24
+ kind: 'tenant-key-candidate';
25
+ table: string;
26
+ columns: string[];
27
+ message: string;
28
+ } | {
29
+ kind: 'tenant-column-missing';
30
+ table: string;
31
+ column: string;
32
+ message: string;
33
+ };
14
34
  /**
15
35
  * Generate dataset definitions from ClickHouse schema
16
36
  */
@@ -20,5 +40,6 @@ export declare function generateDatasets(options: DatasetGeneratorOptions): Prom
20
40
  table: string;
21
41
  exportName: string;
22
42
  }[];
43
+ warnings: DatasetGenerationWarning[];
23
44
  }>;
24
45
  //# sourceMappingURL=dataset-generator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;CACzC;AAuPD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB;;;;;;GAmEtE"}
1
+ {"version":3,"file":"dataset-generator.d.ts","sourceRoot":"","sources":["../../src/generators/dataset-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,qBAAqB,CAAC;AAG1E,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,8BAA8B,CAAC;IACxC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAChC;IACE,IAAI,EAAE,sBAAsB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,uBAAuB,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAkSN;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB;;;;;;;GAsEtE"}
@@ -48,9 +48,12 @@ function isTimestampColumn(column) {
48
48
  name === 'updated');
49
49
  }
50
50
  /**
51
- * Check if a column is likely a tenant ID column
51
+ * Check if a column name is worth presenting as a possible tenant key.
52
+ *
53
+ * A naming match is never enough to activate tenant enforcement. `tenantKey`
54
+ * is a security boundary that must be chosen explicitly by the application.
52
55
  */
53
- function isTenantColumn(column) {
56
+ function isTenantKeyCandidate(column) {
54
57
  const name = column.name.toLowerCase();
55
58
  return (name === 'tenant_id' ||
56
59
  name === 'organization_id' ||
@@ -79,7 +82,7 @@ function isIdentifierColumn(column) {
79
82
  name.endsWith('_id') ||
80
83
  originalName.endsWith('Id') ||
81
84
  name.includes('uuid') ||
82
- isTenantColumn(column));
85
+ isTenantKeyCandidate(column));
83
86
  }
84
87
  /**
85
88
  * Check if a numeric column is a coordinate that should not be summed or averaged.
@@ -128,7 +131,7 @@ function generateLabel(columnName) {
128
131
  /**
129
132
  * Generate dataset definition code for a single table
130
133
  */
131
- async function generateDatasetForTable(client, tableName) {
134
+ async function generateDatasetForTable(client, tableName, tenantColumn) {
132
135
  // Get column information
133
136
  const columnsQuery = await client.query({
134
137
  query: `DESCRIBE TABLE ${tableName}`,
@@ -138,8 +141,34 @@ async function generateDatasetForTable(client, tableName) {
138
141
  // Determine timeKey (prefer created_at, then any timestamp column)
139
142
  const timestampColumns = columns.filter(isTimestampColumn);
140
143
  const timeKeyColumn = timestampColumns.find((c) => c.name === 'created_at') || timestampColumns[0];
141
- // Determine tenantKey
142
- const tenantColumn = columns.find(isTenantColumn);
144
+ // A tenant column is only honoured when the caller configured one explicitly.
145
+ // Column-name matches are reported for review and never become policy.
146
+ const resolvedTenantColumn = tenantColumn
147
+ ? columns.find((column) => column.name === tenantColumn)?.name
148
+ : undefined;
149
+ // Report every match, not just the first — picking the right tenant boundary
150
+ // needs the full set (e.g. both `organization_id` and `customer_id`).
151
+ const tenantCandidates = columns.filter(isTenantKeyCandidate).map((column) => column.name);
152
+ const warnings = [];
153
+ if (tenantColumn && !resolvedTenantColumn) {
154
+ warnings.push({
155
+ kind: 'tenant-column-missing',
156
+ table: tableName,
157
+ column: tenantColumn,
158
+ message: `Dataset "${tableName}" has no "${tenantColumn}" column, so tenant isolation was not applied. ` +
159
+ 'Tenant-scoped requests against it will fail until you set tenantKey to the correct column.',
160
+ });
161
+ }
162
+ if (!resolvedTenantColumn && tenantCandidates.length > 0) {
163
+ const quoted = tenantCandidates.map((name) => `"${name}"`).join(', ');
164
+ warnings.push({
165
+ kind: 'tenant-key-candidate',
166
+ table: tableName,
167
+ columns: tenantCandidates,
168
+ message: `Dataset "${tableName}" has ${tenantCandidates.length === 1 ? 'possible tenant key' : 'possible tenant keys'} ${quoted}. ` +
169
+ 'Review them and add tenantKey explicitly only after configuring trusted runtime tenant scope.',
170
+ });
171
+ }
143
172
  // Generate dimensions
144
173
  const dimensionLines = [];
145
174
  for (const column of columns) {
@@ -181,8 +210,13 @@ async function generateDatasetForTable(client, tableName) {
181
210
  if (timeKeyColumn) {
182
211
  configLines.push(` timeKey: '${timeKeyColumn.name}',`);
183
212
  }
184
- if (tenantColumn) {
185
- configLines.push(` tenantKey: '${tenantColumn.name}', // Auto-detected tenant isolation column`);
213
+ if (resolvedTenantColumn) {
214
+ configLines.push(` tenantKey: '${resolvedTenantColumn}',`);
215
+ }
216
+ else if (tenantCandidates.length > 0) {
217
+ const quoted = tenantCandidates.map((name) => `'${name}'`).join(', ');
218
+ configLines.push(` // Possible tenant ${tenantCandidates.length === 1 ? 'key' : 'keys'}: ${quoted}.`);
219
+ configLines.push(` // Add tenantKey explicitly only after configuring trusted runtime tenant scope.`);
186
220
  }
187
221
  configLines.push(` dimensions: {`);
188
222
  configLines.push(...dimensionLines);
@@ -190,10 +224,13 @@ async function generateDatasetForTable(client, tableName) {
190
224
  configLines.push(` measures: {`);
191
225
  configLines.push(...measureLines);
192
226
  configLines.push(` },`);
193
- return `export const ${datasetName}Dataset = dataset('${tableName}', {
227
+ return {
228
+ code: `export const ${datasetName}Dataset = dataset('${tableName}', {
194
229
  ${configLines.join('\n')}
195
230
  });
196
- `;
231
+ `,
232
+ warnings,
233
+ };
197
234
  }
198
235
  /**
199
236
  * Generate dataset definitions from ClickHouse schema
@@ -220,9 +257,11 @@ export async function generateDatasets(options) {
220
257
  }
221
258
  // Generate dataset definitions
222
259
  const datasetDefinitions = [];
260
+ const warnings = [];
223
261
  for (const table of tables) {
224
- const datasetCode = await generateDatasetForTable(client, table.name);
225
- datasetDefinitions.push(datasetCode);
262
+ const generated = await generateDatasetForTable(client, table.name, options.tenantColumn);
263
+ datasetDefinitions.push(generated.code);
264
+ warnings.push(...generated.warnings);
226
265
  }
227
266
  // Build the complete output file
228
267
  const header = `// Generated by @hypequery/cli
@@ -251,5 +290,6 @@ ${tables.map((table) => ` ${table.name}: ${tableToPascalCase(table.name)}Datase
251
290
  table: table.name,
252
291
  exportName: `${tableToPascalCase(table.name)}Dataset`,
253
292
  })),
293
+ warnings,
254
294
  };
255
295
  }
@@ -3,6 +3,12 @@
3
3
  * templates so the generated code stays identical across scaffold styles.
4
4
  */
5
5
  export type AuthTemplateMode = 'none' | 'context';
6
+ /**
7
+ * Tenant column the context-auth scaffold configures as its trusted runtime
8
+ * tenant scope. Generated datasets must use the same column for their
9
+ * `tenantKey`, otherwise tenant-scoped requests fail at runtime.
10
+ */
11
+ export declare const CONTEXT_AUTH_TENANT_COLUMN = "tenant_id";
6
12
  /**
7
13
  * Type guards that pull a trusted `HostUser` off the underlying request. The
8
14
  * host framework is expected to have already authenticated the request and
@@ -1 +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"}
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,0BAA0B,cAAc,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,eAAe,+qBAoBzB,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,iBAAiB,gVAUzB,CAAC"}
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Tenant column the context-auth scaffold configures as its trusted runtime
3
+ * tenant scope. Generated datasets must use the same column for their
4
+ * `tenantKey`, otherwise tenant-scoped requests fail at runtime.
5
+ */
6
+ export const CONTEXT_AUTH_TENANT_COLUMN = 'tenant_id';
1
7
  /**
2
8
  * Type guards that pull a trusted `HostUser` off the underlying request. The
3
9
  * host framework is expected to have already authenticated the request and
@@ -36,6 +42,6 @@ export const contextAuthConfig = ` auth: fromContext(({ request }) => {
36
42
  }),
37
43
  tenant: {
38
44
  extract: (auth: { tenantId?: string }) => auth.tenantId,
39
- column: 'tenant_id',
45
+ column: '${CONTEXT_AUTH_TENANT_COLUMN}',
40
46
  mode: 'auto-inject',
41
47
  },`;
@@ -1,5 +1,8 @@
1
+ import { type AuthTemplateMode } from './auth-scaffold.js';
1
2
  /**
2
3
  * Generate placeholder datasets.ts file for no-connection init.
3
4
  */
4
- export declare function generateDatasetsPlaceholderTemplate(): string;
5
+ export declare function generateDatasetsPlaceholderTemplate(options?: {
6
+ auth?: AuthTemplateMode;
7
+ }): string;
5
8
  //# sourceMappingURL=datasets.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"datasets.d.ts","sourceRoot":"","sources":["../../src/templates/datasets.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,mCAAmC,IAAI,MAAM,CAmB5D"}
1
+ {"version":3,"file":"datasets.d.ts","sourceRoot":"","sources":["../../src/templates/datasets.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,gBAAgB,EAEtB,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,wBAAgB,mCAAmC,CACjD,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,gBAAgB,CAAA;CAAO,GACxC,MAAM,CA6BR"}
@@ -1,15 +1,25 @@
1
+ import { CONTEXT_AUTH_TENANT_COLUMN, } from './auth-scaffold.js';
1
2
  /**
2
3
  * Generate placeholder datasets.ts file for no-connection init.
3
4
  */
4
- export function generateDatasetsPlaceholderTemplate() {
5
+ export function generateDatasetsPlaceholderTemplate(options = {}) {
6
+ // Context auth wires a trusted runtime tenant scope in api.ts, and tenant-scoped
7
+ // dataset requests fail unless the dataset declares the matching tenantKey.
8
+ const tenantScoped = options.auth === 'context';
9
+ const tenantKeyLine = tenantScoped
10
+ ? ` tenantKey: '${CONTEXT_AUTH_TENANT_COLUMN}',\n`
11
+ : '';
12
+ const tenantDimensionLine = tenantScoped
13
+ ? ` tenantId: dimension.string({ column: '${CONTEXT_AUTH_TENANT_COLUMN}' }),\n`
14
+ : '';
5
15
  return `import { dataset, dimension, measure } from '@hypequery/datasets';
6
16
 
7
17
  const exampleEvents = dataset('example_events', {
8
18
  source: 'example_events',
9
19
  timeKey: 'created_at',
10
- dimensions: {
20
+ ${tenantKeyLine} dimensions: {
11
21
  id: dimension.string(),
12
- createdAt: dimension.timestamp({ column: 'created_at' }),
22
+ ${tenantDimensionLine} createdAt: dimension.timestamp({ column: 'created_at' }),
13
23
  },
14
24
  measures: {
15
25
  eventCount: measure.count('id', { label: 'Event Count' }),
@@ -0,0 +1,3 @@
1
+ import type { DatasetGenerationWarning } from '../generators/dataset-generator.js';
2
+ export declare function logDatasetGenerationWarnings(warnings: readonly DatasetGenerationWarning[] | undefined): void;
3
+ //# sourceMappingURL=dataset-generation-warnings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-generation-warnings.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-generation-warnings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAGnF,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,SAAS,wBAAwB,EAAE,GAAG,SAAS,GACxD,IAAI,CAUN"}
@@ -0,0 +1,11 @@
1
+ import { logger } from './logger.js';
2
+ export function logDatasetGenerationWarnings(warnings) {
3
+ if (!warnings?.length) {
4
+ return;
5
+ }
6
+ logger.newline();
7
+ logger.warn('Review generated dataset tenant isolation:');
8
+ for (const warning of warnings) {
9
+ logger.indent(`• ${warning.message}`);
10
+ }
11
+ }
@@ -0,0 +1,16 @@
1
+ import { type AuthTemplateMode } from '../templates/auth-scaffold.js';
2
+ export interface RegenerateDatasetsCommandOptions {
3
+ outputDir: string;
4
+ auth: AuthTemplateMode;
5
+ /** Comma-separated table list to suggest, if any. */
6
+ tables?: string;
7
+ }
8
+ /**
9
+ * The `generate:datasets` invocation to hand a user after `init`.
10
+ *
11
+ * Regeneration rewrites the whole file, so a context-auth scaffold has to
12
+ * repeat its tenant column here. Without it the refresh drops `tenantKey` and
13
+ * every tenant-scoped request starts failing.
14
+ */
15
+ export declare function formatRegenerateDatasetsCommand(options: RegenerateDatasetsCommandOptions): string;
16
+ //# sourceMappingURL=regenerate-datasets-command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"regenerate-datasets-command.d.ts","sourceRoot":"","sources":["../../src/utils/regenerate-datasets-command.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAElG,MAAM,WAAW,gCAAgC;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,gBAAgB,CAAC;IACvB,qDAAqD;IACrD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,gCAAgC,GACxC,MAAM,CAYR"}
@@ -0,0 +1,18 @@
1
+ import { CONTEXT_AUTH_TENANT_COLUMN } from '../templates/auth-scaffold.js';
2
+ /**
3
+ * The `generate:datasets` invocation to hand a user after `init`.
4
+ *
5
+ * Regeneration rewrites the whole file, so a context-auth scaffold has to
6
+ * repeat its tenant column here. Without it the refresh drops `tenantKey` and
7
+ * every tenant-scoped request starts failing.
8
+ */
9
+ export function formatRegenerateDatasetsCommand(options) {
10
+ const parts = ['hypequery generate:datasets', `--path ${options.outputDir}`];
11
+ if (options.tables) {
12
+ parts.push(`--tables ${options.tables}`);
13
+ }
14
+ if (options.auth === 'context') {
15
+ parts.push(`--tenant-column ${CONTEXT_AUTH_TENANT_COLUMN}`);
16
+ }
17
+ return parts.join(' ');
18
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -30,8 +30,8 @@
30
30
  "open": "^10.0.0",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/deployment": "0.7.3",
34
- "@hypequery/protocol": "0.11.0"
33
+ "@hypequery/deployment": "0.7.4",
34
+ "@hypequery/protocol": "0.11.1"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "1.3.0"
@@ -45,7 +45,7 @@
45
45
  "@vitest/coverage-v8": "^3.2.6",
46
46
  "typescript": "^5.7.3",
47
47
  "vitest": "^3.2.6",
48
- "@hypequery/serve": "0.16.0"
48
+ "@hypequery/serve": "0.16.2"
49
49
  },
50
50
  "repository": {
51
51
  "type": "git",