@hypequery/cli 1.25.2 → 1.26.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.
@@ -0,0 +1,4 @@
1
+ import type { ClientFieldAnnotation } from '@hypequery/protocol';
2
+ /** One-line JSDoc from backend presentation text; validated text has no line breaks. */
3
+ export declare function docComment(annotation: ClientFieldAnnotation | undefined, indent?: string): string;
4
+ //# sourceMappingURL=doc-comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doc-comments.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/doc-comments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEjE,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,UAAU,EAAE,qBAAqB,GAAG,SAAS,EAAE,MAAM,SAAK,GAAG,MAAM,CAK7F"}
@@ -0,0 +1,8 @@
1
+ /** One-line JSDoc from backend presentation text; validated text has no line breaks. */
2
+ export function docComment(annotation, indent = '') {
3
+ const parts = [annotation?.label, annotation?.description].filter((part) => part !== undefined);
4
+ if (parts.length === 0)
5
+ return '';
6
+ // The comment terminator is the only sequence that can escape a block comment.
7
+ return `${indent}/** ${parts.join(' — ').replaceAll('*/', '*\\/')} */\n`;
8
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"input-types.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/input-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAA+B,CAAC;AACxE,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAsD,CAAC;AACzG,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAgD,CAAC;AAWnG,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAqBxE"}
1
+ {"version":3,"file":"input-types.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/input-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAA+B,CAAC;AACxE,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAsD,CAAC;AACzG,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAgD,CAAC;AAenG,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAqBxE"}
@@ -4,6 +4,10 @@ export const names = (values) => union([...values].sort().map(literal));
4
4
  function predicate(field, definition, having = false) {
5
5
  const scalar = definition.type === 'number' ? 'number' : definition.type === 'boolean' ? 'boolean' : 'string';
6
6
  return [...definition.operators].sort().map(operator => {
7
+ // A null check names no value (RFC 0023).
8
+ if (operator === 'isNull' || operator === 'isNotNull') {
9
+ return `{ readonly ${having ? 'measure' : 'field'}: ${literal(field)}; readonly operator: ${literal(operator)} }`;
10
+ }
7
11
  const value = operator === 'between' ? `readonly [${scalar}, ${scalar}]`
8
12
  : operator === 'in' || operator === 'notIn' ? `readonly [${scalar}, ...${scalar}[]]` : scalar;
9
13
  return `{ readonly ${having ? 'measure' : 'field'}: ${literal(field)}; readonly operator: ${literal(operator)}; readonly value: ${value} }`;
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/render.ts"],"names":[],"mappings":"AA0BA,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAqB5D"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/render.ts"],"names":[],"mappings":"AAmCA,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAqB5D"}
@@ -1,13 +1,16 @@
1
1
  import { validateProtocolClientContract } from '@hypequery/protocol';
2
+ import { docComment } from './doc-comments.js';
2
3
  import { clientInputType, literal } from './input-types.js';
3
4
  function dimensionType(definition) {
4
5
  const wire = definition.wireType === 'number-or-string' ? 'number | string' : definition.wireType;
5
6
  return wire + (definition.nullable === false ? '' : ' | null');
6
7
  }
7
8
  function endpointType(endpoint) {
9
+ const fields = endpoint.annotations?.fields ?? {};
8
10
  const dimensions = Object.keys(endpoint.dimensions).sort().filter(key => endpoint.dimensions[key].groupable)
9
- .map(key => `readonly ${literal(key)}?: ${dimensionType(endpoint.dimensions[key])};`);
10
- const measures = Object.keys(endpoint.measures).sort().map(key => `readonly ${literal(key)}?: string | null;`);
11
+ .map(key => `${docComment(fields[key])}readonly ${literal(key)}?: ${dimensionType(endpoint.dimensions[key])};`);
12
+ const measures = Object.keys(endpoint.measures).sort()
13
+ .map(key => `${docComment(fields[key])}readonly ${literal(key)}?: string | null;`);
11
14
  // The physical time column may be nullable; the descriptor makes no non-null guarantee.
12
15
  const row = `{ ${[...dimensions, ...measures, ...(endpoint.time ? ['readonly period?: string | null;'] : [])].join('\n')} }`;
13
16
  const semantic = {
@@ -21,6 +24,11 @@ function endpointType(endpoint) {
21
24
  readonly __hypequeryClientSemantic?: ${JSON.stringify(semantic)};
22
25
  }`;
23
26
  }
27
+ /** A metric documents itself through its sole measure; a dataset through its group text. */
28
+ function endpointDoc(endpoint) {
29
+ const metric = endpoint.kind === 'metric' ? endpoint.annotations?.fields?.[Object.keys(endpoint.measures)[0]] : undefined;
30
+ return metric ?? endpoint.annotations;
31
+ }
24
32
  /** Data-only generator: no API module evaluation, credentials, drivers or server dependencies. */
25
33
  export function renderClientContract(source) {
26
34
  const contract = validateProtocolClientContract(source);
@@ -36,7 +44,7 @@ export interface ClientResponseMeta {
36
44
  cache?: { hit: boolean };
37
45
  }
38
46
  export type ClientApi = {
39
- ${entries.map(({ endpoint, key }) => ` ${literal(key)}: ${endpointType(endpoint)};`).join('\n')}
47
+ ${entries.map(({ endpoint, key }) => `${docComment(endpointDoc(endpoint), ' ')} ${literal(key)}: ${endpointType(endpoint)};`).join('\n')}
40
48
  };
41
49
  export const clientManifest = {
42
50
  ${entries.map(({ endpoint, key }) => ` [${literal(key)}]: { method: 'POST', path: ${literal(endpoint.path)}, schemaHash: ${literal(endpoint.schemaHash)} },`).join('\n')}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.25.2",
3
+ "version": "1.26.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.10.2",
34
- "@hypequery/protocol": "0.23.0"
33
+ "@hypequery/deployment": "0.11.0",
34
+ "@hypequery/protocol": "0.24.0"
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.10.2",
58
- "@hypequery/serve": "0.23.1"
57
+ "@hypequery/mcp": "0.11.0",
58
+ "@hypequery/serve": "0.24.0"
59
59
  },
60
60
  "repository": {
61
61
  "type": "git",