@hypequery/cli 1.26.0 → 1.27.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 (33) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +2 -0
  3. package/dist/commands/deploy.d.ts +2 -0
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +17 -1
  6. package/dist/commands/generate-client.d.ts.map +1 -1
  7. package/dist/commands/generate-client.js +9 -2
  8. package/dist/generators/dataset-generator.d.ts.map +1 -1
  9. package/dist/generators/dataset-generator.js +75 -56
  10. package/dist/utils/clickhouse-dimension-type.d.ts +27 -0
  11. package/dist/utils/clickhouse-dimension-type.d.ts.map +1 -0
  12. package/dist/utils/clickhouse-dimension-type.js +61 -0
  13. package/dist/utils/clickhouse-type-utils.d.ts.map +1 -1
  14. package/dist/utils/clickhouse-type-utils.js +9 -1
  15. package/dist/utils/client-contract-file.d.ts +5 -0
  16. package/dist/utils/client-contract-file.d.ts.map +1 -1
  17. package/dist/utils/client-contract-file.js +49 -5
  18. package/dist/utils/deployment-upload.d.ts +6 -2
  19. package/dist/utils/deployment-upload.d.ts.map +1 -1
  20. package/dist/utils/deployment-upload.js +12 -2
  21. package/dist/utils/draft-deployment.d.ts +16 -0
  22. package/dist/utils/draft-deployment.d.ts.map +1 -0
  23. package/dist/utils/draft-deployment.js +35 -0
  24. package/dist/utils/field-namer.d.ts +10 -0
  25. package/dist/utils/field-namer.d.ts.map +1 -0
  26. package/dist/utils/field-namer.js +20 -0
  27. package/dist/utils/json-pointer.d.ts +3 -0
  28. package/dist/utils/json-pointer.d.ts.map +1 -0
  29. package/dist/utils/json-pointer.js +4 -0
  30. package/dist/utils/telemetry/catalog.d.ts +1 -1
  31. package/dist/utils/telemetry/domains.d.ts +4 -4
  32. package/dist/utils/telemetry/domains.js +4 -4
  33. package/package.json +5 -5
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAE9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAySD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAE9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AA2SD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -208,6 +208,7 @@ program
208
208
  .command('deployment:submit <bundle>')
209
209
  .description('Submit a prebuilt deployment bundle and release')
210
210
  .requiredOption('--release <path>', 'Target-bound release JSON path')
211
+ .option('--draft <name>', 'Save a named Cloud draft without publishing Production')
211
212
  .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
212
213
  .option('--replace-restored', 'Intentionally replace a restored live release')
213
214
  .action(runCommand(async (bundle, options) => {
@@ -231,6 +232,7 @@ program
231
232
  .option('--project <project>', 'Target project identifier (advanced override)')
232
233
  .option('--environment <environment>', 'Target environment identifier (advanced override)')
233
234
  .option('--no-source', 'Exclude project source files from the deployment bundle')
235
+ .option('--draft <name>', 'Save a named Cloud draft without publishing Production')
234
236
  .option('--endpoint <url>', 'HTTPS submission endpoint; requires HYPEQUERY_API_TOKEN')
235
237
  .option('--replace-restored', 'Intentionally replace a restored live release')
236
238
  .option('--mcp-config', 'Also print MCP client configuration for the hosted endpoint')
@@ -7,12 +7,14 @@ export interface SubmitDeploymentOptions {
7
7
  release?: string;
8
8
  endpoint?: string;
9
9
  replaceRestored?: boolean;
10
+ draft?: string;
10
11
  /** Also print MCP client configuration for the hosted endpoint. */
11
12
  mcpConfig?: boolean;
12
13
  }
13
14
  export interface DeployOptions {
14
15
  endpoint?: string;
15
16
  replaceRestored?: boolean;
17
+ draft?: string;
16
18
  project?: string;
17
19
  environment?: string;
18
20
  bundleOutput?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAEvC,OAAO,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,oBAAoB,EACrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,oBAAoB,CAAC;CACvD;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CAiFvC;AAsBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CAwCvC"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mCAAmC,EAEnC,KAAK,4BAA4B,EAElC,MAAM,+BAA+B,CAAC;AAGvC,OAAO,EACL,KAAK,qBAAqB,EAC3B,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EACL,oBAAoB,EACrB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,sBAAsB,EACtB,+BAA+B,EAChC,MAAM,iBAAiB,CAAC;AAMzB,MAAM,WAAW,uBAAuB;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mEAAmE;IACnE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,mCAAmC,CAAC;IACtE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,CAAC;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,mBAAmB,CAAC;IAChD,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,oBAAoB,CAAC;CACvD;AAED,MAAM,WAAW,kBAAmB,SAAQ,4BAA4B;IACtE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,sBAAsB,CAAC;IACzD,QAAQ,CAAC,wBAAwB,CAAC,EAAE,OAAO,+BAA+B,CAAC;IAC3E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,OAAO,uBAAuB,CAAC;CAC5D;AA4DD,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,uBAA4B,EACrC,YAAY,GAAE,4BAAiC,GAC9C,OAAO,CAAC,4BAA4B,CAAC,CA8FvC;AAsBD,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,GAAE,aAAkB,EAC3B,YAAY,GAAE,kBAAuB,GACpC,OAAO,CAAC,4BAA4B,CAAC,CA0CvC"}
@@ -3,6 +3,7 @@ import path from 'node:path';
3
3
  import { prepareProtocolDeploymentReleaseEnvelope, } from '@hypequery/protocol';
4
4
  import { DEPLOYMENT_BUNDLE_MANIFEST, verifyDeploymentBundle, } from '../utils/deployment-bundle.js';
5
5
  import { createHttpDeploymentUploadTransport, DeploymentUploadError, } from '../utils/deployment-upload.js';
6
+ import { validateDraftDeploymentOptions } from '../utils/draft-deployment.js';
6
7
  import { logger } from '../utils/logger.js';
7
8
  import { resolveDeploymentCredential } from '../utils/cloud-deployment-access.js';
8
9
  import { fetchLiveDeployment } from '../utils/live-deployment.js';
@@ -73,6 +74,7 @@ export async function submitDeploymentCommand(bundlePath, options = {}, dependen
73
74
  + 'Usage: hypequery deployment:submit analytics/hypequery-deployment '
74
75
  + '--release analytics/hypequery-deployment.release.json');
75
76
  }
77
+ validateDraftDeploymentOptions(options);
76
78
  const releasePath = requiredConfiguration(options.release, 'Missing required --release <path>.');
77
79
  const { endpoint: deploymentEndpoint, token } = await resolveDeploymentCredential(options.endpoint, dependencies);
78
80
  let bundle;
@@ -95,7 +97,7 @@ export async function submitDeploymentCommand(bundlePath, options = {}, dependen
95
97
  if (release.release.bundleIdentity !== bundle.identity) {
96
98
  throw new DeploymentUploadError('HQ_UPLOAD_IDENTITY_MISMATCH', 'Release bundle identity does not match the verified deployment bundle.');
97
99
  }
98
- const live = await (dependencies.fetchLive ?? fetchLiveDeployment)({
100
+ const live = options.draft !== undefined ? null : await (dependencies.fetchLive ?? fetchLiveDeployment)({
99
101
  endpoint: deploymentEndpoint,
100
102
  token,
101
103
  target: release.release.target,
@@ -115,8 +117,20 @@ export async function submitDeploymentCommand(bundlePath, options = {}, dependen
115
117
  ? { expectedActivationRevision: live.active?.revision ?? null }
116
118
  : {}),
117
119
  ...(options.replaceRestored ? { replaceRestored: true } : {}),
120
+ ...(options.draft !== undefined ? { draft: options.draft } : {}),
118
121
  };
119
122
  const result = await createTransport(transportOptions).submit(bundle, release);
123
+ if (options.draft !== undefined) {
124
+ if (!result.draft || result.draft.name !== options.draft) {
125
+ throw new DeploymentUploadError('HQ_UPLOAD_INVALID_RESPONSE', 'Deployment service did not acknowledge the requested draft.');
126
+ }
127
+ logger.success(`Draft saved: ${result.draft.name}`);
128
+ logger.info(`Draft ID: ${result.draft.draftId}`);
129
+ logger.info(`Staging endpoint: ${result.draft.stagingUrl}`);
130
+ logger.info(`Release identity: ${result.releaseIdentity}`);
131
+ logger.info('Review and publish this draft in Cloud when ready.');
132
+ return result;
133
+ }
120
134
  logger.success(result.status === 'accepted'
121
135
  ? 'Deployment release accepted'
122
136
  : 'Deployment release already exists');
@@ -150,6 +164,7 @@ async function rejectBundleDirectorySource(sourcePath) {
150
164
  + `${sourcePath} --release <file>\`.`);
151
165
  }
152
166
  export async function deployCommand(sourcePath, options = {}, dependencies = {}) {
167
+ validateDraftDeploymentOptions(options);
153
168
  if (!sourcePath) {
154
169
  throw new Error('Missing Cloud publication module path.\n\n'
155
170
  + 'Usage: hypequery deploy analytics/cloud.ts');
@@ -182,5 +197,6 @@ export async function deployCommand(sourcePath, options = {}, dependencies = {})
182
197
  endpoint: options.endpoint,
183
198
  replaceRestored: options.replaceRestored,
184
199
  mcpConfig: options.mcpConfig,
200
+ ...(options.draft !== undefined ? { draft: options.draft } : {}),
185
201
  }, dependencies);
186
202
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-client.d.ts","sourceRoot":"","sources":["../../src/commands/generate-client.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IAAG,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAE5F,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhH"}
1
+ {"version":3,"file":"generate-client.d.ts","sourceRoot":"","sources":["../../src/commands/generate-client.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IAAG,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAE5F,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CAoChH"}
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { decodeProtocolClientContract } from '@hypequery/protocol';
3
3
  import { renderClientContract } from '../generators/client-contract/render.js';
4
- import { checkClientOutputPath, readClientArtifact, readClientOutput } from '../utils/client-contract-file.js';
4
+ import { checkClientOutputPath, describeClientContractError, readClientArtifact, readClientOutput } from '../utils/client-contract-file.js';
5
5
  import { writeGeneratedFileAtomically } from '../utils/generated-file.js';
6
6
  import { logger } from '../utils/logger.js';
7
7
  import { updateCommandTelemetry, setCommandTelemetryError } from '../utils/telemetry/command-context.js';
@@ -16,7 +16,14 @@ export async function generateClientCommand(artifact, options = {}) {
16
16
  throw new Error('Client input and output must be different files.');
17
17
  await checkClientOutputPath(output);
18
18
  const current = await readClientOutput(output);
19
- const contract = decodeProtocolClientContract(await readClientArtifact(input));
19
+ const text = await readClientArtifact(input);
20
+ let contract;
21
+ try {
22
+ contract = decodeProtocolClientContract(text);
23
+ }
24
+ catch (error) {
25
+ throw describeClientContractError(error);
26
+ }
20
27
  if (contract.view !== 'build')
21
28
  throw new Error('Client generation requires a build view exported by the definition owner.');
22
29
  const contents = renderClientContract(contract);
@@ -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;IACxC;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,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;;;;;;;;GAyEtE"}
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;AAS1E,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;IACtB,8EAA8E;IAC9E,WAAW,CAAC,EAAE,OAAO,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;AAuTN;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,uBAAuB;;;;;;;;GAyEtE"}
@@ -7,45 +7,14 @@
7
7
  import fs from 'fs/promises';
8
8
  import path from 'path';
9
9
  import { getClickHouseClient } from '../utils/clickhouse-client.js';
10
+ import { clickHouseDimensionType, namedTupleElements, } from '../utils/clickhouse-dimension-type.js';
11
+ import { createFieldNamer } from '../utils/field-namer.js';
12
+ const SQL_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
10
13
  /**
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
14
+ * Whether a column can be the dataset's timeKey: its values are dates or times.
39
15
  */
40
16
  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');
17
+ return clickHouseDimensionType(column.type) === 'timestamp';
49
18
  }
50
19
  /**
51
20
  * Check if a column name is worth presenting as a possible tenant key.
@@ -65,11 +34,7 @@ function isTenantKeyCandidate(column) {
65
34
  * Check if a column is numeric and suitable for measures
66
35
  */
67
36
  function isNumericColumn(column) {
68
- const type = column.type.toLowerCase();
69
- return (type.includes('int') ||
70
- type.includes('float') ||
71
- type.includes('double') ||
72
- type.includes('decimal'));
37
+ return clickHouseDimensionType(column.type) === 'number';
73
38
  }
74
39
  /**
75
40
  * Check if a numeric column is more likely an identifier than an additive value.
@@ -169,39 +134,93 @@ async function generateDatasetForTable(client, tableName, tenantColumn) {
169
134
  'Review them and add tenantKey explicitly only after configuring trusted runtime tenant scope.',
170
135
  });
171
136
  }
172
- // Generate dimensions
137
+ // Generate dimensions. A named tuple becomes one dimension per scalar
138
+ // element; columns no dimension type describes are left out with a note.
139
+ const fields = [];
140
+ const skippedColumns = [];
141
+ for (const column of columns) {
142
+ const dimensionType = clickHouseDimensionType(column.type);
143
+ if (dimensionType) {
144
+ fields.push({ column, type: dimensionType, name: columnToCamelCase(column.name) });
145
+ continue;
146
+ }
147
+ const elements = SQL_IDENTIFIER.test(column.name) ? namedTupleElements(column.type) : null;
148
+ if (elements?.length) {
149
+ for (const element of elements) {
150
+ fields.push({
151
+ column,
152
+ element: element.name,
153
+ type: element.type,
154
+ name: columnToCamelCase(`${column.name}_${element.name}`),
155
+ });
156
+ }
157
+ continue;
158
+ }
159
+ skippedColumns.push(column);
160
+ }
161
+ // Field names are SQL aliases, so none may shadow a source column another
162
+ // field reads. A column spelled like its field keeps that name, then other
163
+ // columns, then tuple elements; a clash takes a numeric suffix instead.
164
+ const nameField = createFieldNamer(columns.map((column) => column.name));
165
+ const naming = [
166
+ ...fields.filter((field) => !field.element && field.name === field.column.name),
167
+ ...fields.filter((field) => !field.element && field.name !== field.column.name),
168
+ ...fields.filter((field) => field.element),
169
+ ];
170
+ for (const field of naming) {
171
+ const name = nameField(field.name, field.element ? undefined : field.column.name);
172
+ if (name !== field.name)
173
+ field.renamedFrom = field.name;
174
+ field.name = name;
175
+ }
176
+ const fieldNameByColumn = new Map(fields.filter((field) => !field.element).map((field) => [field.column.name, field.name]));
173
177
  const dimensionLines = [];
174
178
  for (const column of columns) {
175
- const fieldName = columnToCamelCase(column.name);
176
- const dimensionType = clickhouseToDimensionType(column.type);
177
- const label = generateLabel(column.name);
178
- // Skip if column name === fieldName (no transformation needed)
179
- if (column.name === fieldName) {
180
- dimensionLines.push(` ${fieldName}: dimension.${dimensionType}({ label: '${label}' }),`);
179
+ if (skippedColumns.includes(column)) {
180
+ // ClickHouse can spell a nested type over several lines; keep the note on one comment line.
181
+ const type = column.type.replace(/\s+/g, ' ');
182
+ dimensionLines.push(` // ${column.name} (${type}) is not included: no dimension type describes its values.`);
183
+ continue;
181
184
  }
182
- else {
183
- dimensionLines.push(` ${fieldName}: dimension.${dimensionType}({ column: '${column.name}', label: '${label}' }),`);
185
+ for (const field of fields.filter((candidate) => candidate.column === column)) {
186
+ const label = generateLabel(column.name);
187
+ if (field.renamedFrom) {
188
+ dimensionLines.push(` // ${field.element ? `${column.name}.${field.element}` : column.name} is ${field.name}: ${field.renamedFrom} is taken by another field or column.`);
189
+ }
190
+ if (field.element) {
191
+ dimensionLines.push(` ${field.name}: dimension.${field.type}({ sql: "tupleElement(${column.name}, '${field.element}')", ` +
192
+ `dependencies: ['${column.name}'], label: '${label} ${generateLabel(field.element)}' }),`);
193
+ }
194
+ else if (column.name === field.name) {
195
+ // No column option needed when the field is spelled like the column
196
+ dimensionLines.push(` ${field.name}: dimension.${field.type}({ label: '${label}' }),`);
197
+ }
198
+ else {
199
+ dimensionLines.push(` ${field.name}: dimension.${field.type}({ column: '${column.name}', label: '${label}' }),`);
200
+ }
184
201
  }
185
202
  }
186
203
  // Generate measures for numeric value columns only. IDs and coordinates stay
187
204
  // dimensions because summing or averaging them is usually noise.
188
205
  const numericColumns = columns.filter(isMeasureCandidate);
189
206
  const measureLines = [];
207
+ const countField = fields[0]?.name;
190
208
  if (numericColumns.length > 0) {
191
209
  measureLines.push(` // Count measures`);
192
- measureLines.push(` totalCount: measure.count('${columnToCamelCase(columns[0].name)}', { label: 'Total Count' }),`);
210
+ measureLines.push(` ${nameField('totalCount')}: measure.count('${countField}', { label: 'Total Count' }),`);
193
211
  measureLines.push(``);
194
212
  for (const column of numericColumns) {
195
- const fieldName = columnToCamelCase(column.name);
213
+ const fieldName = fieldNameByColumn.get(column.name);
214
+ const measureSuffix = fieldName.charAt(0).toUpperCase() + fieldName.slice(1);
196
215
  const label = generateLabel(column.name);
197
216
  measureLines.push(` // ${label} measures`);
198
- measureLines.push(` total${tableToPascalCase(column.name)}: measure.sum('${fieldName}', { label: 'Total ${label}' }),`);
199
- measureLines.push(` avg${tableToPascalCase(column.name)}: measure.avg('${fieldName}', { label: 'Average ${label}' }),`);
217
+ measureLines.push(` ${nameField(`total${measureSuffix}`)}: measure.sum('${fieldName}', { label: 'Total ${label}' }),`);
218
+ measureLines.push(` ${nameField(`avg${measureSuffix}`)}: measure.avg('${fieldName}', { label: 'Average ${label}' }),`);
200
219
  measureLines.push(``);
201
220
  }
202
221
  }
203
- else {
204
- measureLines.push(` totalCount: measure.count('${columnToCamelCase(columns[0].name)}', { label: 'Total Count' }),`);
222
+ else if (countField) {
223
+ measureLines.push(` ${nameField('totalCount')}: measure.count('${countField}', { label: 'Total Count' }),`);
205
224
  }
206
225
  // Build the dataset definition
207
226
  const datasetName = tableToPascalCase(tableName);
@@ -0,0 +1,27 @@
1
+ /** A dataset dimension type the scaffolder can emit. */
2
+ export type ScaffoldDimensionType = 'string' | 'number' | 'boolean' | 'timestamp';
3
+ export interface NamedTupleElement {
4
+ name: string;
5
+ type: ScaffoldDimensionType;
6
+ }
7
+ /**
8
+ * The value type behind `Nullable`, `LowCardinality` and
9
+ * `SimpleAggregateFunction(fn, T)` wrappers; none changes what a value is.
10
+ */
11
+ export declare function unwrapClickHouseType(type: string): string;
12
+ /**
13
+ * Maps a ClickHouse column type to a dataset dimension type, or null when no
14
+ * scalar dimension type describes its values (Array, Tuple, Map, geo, JSON,
15
+ * Interval, ...). The outer type name is matched exactly: a substring match
16
+ * reads `Point` or `Array(Int32)` as numbers. Mirrors Cloud's `protocolTypeOf`
17
+ * so a table scaffolds with the same field types in both.
18
+ */
19
+ export declare function clickHouseDimensionType(type: string): ScaffoldDimensionType | null;
20
+ /**
21
+ * The scalar elements of a named `Tuple(name T, ...)` column, each of which can
22
+ * be its own dimension through `tupleElement`. Null for any other type,
23
+ * including unnamed tuples, whose positions carry no meaning to name a field
24
+ * after. Elements without a scalar type or a plain identifier name are left out.
25
+ */
26
+ export declare function namedTupleElements(type: string): NamedTupleElement[] | null;
27
+ //# sourceMappingURL=clickhouse-dimension-type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clickhouse-dimension-type.d.ts","sourceRoot":"","sources":["../../src/utils/clickhouse-dimension-type.ts"],"names":[],"mappings":"AAEA,wDAAwD;AACxD,MAAM,MAAM,qBAAqB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;AAElF,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,qBAAqB,CAAC;CAC7B;AAID;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASzD;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAOlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,EAAE,GAAG,IAAI,CAgB3E"}
@@ -0,0 +1,61 @@
1
+ import { matchTypeCall } from './clickhouse-type-utils.js';
2
+ const IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
3
+ /**
4
+ * The value type behind `Nullable`, `LowCardinality` and
5
+ * `SimpleAggregateFunction(fn, T)` wrappers; none changes what a value is.
6
+ */
7
+ export function unwrapClickHouseType(type) {
8
+ let current = type.trim();
9
+ const simple = /^SimpleAggregateFunction\(\s*[A-Za-z_][A-Za-z0-9_]*\s*,\s*(.+)\)$/s.exec(current);
10
+ if (simple)
11
+ current = simple[1].trim();
12
+ for (;;) {
13
+ const wrapped = /^(?:Nullable|LowCardinality)\((.*)\)$/s.exec(current);
14
+ if (!wrapped)
15
+ return current;
16
+ current = wrapped[1].trim();
17
+ }
18
+ }
19
+ /**
20
+ * Maps a ClickHouse column type to a dataset dimension type, or null when no
21
+ * scalar dimension type describes its values (Array, Tuple, Map, geo, JSON,
22
+ * Interval, ...). The outer type name is matched exactly: a substring match
23
+ * reads `Point` or `Array(Int32)` as numbers. Mirrors Cloud's `protocolTypeOf`
24
+ * so a table scaffolds with the same field types in both.
25
+ */
26
+ export function clickHouseDimensionType(type) {
27
+ const value = unwrapClickHouseType(type);
28
+ if (/^(?:U?Int\d+|Float\d+|Decimal\d*)(?:\(.*\))?$/s.test(value))
29
+ return 'number';
30
+ if (/^(?:Date|Date32|DateTime|DateTime64)(?:\(.*\))?$/s.test(value))
31
+ return 'timestamp';
32
+ if (value === 'Bool')
33
+ return 'boolean';
34
+ if (/^(?:String|FixedString|UUID|Enum\d*|IPv4|IPv6)(?:\(.*\))?$/s.test(value))
35
+ return 'string';
36
+ return null;
37
+ }
38
+ /**
39
+ * The scalar elements of a named `Tuple(name T, ...)` column, each of which can
40
+ * be its own dimension through `tupleElement`. Null for any other type,
41
+ * including unnamed tuples, whose positions carry no meaning to name a field
42
+ * after. Elements without a scalar type or a plain identifier name are left out.
43
+ */
44
+ export function namedTupleElements(type) {
45
+ const parts = matchTypeCall(unwrapClickHouseType(type), 'Tuple');
46
+ if (!parts?.length)
47
+ return null;
48
+ const elements = [];
49
+ for (const part of parts) {
50
+ const named = /^(\S+)\s+(.+)$/s.exec(part);
51
+ // Element types never contain top-level whitespace before their first
52
+ // `(`, so a part without a name-then-space prefix is an unnamed element.
53
+ if (!named || named[1].includes('('))
54
+ return null;
55
+ const elementType = clickHouseDimensionType(named[2]);
56
+ if (IDENTIFIER.test(named[1]) && elementType) {
57
+ elements.push({ name: named[1], type: elementType });
58
+ }
59
+ }
60
+ return elements;
61
+ }
@@ -1 +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"}
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,CAqDzD;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"}
@@ -3,10 +3,18 @@ export function splitTopLevelArgs(value) {
3
3
  let current = '';
4
4
  let depth = 0;
5
5
  let quote = null;
6
+ let escaped = false;
6
7
  for (const char of value) {
7
8
  if (quote) {
8
9
  current += char;
9
- if (char === quote) {
10
+ // ClickHouse escapes a quote inside a literal with a backslash (`'can\'t'`).
11
+ if (escaped) {
12
+ escaped = false;
13
+ }
14
+ else if (char === '\\') {
15
+ escaped = true;
16
+ }
17
+ else if (char === quote) {
10
18
  quote = null;
11
19
  }
12
20
  continue;
@@ -3,4 +3,9 @@ export declare function checkClientOutputPath(filePath: string): Promise<void>;
3
3
  /** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
4
4
  export declare function readClientArtifact(filePath: string, maxBytes?: number): Promise<string>;
5
5
  export declare function readClientOutput(filePath: string): Promise<string | undefined>;
6
+ /**
7
+ * Say where a client contract is invalid. An unknown field usually means the
8
+ * contract was exported by a newer hypequery than this CLI understands.
9
+ */
10
+ export declare function describeClientContractError(error: unknown): unknown;
6
11
  //# sourceMappingURL=client-contract-file.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client-contract-file.d.ts","sourceRoot":"","sources":["../../src/utils/client-contract-file.ts"],"names":[],"mappings":"AAIA,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3E;AAED,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhG;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAMpF"}
1
+ {"version":3,"file":"client-contract-file.d.ts","sourceRoot":"","sources":["../../src/utils/client-contract-file.ts"],"names":[],"mappings":"AAMA,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqB3E;AAsBD,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhG;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAMpF;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAQnE"}
@@ -1,18 +1,27 @@
1
1
  import { constants } from 'node:fs';
2
- import { lstat, open } from 'node:fs/promises';
2
+ import { lstat, open, realpath, stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
+ import { ProtocolClientContractError } from '@hypequery/protocol';
5
+ import { pointer } from './json-pointer.js';
4
6
  /** Refuse filesystem indirection for generated output and its existing ancestors. */
5
7
  export async function checkClientOutputPath(filePath) {
6
- let current = path.resolve(filePath);
8
+ const target = path.resolve(filePath);
9
+ let current = target;
7
10
  while (true) {
8
11
  try {
9
12
  const info = await lstat(current);
10
- if (info.isSymbolicLink() || (current === path.resolve(filePath) ? !info.isFile() : !info.isDirectory())) {
11
- throw new Error('Client output must be a regular file under ordinary directories.');
13
+ if (info.isSymbolicLink())
14
+ throw await throughLink(filePath, target, current);
15
+ if (current === target ? !info.isFile() : !info.isDirectory()) {
16
+ throw new Error(current === target
17
+ ? `Client output ${filePath} must be a regular file.`
18
+ : `Client output ${filePath} must be a regular file under ordinary directories: ${current} is not a directory.`);
12
19
  }
13
20
  }
14
21
  catch (error) {
15
- if (error.code !== 'ENOENT')
22
+ // Missing, or below a file: the ancestor that exists is checked next.
23
+ const code = error.code;
24
+ if (code !== 'ENOENT' && code !== 'ENOTDIR')
16
25
  throw error;
17
26
  }
18
27
  const parent = path.dirname(current);
@@ -21,6 +30,27 @@ export async function checkClientOutputPath(filePath) {
21
30
  current = parent;
22
31
  }
23
32
  }
33
+ /**
34
+ * Name the link, and the same output without it, e.g. /tmp → /private/tmp on macOS.
35
+ * The path is suggested only when the link resolves to what that position needs:
36
+ * a directory above the output, a regular file at the output itself.
37
+ */
38
+ async function throughLink(filePath, target, link) {
39
+ const message = link === target
40
+ ? `Client output ${filePath} must be a regular file, not a symbolic link.`
41
+ : `Client output ${filePath} must be a regular file under ordinary directories: ${link} is a symbolic link.`;
42
+ try {
43
+ const real = await realpath(link);
44
+ const info = await stat(real);
45
+ if (link === target ? !info.isFile() : !info.isDirectory())
46
+ return new Error(message);
47
+ const resolved = path.join(real, path.relative(link, target));
48
+ return new Error(`${message} Use the path it points to instead: ${resolved}`);
49
+ }
50
+ catch {
51
+ return new Error(message);
52
+ }
53
+ }
24
54
  /** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
25
55
  export async function readClientArtifact(filePath, maxBytes = 1_048_576) {
26
56
  const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
@@ -56,3 +86,17 @@ export async function readClientOutput(filePath) {
56
86
  throw error;
57
87
  }
58
88
  }
89
+ /**
90
+ * Say where a client contract is invalid. An unknown field usually means the
91
+ * contract was exported by a newer hypequery than this CLI understands.
92
+ */
93
+ export function describeClientContractError(error) {
94
+ if (!(error instanceof ProtocolClientContractError))
95
+ return error;
96
+ const at = error.field === undefined ? error.path : pointer(error.path, error.field);
97
+ const where = at ? ` at ${at}` : '';
98
+ const hint = error.code === 'HQ_CLIENT_UNKNOWN_FIELD'
99
+ ? ' The contract may come from a newer hypequery than this CLI; upgrade @hypequery/cli.'
100
+ : '';
101
+ return new Error(`${error.message.replace(/\.$/, '')}${where} (${error.code}).${hint}`, { cause: error });
102
+ }
@@ -1,8 +1,11 @@
1
1
  import type { ReadableStream } from 'node:stream/web';
2
2
  import { type PreparedProtocolDeploymentReleaseEnvelope } from '@hypequery/protocol';
3
3
  import { type VerifiedDeploymentBundle } from './deployment-bundle.js';
4
- import type { DeploymentSubmissionResponse } from '@hypequery/deployment';
5
- export type { DeploymentSubmissionResponse } from '@hypequery/deployment';
4
+ import type { DeploymentSubmissionResponse as BaseDeploymentSubmissionResponse } from '@hypequery/deployment';
5
+ import { type DraftDeploymentResult } from './draft-deployment.js';
6
+ export type DeploymentSubmissionResponse = BaseDeploymentSubmissionResponse & {
7
+ readonly draft?: DraftDeploymentResult;
8
+ };
6
9
  export type DeploymentUploadErrorCode = 'HQ_UPLOAD_CONFIGURATION' | 'HQ_UPLOAD_IDENTITY_MISMATCH' | 'HQ_UPLOAD_BUNDLE_CHANGED' | 'HQ_UPLOAD_NETWORK' | 'HQ_UPLOAD_REJECTED' | 'HQ_UPLOAD_INVALID_RESPONSE';
7
10
  export declare class DeploymentUploadError extends Error {
8
11
  readonly code: DeploymentUploadErrorCode;
@@ -29,6 +32,7 @@ export interface HttpDeploymentUploadTransportOptions {
29
32
  readonly token: string;
30
33
  readonly expectedActivationRevision?: string | null;
31
34
  readonly replaceRestored?: boolean;
35
+ readonly draft?: string;
32
36
  readonly timeoutMs?: number;
33
37
  readonly fetch?: DeploymentFetch;
34
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAE1E,YAAY,EAAE,4BAA4B,EAAE,MAAM,uBAAuB,CAAC;AAQ1E,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,0BAA0B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AA+WD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,yBAAyB,CA+F3B"}
1
+ {"version":3,"file":"deployment-upload.d.ts","sourceRoot":"","sources":["../../src/utils/deployment-upload.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAIL,KAAK,yCAAyC,EAC/C,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAAE,4BAA4B,IAAI,gCAAgC,EAAE,MAAM,uBAAuB,CAAC;AAC9G,OAAO,EAA8D,KAAK,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE/H,MAAM,MAAM,4BAA4B,GAAG,gCAAgC,GAAG;IAC5E,QAAQ,CAAC,KAAK,CAAC,EAAE,qBAAqB,CAAC;CACxC,CAAC;AAQF,MAAM,MAAM,yBAAyB,GACjC,yBAAyB,GACzB,6BAA6B,GAC7B,0BAA0B,GAC1B,mBAAmB,GACnB,oBAAoB,GACpB,4BAA4B,CAAC;AAEjC,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;gBAEb,IAAI,EAAE,yBAAyB,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAM9E;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,mBAAmB,KACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;AAErC,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,0BAA0B,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,CACJ,MAAM,EAAE,wBAAwB,EAChC,OAAO,EAAE,yCAAyC,GACjD,OAAO,CAAC,4BAA4B,CAAC,CAAC;CAC1C;AAoXD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,yBAAyB,CAqG3B"}
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { prepareProtocolDeploymentBundleManifest, prepareProtocolDeploymentReleaseEnvelope, } from '@hypequery/protocol';
5
5
  import { DEPLOYMENT_BUNDLE_MANIFEST, } from './deployment-bundle.js';
6
6
  import { logger } from './logger.js';
7
+ import { parseDraftDeploymentResult, validateDraftDeploymentOptions } from './draft-deployment.js';
7
8
  const SHA256_PATTERN = /^[0-9a-f]{64}$/;
8
9
  const MAX_RESPONSE_BYTES = 64 * 1024;
9
10
  const DEFAULT_TIMEOUT_MS = 120_000;
@@ -249,15 +250,18 @@ function exactRecord(value, fields) {
249
250
  }
250
251
  return record;
251
252
  }
252
- function validateSubmissionResponse(input, bundleIdentity, releaseIdentity, status) {
253
+ function validateSubmissionResponse(input, bundleIdentity, releaseIdentity, status, expectedDraft) {
253
254
  const value = exactRecord(input, [
254
255
  'kind',
255
256
  'version',
256
257
  'status',
257
258
  'releaseIdentity',
258
259
  'bundleIdentity',
260
+ ...(expectedDraft ? ['draft'] : []),
259
261
  ]);
262
+ const draft = expectedDraft && value ? parseDraftDeploymentResult(value.draft, expectedDraft) : undefined;
260
263
  if (!value
264
+ || (expectedDraft && !draft)
261
265
  || value.kind !== 'hypequery-deployment-submission'
262
266
  || value.version !== 1
263
267
  || (value.status !== 'accepted' && value.status !== 'already-exists')
@@ -275,6 +279,7 @@ function validateSubmissionResponse(input, bundleIdentity, releaseIdentity, stat
275
279
  status: value.status,
276
280
  releaseIdentity: value.releaseIdentity,
277
281
  bundleIdentity: value.bundleIdentity,
282
+ ...(draft ? { draft } : {}),
278
283
  });
279
284
  }
280
285
  function rejectedMessage(response, body) {
@@ -319,6 +324,10 @@ function nestedUploadError(input) {
319
324
  }
320
325
  const defaultFetch = async (input, init) => fetch(input, init);
321
326
  export function createHttpDeploymentUploadTransport(options) {
327
+ validateDraftDeploymentOptions(options);
328
+ if (options.draft !== undefined && options.expectedActivationRevision !== undefined) {
329
+ configurationError('Draft uploads cannot specify an expected Production activation revision.');
330
+ }
322
331
  const endpoint = endpointUrl(options.endpoint);
323
332
  const token = bearerToken(options.token);
324
333
  const timeoutMs = timeout(options.timeoutMs);
@@ -347,6 +356,7 @@ export function createHttpDeploymentUploadTransport(options) {
347
356
  'Idempotency-Key': preparedRelease.identity,
348
357
  'X-HypeQuery-Bundle-Identity': manifest.identity,
349
358
  'X-HypeQuery-Release-Identity': preparedRelease.identity,
359
+ ...(options.draft !== undefined ? { 'X-HypeQuery-Draft-Name': options.draft } : {}),
350
360
  ...(options.expectedActivationRevision !== undefined
351
361
  ? {
352
362
  'X-HypeQuery-Expected-Activation-Revision': options.expectedActivationRevision ?? 'none',
@@ -387,7 +397,7 @@ export function createHttpDeploymentUploadTransport(options) {
387
397
  catch {
388
398
  throw new DeploymentUploadError('HQ_UPLOAD_INVALID_RESPONSE', 'Deployment service returned invalid JSON.', response.status);
389
399
  }
390
- return validateSubmissionResponse(input, manifest.identity, preparedRelease.identity, response.status);
400
+ return validateSubmissionResponse(input, manifest.identity, preparedRelease.identity, response.status, options.draft !== undefined ? { name: options.draft, endpoint, project: preparedRelease.release.target.project } : undefined);
391
401
  },
392
402
  };
393
403
  return Object.freeze(transport);
@@ -0,0 +1,16 @@
1
+ export interface DraftDeploymentResult {
2
+ readonly draftId: string;
3
+ readonly name: string;
4
+ readonly stagingUrl: string;
5
+ }
6
+ export declare function validateDraftDeploymentOptions(options: {
7
+ draft?: string;
8
+ replaceRestored?: boolean;
9
+ }): void;
10
+ /** Accept only the requested draft and a staging endpoint on the deployment service. */
11
+ export declare function parseDraftDeploymentResult(input: unknown, expected: {
12
+ name: string;
13
+ endpoint: string;
14
+ project: string;
15
+ }): DraftDeploymentResult | undefined;
16
+ //# sourceMappingURL=draft-deployment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-deployment.d.ts","sourceRoot":"","sources":["../../src/utils/draft-deployment.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,wBAAgB,8BAA8B,CAAC,OAAO,EAAE;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,IAAI,CAQP;AAED,wFAAwF;AACxF,wBAAgB,0BAA0B,CACxC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC5D,qBAAqB,GAAG,SAAS,CAmBnC"}
@@ -0,0 +1,35 @@
1
+ export function validateDraftDeploymentOptions(options) {
2
+ if (options.draft === undefined)
3
+ return;
4
+ if (options.draft.length > 64 || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(options.draft)) {
5
+ throw new Error('--draft must be a lowercase name with letters, numbers and single hyphens (max 64 characters).');
6
+ }
7
+ if (options.replaceRestored) {
8
+ throw new Error('--draft cannot be combined with --replace-restored; draft uploads do not replace Production.');
9
+ }
10
+ }
11
+ /** Accept only the requested draft and a staging endpoint on the deployment service. */
12
+ export function parseDraftDeploymentResult(input, expected) {
13
+ if (typeof input !== 'object' || input === null || Array.isArray(input)
14
+ || Object.getPrototypeOf(input) !== Object.prototype)
15
+ return undefined;
16
+ const value = input;
17
+ if (Object.keys(value).sort().join(',') !== 'draftId,name,stagingUrl'
18
+ || typeof value.draftId !== 'string'
19
+ || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(value.draftId)
20
+ || value.name !== expected.name
21
+ || typeof value.stagingUrl !== 'string')
22
+ return undefined;
23
+ let url;
24
+ try {
25
+ url = new URL(value.stagingUrl, expected.endpoint);
26
+ }
27
+ catch {
28
+ return undefined;
29
+ }
30
+ if (url.origin !== new URL(expected.endpoint).origin || url.username || url.password
31
+ || url.search || url.hash
32
+ || url.pathname !== `/api/gateway/${encodeURIComponent(expected.project)}/draft/${value.draftId}`)
33
+ return undefined;
34
+ return Object.freeze({ draftId: value.draftId, name: expected.name, stagingUrl: url.href });
35
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Hands out the field names of one generated dataset. A field's name is also
3
+ * its SQL alias, and ClickHouse resolves an alias before a column of the same
4
+ * name, so a field may take a source column's name only when it reads exactly
5
+ * that column; otherwise every expression reading that column would read the
6
+ * aliased field instead. A name that is taken, or is another column's name,
7
+ * gets the lowest free numeric suffix (`fareTotal2`).
8
+ */
9
+ export declare function createFieldNamer(columnNames: readonly string[]): (base: string, ownColumn?: string) => string;
10
+ //# sourceMappingURL=field-namer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"field-namer.d.ts","sourceRoot":"","sources":["../../src/utils/field-namer.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,IAGrD,MAAM,MAAM,EAAE,YAAY,MAAM,KAAG,MAAM,CAQlD"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Hands out the field names of one generated dataset. A field's name is also
3
+ * its SQL alias, and ClickHouse resolves an alias before a column of the same
4
+ * name, so a field may take a source column's name only when it reads exactly
5
+ * that column; otherwise every expression reading that column would read the
6
+ * aliased field instead. A name that is taken, or is another column's name,
7
+ * gets the lowest free numeric suffix (`fareTotal2`).
8
+ */
9
+ export function createFieldNamer(columnNames) {
10
+ const columns = new Set(columnNames);
11
+ const taken = new Set();
12
+ return (base, ownColumn) => {
13
+ let name = base;
14
+ for (let suffix = 2; taken.has(name) || (columns.has(name) && name !== ownColumn); suffix += 1) {
15
+ name = `${base}${suffix}`;
16
+ }
17
+ taken.add(name);
18
+ return name;
19
+ };
20
+ }
@@ -0,0 +1,3 @@
1
+ /** RFC 6901: append *key* to the JSON pointer *path*. */
2
+ export declare function pointer(path: string, key: string): string;
3
+ //# sourceMappingURL=json-pointer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json-pointer.d.ts","sourceRoot":"","sources":["../../src/utils/json-pointer.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzD"}
@@ -0,0 +1,4 @@
1
+ /** RFC 6901: append *key* to the JSON pointer *path*. */
2
+ export function pointer(path, key) {
3
+ return `${path}/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`;
4
+ }
@@ -625,7 +625,7 @@ export declare const EVENT_CATALOG: {
625
625
  readonly flags_used: {
626
626
  readonly kind: "enum_list";
627
627
  readonly description: string;
628
- readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--client-contract-url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud-url" | "--environment" | "--bundle-output" | "--release-output" | "--project" | "--no-source" | "--endpoint" | "--replace-restored" | "--mcp-config" | "--release" | "--no-telemetry" | "--help" | "--version")[];
628
+ readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--client-contract-url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud-url" | "--environment" | "--bundle-output" | "--release-output" | "--project" | "--no-source" | "--endpoint" | "--replace-restored" | "--mcp-config" | "--draft" | "--release" | "--no-telemetry" | "--help" | "--version")[];
629
629
  };
630
630
  readonly outcome: {
631
631
  readonly kind: "enum";
@@ -18,11 +18,11 @@ export declare const COMMAND_FLAGS: {
18
18
  readonly 'generate:client': readonly ["--output", "--check", "--force"];
19
19
  readonly login: readonly ["--cloud-url", "--environment"];
20
20
  readonly logout: readonly [];
21
- readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--no-source", "--endpoint", "--replace-restored", "--mcp-config"];
21
+ readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--no-source", "--endpoint", "--replace-restored", "--mcp-config", "--draft"];
22
22
  readonly 'deployment:build': readonly ["--bundle-output", "--no-source"];
23
23
  readonly 'deployment:validate': readonly [];
24
24
  readonly 'deployment:release': readonly ["--project", "--environment", "--output"];
25
- readonly 'deployment:submit': readonly ["--release", "--endpoint", "--replace-restored"];
25
+ readonly 'deployment:submit': readonly ["--release", "--endpoint", "--replace-restored", "--draft"];
26
26
  readonly 'deployment:status': readonly ["--project", "--environment", "--endpoint", "--mcp-config"];
27
27
  readonly pull: readonly ["--output", "--project", "--environment", "--endpoint"];
28
28
  readonly diff: readonly ["--project", "--environment", "--endpoint"];
@@ -44,11 +44,11 @@ export declare const VALUE_FLAGS: {
44
44
  readonly 'generate:client': readonly ["--output"];
45
45
  readonly login: readonly ["--cloud-url", "--environment"];
46
46
  readonly logout: readonly [];
47
- readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--endpoint"];
47
+ readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--endpoint", "--draft"];
48
48
  readonly 'deployment:build': readonly ["--bundle-output"];
49
49
  readonly 'deployment:validate': readonly [];
50
50
  readonly 'deployment:release': readonly ["--project", "--environment", "--output"];
51
- readonly 'deployment:submit': readonly ["--release", "--endpoint"];
51
+ readonly 'deployment:submit': readonly ["--release", "--endpoint", "--draft"];
52
52
  readonly 'deployment:status': readonly ["--project", "--environment", "--endpoint"];
53
53
  readonly pull: readonly ["--output", "--project", "--environment", "--endpoint"];
54
54
  readonly diff: readonly ["--project", "--environment", "--endpoint"];
@@ -27,11 +27,11 @@ export const COMMAND_FLAGS = {
27
27
  'generate:client': ['--output', '--check', '--force'],
28
28
  login: ['--cloud-url', '--environment'],
29
29
  logout: [],
30
- deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--no-source', '--endpoint', '--replace-restored', '--mcp-config'],
30
+ deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--no-source', '--endpoint', '--replace-restored', '--mcp-config', '--draft'],
31
31
  'deployment:build': ['--bundle-output', '--no-source'],
32
32
  'deployment:validate': [],
33
33
  'deployment:release': ['--project', '--environment', '--output'],
34
- 'deployment:submit': ['--release', '--endpoint', '--replace-restored'],
34
+ 'deployment:submit': ['--release', '--endpoint', '--replace-restored', '--draft'],
35
35
  'deployment:status': ['--project', '--environment', '--endpoint', '--mcp-config'],
36
36
  pull: ['--output', '--project', '--environment', '--endpoint'],
37
37
  diff: ['--project', '--environment', '--endpoint'],
@@ -50,11 +50,11 @@ export const VALUE_FLAGS = {
50
50
  'generate:client': ['--output'],
51
51
  login: ['--cloud-url', '--environment'],
52
52
  logout: [],
53
- deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--endpoint'],
53
+ deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--endpoint', '--draft'],
54
54
  'deployment:build': ['--bundle-output'],
55
55
  'deployment:validate': [],
56
56
  'deployment:release': ['--project', '--environment', '--output'],
57
- 'deployment:submit': ['--release', '--endpoint'],
57
+ 'deployment:submit': ['--release', '--endpoint', '--draft'],
58
58
  'deployment:status': ['--project', '--environment', '--endpoint'],
59
59
  pull: ['--output', '--project', '--environment', '--endpoint'],
60
60
  diff: ['--project', '--environment', '--endpoint'],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.26.0",
3
+ "version": "1.27.0",
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": "^11.0.4",
31
31
  "ora": "^8.0.1",
32
32
  "prompts": "^2.4.2",
33
- "@hypequery/deployment": "0.11.0",
34
- "@hypequery/protocol": "0.24.0"
33
+ "@hypequery/deployment": "0.11.1",
34
+ "@hypequery/protocol": "0.24.1"
35
35
  },
36
36
  "optionalDependencies": {
37
37
  "@napi-rs/keyring": "2.1.2"
@@ -54,8 +54,8 @@
54
54
  "@vitest/coverage-v8": "^5.0.5",
55
55
  "typescript": "^6.0.3",
56
56
  "vitest": "^5.0.5",
57
- "@hypequery/mcp": "0.11.0",
58
- "@hypequery/serve": "0.24.0"
57
+ "@hypequery/mcp": "0.11.1",
58
+ "@hypequery/serve": "0.24.1"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",