@hypequery/datasets 0.15.0 → 0.17.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 (70) hide show
  1. package/dist/agent-catalog.d.ts.map +1 -1
  2. package/dist/api.type-test.js +45 -1
  3. package/dist/catalog.d.ts +6 -0
  4. package/dist/catalog.d.ts.map +1 -1
  5. package/dist/catalog.js +12 -0
  6. package/dist/dataset-query.d.ts +2 -0
  7. package/dist/dataset-query.d.ts.map +1 -1
  8. package/dist/dataset-query.js +18 -21
  9. package/dist/dataset.d.ts +2 -2
  10. package/dist/dataset.d.ts.map +1 -1
  11. package/dist/dataset.js +4 -2
  12. package/dist/executor.d.ts.map +1 -1
  13. package/dist/executor.js +9 -1
  14. package/dist/index.d.ts +6 -2
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +4 -1
  17. package/dist/measure.d.ts +4 -1
  18. package/dist/measure.d.ts.map +1 -1
  19. package/dist/measure.js +12 -0
  20. package/dist/portable-executor.d.ts +4 -5
  21. package/dist/portable-executor.d.ts.map +1 -1
  22. package/dist/portable-executor.js +2 -20
  23. package/dist/protocol-adapter.d.ts +2 -2
  24. package/dist/protocol-adapter.js +2 -2
  25. package/dist/protocol-deployment-adapter.d.ts +8 -0
  26. package/dist/protocol-deployment-adapter.d.ts.map +1 -0
  27. package/dist/protocol-deployment-adapter.js +31 -0
  28. package/dist/protocol-rehydrate.d.ts +4 -2
  29. package/dist/protocol-rehydrate.d.ts.map +1 -1
  30. package/dist/protocol-rehydrate.js +44 -12
  31. package/dist/protocol-schema-adapter.d.ts +9 -0
  32. package/dist/protocol-schema-adapter.d.ts.map +1 -0
  33. package/dist/protocol-schema-adapter.js +249 -0
  34. package/dist/relationships.d.ts +25 -4
  35. package/dist/relationships.d.ts.map +1 -1
  36. package/dist/relationships.js +25 -4
  37. package/dist/semantic-planner.d.ts.map +1 -1
  38. package/dist/semantic-planner.js +3 -0
  39. package/dist/semantic-query-schema.d.ts +19 -0
  40. package/dist/semantic-query-schema.d.ts.map +1 -1
  41. package/dist/semantic-query-schema.js +25 -1
  42. package/dist/types.d.ts +46 -7
  43. package/dist/types.d.ts.map +1 -1
  44. package/dist/utils/agent-catalog-projection.d.ts +6 -2
  45. package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
  46. package/dist/utils/agent-catalog-projection.js +2 -2
  47. package/dist/utils/dataset-definition-validation.d.ts +2 -2
  48. package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
  49. package/dist/utils/dataset-definition-validation.js +13 -1
  50. package/dist/utils/dataset-derived-query.d.ts +12 -0
  51. package/dist/utils/dataset-derived-query.d.ts.map +1 -0
  52. package/dist/utils/dataset-derived-query.js +112 -0
  53. package/dist/utils/dataset-measures.d.ts +10 -0
  54. package/dist/utils/dataset-measures.d.ts.map +1 -0
  55. package/dist/utils/dataset-measures.js +24 -0
  56. package/dist/utils/dataset-normalization.d.ts +3 -3
  57. package/dist/utils/dataset-normalization.d.ts.map +1 -1
  58. package/dist/utils/dataset-query-result.d.ts +10 -0
  59. package/dist/utils/dataset-query-result.d.ts.map +1 -0
  60. package/dist/utils/dataset-query-result.js +20 -0
  61. package/dist/utils/dataset-query-validation.js +2 -2
  62. package/dist/utils/derived-measure-validation.d.ts +3 -0
  63. package/dist/utils/derived-measure-validation.d.ts.map +1 -0
  64. package/dist/utils/derived-measure-validation.js +138 -0
  65. package/dist/utils/protocol-metric-expressions.d.ts +2 -1
  66. package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
  67. package/dist/utils/protocol-metric-expressions.js +4 -0
  68. package/dist/utils/semantic-metadata-validation.d.ts +2 -2
  69. package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
  70. package/package.json +3 -3
@@ -0,0 +1,31 @@
1
+ import { parseProtocolIdentifier, validateProtocolDeploymentContract, } from '@hypequery/protocol';
2
+ import { buildProtocolDatasetContract } from './protocol-adapter.js';
3
+ import { derivedMeasureExpression } from './utils/protocol-metric-expressions.js';
4
+ import { toProtocolSemanticMetadata } from './utils/protocol-semantic-metadata.js';
5
+ /** Build the deployment contract; local metrics are not carried. */
6
+ export function buildProtocolDeploymentContract(datasets, options = {}) {
7
+ const contracts = datasets.map(dataset => {
8
+ const contract = buildProtocolDatasetContract(dataset, {
9
+ endpoint: options.endpoints?.[dataset.name],
10
+ });
11
+ const { metrics: _metrics, ...base } = contract;
12
+ const derived = Object.entries(dataset.derivedMeasures ?? {}).map(([name, definition]) => ({
13
+ kind: 'derived',
14
+ name: parseProtocolIdentifier(name),
15
+ uses: Object.entries(definition.uses).map(([alias, measure]) => ({
16
+ alias: parseProtocolIdentifier(alias),
17
+ measure: parseProtocolIdentifier(measure),
18
+ })),
19
+ expression: derivedMeasureExpression(definition),
20
+ ...(definition.label !== undefined ? { label: definition.label } : {}),
21
+ ...(definition.description !== undefined ? { description: definition.description } : {}),
22
+ ...toProtocolSemanticMetadata(definition),
23
+ }));
24
+ return { ...base, measures: [...base.measures, ...derived] };
25
+ });
26
+ return validateProtocolDeploymentContract({
27
+ kind: 'hypequery-deployment',
28
+ version: 2,
29
+ datasets: contracts,
30
+ });
31
+ }
@@ -11,7 +11,7 @@
11
11
  * exactly the same code path as an authored one. That is what makes the
12
12
  * byte-identical SQL claim mitigated by construction rather than by review.
13
13
  */
14
- import type { ProtocolDatasetContract } from '@hypequery/protocol';
14
+ import type { ProtocolDatasetContract, ProtocolDeploymentDataset } from '@hypequery/protocol';
15
15
  import type { AnyDatasetInstance, DatasetCachePolicy, MetricHandle } from './types.js';
16
16
  /** A rebuilt dataset in the registry shape Serve, MCP, and the planner accept. */
17
17
  export type RehydratedDataset = AnyDatasetInstance & {
@@ -52,5 +52,7 @@ export declare class UnsupportedContractFeatureError extends Error {
52
52
  * attached, which is the registry shape `getDatasetCatalog`,
53
53
  * `projectAgentSafeCatalog`, and `DatasetClient` already accept.
54
54
  */
55
- export declare function rehydrateProtocolDatasets(contracts: readonly ProtocolDatasetContract[], options?: RehydrateProtocolDatasetsOptions): Readonly<Record<string, RehydratedDataset>>;
55
+ export declare function rehydrateProtocolDatasets(contracts: readonly (ProtocolDatasetContract | ProtocolDeploymentDataset)[], options?: RehydrateProtocolDatasetsOptions): Readonly<Record<string, RehydratedDataset>>;
56
+ /** Strict v2 reader that returns executable datasets without metric handles. */
57
+ export declare function rehydrateProtocolDeploymentContract(input: unknown, options?: RehydrateProtocolDatasetsOptions): Readonly<Record<string, RehydratedDataset>>;
56
58
  //# sourceMappingURL=protocol-rehydrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"protocol-rehydrate.d.ts","sourceRoot":"","sources":["../src/protocol-rehydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,uBAAuB,EAKxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAKlB,YAAY,EAIb,MAAM,YAAY,CAAC;AAMpB,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG;IACnD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEF,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACjD;AAED;;;;;GAKG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMjE;AAwOD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,uBAAuB,EAAE,EAC7C,OAAO,GAAE,gCAAqC,GAC7C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAqE7C"}
1
+ {"version":3,"file":"protocol-rehydrate.d.ts","sourceRoot":"","sources":["../src/protocol-rehydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,uBAAuB,EAEvB,yBAAyB,EAM1B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAMlB,YAAY,EAIb,MAAM,YAAY,CAAC;AAMpB,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG;IACnD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEF,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACjD;AAED;;;;;GAKG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMjE;AAyPD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,CAAC,uBAAuB,GAAG,yBAAyB,CAAC,EAAE,EAC3E,OAAO,GAAE,gCAAqC,GAC7C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAmF7C;AAED,gFAAgF;AAChF,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,gCAAqC,GAC7C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAG7C"}
@@ -11,6 +11,7 @@
11
11
  * exactly the same code path as an authored one. That is what makes the
12
12
  * byte-identical SQL claim mitigated by construction rather than by review.
13
13
  */
14
+ import { validateProtocolDeploymentContract } from '@hypequery/protocol';
14
15
  import { dataset } from './dataset.js';
15
16
  import { snapshotSemanticMetadata } from './utils/semantic-metadata.js';
16
17
  import { withContractCapabilities } from './utils/protocol-metric-capabilities.js';
@@ -72,6 +73,16 @@ function rehydrateMeasure(datasetName, measure) {
72
73
  ...(filters.length > 0 ? { filters: filters } : {}),
73
74
  };
74
75
  }
76
+ function rehydrateDerivedMeasure(datasetName, measure) {
77
+ return {
78
+ __type: 'derived_measure_definition',
79
+ ...snapshotSemanticMetadata(measure),
80
+ uses: Object.fromEntries(measure.uses.map(input => [String(input.alias), String(input.measure)])),
81
+ formula: rehydrateDerivedFormula({ inputs: [], expression: measure.expression }, reason => new UnsupportedContractFeatureError(datasetName, `measure "${measure.name}"`, reason)),
82
+ ...(measure.label !== undefined ? { label: measure.label } : {}),
83
+ ...(measure.description !== undefined ? { description: measure.description } : {}),
84
+ };
85
+ }
75
86
  function rehydrateFilters(contract) {
76
87
  const filters = {};
77
88
  for (const filter of contract.filters) {
@@ -137,7 +148,7 @@ function measureForAggregate(instance, contract, subject, expression) {
137
148
  }
138
149
  // A metric expression carries no raw SQL, so two measures that share an
139
150
  // aggregation identity but override SQL differently are indistinguishable in
140
- // contract v1. Binding to either would be a guess at which SQL to emit.
151
+ // this dataset snapshot. Binding to either would guess which SQL to emit.
141
152
  const distinctSql = new Set(candidates.map(measure => measure.sql?.sql ?? null));
142
153
  if (distinctSql.size > 1) {
143
154
  throw new UnsupportedContractFeatureError(instance.name, subject, `${candidates.map(measure => `"${String(measure.name)}"`).join(' and ')} share `
@@ -202,14 +213,24 @@ function rehydrateMetric(instance, contract, metric) {
202
213
  * `projectAgentSafeCatalog`, and `DatasetClient` already accept.
203
214
  */
204
215
  export function rehydrateProtocolDatasets(contracts, options = {}) {
216
+ const normalized = contracts.map(contract => {
217
+ const measures = contract.measures;
218
+ const derived = measures.filter((measure) => 'kind' in measure);
219
+ const base = {
220
+ ...contract,
221
+ measures: measures.filter((measure) => !('kind' in measure)),
222
+ metrics: contract.metrics ?? [],
223
+ };
224
+ return { base, derived };
225
+ });
205
226
  const instances = new Map();
206
- const registry = {};
227
+ const registry = new Map();
207
228
  // Relationship targets resolve to the published entry, not the bare instance
208
229
  // built below, so a caller that follows a relationship lands on the same
209
230
  // object the registry exposes. Safe because `target()` is only called after
210
231
  // the registry is complete.
211
232
  const resolve = (target) => {
212
- const instance = registry[target] ?? instances.get(target);
233
+ const instance = registry.get(target) ?? instances.get(target);
213
234
  if (!instance) {
214
235
  // The deployment validator already rejects a dangling relationship, so
215
236
  // reaching this means the caller passed a partial contract.
@@ -217,7 +238,7 @@ export function rehydrateProtocolDatasets(contracts, options = {}) {
217
238
  }
218
239
  return instance;
219
240
  };
220
- for (const contract of contracts) {
241
+ for (const { base: contract, derived } of normalized) {
221
242
  const name = String(contract.name);
222
243
  const instance = dataset(name, {
223
244
  source: contract.source,
@@ -232,10 +253,14 @@ export function rehydrateProtocolDatasets(contracts, options = {}) {
232
253
  ...(contract.tenant.kind === 'required' ? { tenantKey: contract.tenant.field } : {}),
233
254
  ...(contract.timeField !== undefined ? { timeKey: String(contract.timeField) } : {}),
234
255
  dimensions: rehydrateDimensions(contract),
235
- measures: Object.fromEntries(contract.measures.map(measure => [
236
- String(measure.name),
237
- rehydrateMeasure(name, measure),
238
- ])),
256
+ measures: Object.fromEntries([
257
+ ...contract.measures.map(measure => [
258
+ String(measure.name), rehydrateMeasure(name, measure),
259
+ ]),
260
+ ...derived.map(measure => [
261
+ String(measure.name), rehydrateDerivedMeasure(name, measure),
262
+ ]),
263
+ ]),
239
264
  filters: rehydrateFilters(contract),
240
265
  relationships: rehydrateRelationships(contract, resolve),
241
266
  ...(contract.limits !== undefined ? { limits: { ...contract.limits } } : {}),
@@ -243,11 +268,11 @@ export function rehydrateProtocolDatasets(contracts, options = {}) {
243
268
  });
244
269
  instances.set(name, instance);
245
270
  }
246
- for (const contract of contracts) {
271
+ for (const { base: contract } of normalized) {
247
272
  const name = String(contract.name);
248
273
  const instance = instances.get(name);
249
274
  const metrics = {};
250
- for (const metric of contract.metrics) {
275
+ for (const metric of contract.metrics ?? []) {
251
276
  try {
252
277
  metrics[String(metric.name)] = rehydrateMetric(instance, contract, metric);
253
278
  }
@@ -258,7 +283,14 @@ export function rehydrateProtocolDatasets(contracts, options = {}) {
258
283
  }
259
284
  }
260
285
  }
261
- registry[name] = Object.assign(Object.create(Object.getPrototypeOf(instance)), instance, { metrics });
286
+ const published = { ...instance, metrics };
287
+ registry.set(name, published);
262
288
  }
263
- return registry;
289
+ // Object.fromEntries defines "__proto__" as an own property, unlike assignment to {}.
290
+ return Object.fromEntries(registry);
291
+ }
292
+ /** Strict v2 reader that returns executable datasets without metric handles. */
293
+ export function rehydrateProtocolDeploymentContract(input, options = {}) {
294
+ const contract = validateProtocolDeploymentContract(input);
295
+ return rehydrateProtocolDatasets(contract.datasets, options);
264
296
  }
@@ -0,0 +1,9 @@
1
+ import type { ZodTypeAny } from 'zod';
2
+ import { type ProtocolSchema } from '@hypequery/protocol';
3
+ export declare class ProtocolSchemaAdapterError extends TypeError {
4
+ readonly path: string;
5
+ constructor(message: string, path?: string);
6
+ }
7
+ /** Converts the portable subset of a Zod v3 schema into RFC 0004. */
8
+ export declare function zodToProtocolSchema(schema: ZodTypeAny | undefined, path?: string): ProtocolSchema;
9
+ //# sourceMappingURL=protocol-schema-adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol-schema-adapter.d.ts","sourceRoot":"","sources":["../src/protocol-schema-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AACtC,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAE7B,qBAAa,0BAA2B,SAAQ,SAAS;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,IAAI,SAAM;CAKxC;AAoPD,qEAAqE;AACrE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,UAAU,GAAG,SAAS,EAC9B,IAAI,SAAM,GACT,cAAc,CAGhB"}
@@ -0,0 +1,249 @@
1
+ import { validateCanonicalValue, validateProtocolSchema, } from '@hypequery/protocol';
2
+ export class ProtocolSchemaAdapterError extends TypeError {
3
+ path;
4
+ constructor(message, path = '$') {
5
+ super(`${message} at ${path}`);
6
+ this.name = 'ProtocolSchemaAdapterError';
7
+ this.path = path;
8
+ }
9
+ }
10
+ function canonicalValue(input, path) {
11
+ try {
12
+ if (Array.isArray(input)) {
13
+ return validateCanonicalValue({
14
+ $hypequery: {
15
+ type: 'array',
16
+ version: 1,
17
+ values: input.map((item, index) => canonicalValue(item, `${path}[${index}]`)),
18
+ },
19
+ });
20
+ }
21
+ if (typeof input === 'object' && input !== null) {
22
+ if ('$hypequery' in input)
23
+ return validateCanonicalValue(input);
24
+ const prototype = Object.getPrototypeOf(input);
25
+ if (prototype !== Object.prototype && prototype !== null) {
26
+ throw new ProtocolSchemaAdapterError('Default is not portable plain data', path);
27
+ }
28
+ return validateCanonicalValue({
29
+ $hypequery: {
30
+ type: 'map',
31
+ version: 1,
32
+ entries: Object.entries(input)
33
+ .sort(([left], [right]) => left.localeCompare(right))
34
+ .map(([key, value]) => [key, canonicalValue(value, `${path}.${key}`)]),
35
+ },
36
+ });
37
+ }
38
+ return validateCanonicalValue(input);
39
+ }
40
+ catch (error) {
41
+ if (error instanceof ProtocolSchemaAdapterError)
42
+ throw error;
43
+ throw new ProtocolSchemaAdapterError('Default is not a canonical protocol value', path);
44
+ }
45
+ }
46
+ function typeName(schema) {
47
+ return String(schema?._def?.typeName ?? 'Unknown');
48
+ }
49
+ function description(schema) {
50
+ return typeof schema.description === 'string'
51
+ ? schema.description
52
+ : undefined;
53
+ }
54
+ function annotate(schema, result) {
55
+ const value = description(schema);
56
+ return value === undefined ? result : { ...result, description: value };
57
+ }
58
+ function convertString(schema, path) {
59
+ const result = { kind: 'string' };
60
+ for (const check of schema._def.checks) {
61
+ switch (check.kind) {
62
+ case 'min':
63
+ result.minLength = check.value;
64
+ break;
65
+ case 'max':
66
+ result.maxLength = check.value;
67
+ break;
68
+ case 'length':
69
+ result.minLength = check.value;
70
+ result.maxLength = check.value;
71
+ break;
72
+ default:
73
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod string check "${String(check.kind)}"`, path);
74
+ }
75
+ }
76
+ return annotate(schema, result);
77
+ }
78
+ function convertNumber(schema, path) {
79
+ const result = { kind: 'number' };
80
+ for (const check of schema._def.checks) {
81
+ switch (check.kind) {
82
+ case 'int':
83
+ result.kind = 'integer';
84
+ break;
85
+ case 'min':
86
+ result[check.inclusive === false ? 'exclusiveMinimum' : 'minimum'] = check.value;
87
+ break;
88
+ case 'max':
89
+ result[check.inclusive === false ? 'exclusiveMaximum' : 'maximum'] = check.value;
90
+ break;
91
+ case 'finite':
92
+ break;
93
+ default:
94
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod number check "${String(check.kind)}"`, path);
95
+ }
96
+ }
97
+ return annotate(schema, result);
98
+ }
99
+ function isOptionalProperty(schema) {
100
+ const name = typeName(schema);
101
+ return name === 'ZodOptional' || name === 'ZodDefault';
102
+ }
103
+ function nativeEnumValues(input) {
104
+ const values = Object.keys(input)
105
+ .filter(key => typeof input[String(input[key])] !== 'number')
106
+ .map(key => input[key]);
107
+ return [...new Set(values)];
108
+ }
109
+ function isUnconstrainedRecordKey(schema) {
110
+ if (schema === undefined || typeName(schema) !== 'ZodString')
111
+ return false;
112
+ const definition = schema._def;
113
+ return Array.isArray(definition.checks)
114
+ && definition.checks.length === 0
115
+ && definition.coerce !== true;
116
+ }
117
+ function convert(schema, path) {
118
+ const definition = schema._def;
119
+ switch (typeName(schema)) {
120
+ case 'ZodAny':
121
+ case 'ZodUnknown':
122
+ return annotate(schema, { kind: 'any' });
123
+ case 'ZodNever':
124
+ case 'ZodVoid':
125
+ case 'ZodUndefined':
126
+ return annotate(schema, { kind: 'void' });
127
+ case 'ZodNull':
128
+ return annotate(schema, { kind: 'null' });
129
+ case 'ZodBoolean':
130
+ return annotate(schema, { kind: 'boolean' });
131
+ case 'ZodString':
132
+ return convertString(schema, path);
133
+ case 'ZodNumber':
134
+ return convertNumber(schema, path);
135
+ case 'ZodLiteral':
136
+ return annotate(schema, {
137
+ kind: 'literal',
138
+ value: canonicalValue(definition.value, `${path}.value`),
139
+ });
140
+ case 'ZodEnum':
141
+ return annotate(schema, {
142
+ kind: 'enum',
143
+ values: [...definition.values],
144
+ });
145
+ case 'ZodNativeEnum': {
146
+ const values = nativeEnumValues(definition.values);
147
+ return annotate(schema, { kind: 'enum', values });
148
+ }
149
+ case 'ZodArray': {
150
+ const result = {
151
+ kind: 'array',
152
+ items: convert(definition.type, `${path}.items`),
153
+ };
154
+ if (definition.minLength?.value !== undefined)
155
+ result.minItems = definition.minLength.value;
156
+ if (definition.maxLength?.value !== undefined)
157
+ result.maxItems = definition.maxLength.value;
158
+ if (definition.exactLength?.value !== undefined) {
159
+ result.minItems = definition.exactLength.value;
160
+ result.maxItems = definition.exactLength.value;
161
+ }
162
+ return annotate(schema, result);
163
+ }
164
+ case 'ZodObject': {
165
+ if (typeName(definition.catchall) !== 'ZodNever') {
166
+ throw new ProtocolSchemaAdapterError('Unsupported Zod object catchall', path);
167
+ }
168
+ const shape = definition.shape();
169
+ const properties = Object.fromEntries(Object.entries(shape).map(([name, property]) => [
170
+ name,
171
+ convert(property, `${path}.properties.${name}`),
172
+ ]));
173
+ const required = Object.entries(shape)
174
+ .filter(([, property]) => !isOptionalProperty(property))
175
+ .map(([name]) => name);
176
+ const unknownProperties = definition.unknownKeys === 'passthrough'
177
+ ? 'preserve'
178
+ : definition.unknownKeys === 'strict'
179
+ ? 'reject'
180
+ : 'strip';
181
+ return annotate(schema, {
182
+ kind: 'object',
183
+ properties,
184
+ required,
185
+ unknownProperties,
186
+ });
187
+ }
188
+ case 'ZodRecord':
189
+ if (!isUnconstrainedRecordKey(definition.keyType)) {
190
+ throw new ProtocolSchemaAdapterError('Unsupported constrained Zod record key', path);
191
+ }
192
+ return annotate(schema, {
193
+ kind: 'record',
194
+ values: convert(definition.valueType, `${path}.values`),
195
+ });
196
+ case 'ZodUnion':
197
+ return annotate(schema, {
198
+ kind: 'union',
199
+ variants: definition.options.map((option, index) => convert(option, `${path}.variants[${index}]`)),
200
+ });
201
+ case 'ZodDiscriminatedUnion':
202
+ // RFC 0004 has no separate discriminator index. Converting every option
203
+ // drops only Zod's lookup metadata; each object's literal discriminator
204
+ // property remains part of its protocol variant.
205
+ return annotate(schema, {
206
+ kind: 'union',
207
+ variants: [...definition.options.values()].map((option, index) => convert(option, `${path}.variants[${index}]`)),
208
+ });
209
+ case 'ZodNullable':
210
+ return annotate(schema, {
211
+ kind: 'union',
212
+ variants: [convert(definition.innerType, `${path}.variants[0]`), { kind: 'null' }],
213
+ });
214
+ case 'ZodOptional':
215
+ return annotate(schema, convert(definition.innerType, path));
216
+ case 'ZodDefault': {
217
+ const inner = convert(definition.innerType, path);
218
+ if (inner.kind === 'void') {
219
+ throw new ProtocolSchemaAdapterError('Void schemas cannot carry defaults', path);
220
+ }
221
+ return annotate(schema, {
222
+ ...inner,
223
+ default: canonicalValue(definition.defaultValue(), `${path}.default`),
224
+ });
225
+ }
226
+ case 'ZodBranded':
227
+ return annotate(schema, convert(definition.type, path));
228
+ case 'ZodReadonly':
229
+ return annotate(schema, convert(definition.innerType, path));
230
+ case 'ZodEffects': {
231
+ const effectType = String(definition.effect?.type ?? 'unknown');
232
+ if (effectType !== 'refinement') {
233
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod effect "${effectType}"`, path);
234
+ }
235
+ // A refinement preserves the wrapped schema's input and output shape.
236
+ // The rule itself remains enforced by Zod, but cannot be advertised by
237
+ // ProtocolSchema (for example, a cross-field selection requirement).
238
+ return annotate(schema, convert(definition.schema, path));
239
+ }
240
+ default:
241
+ throw new ProtocolSchemaAdapterError(`Unsupported Zod type "${typeName(schema)}"`, path);
242
+ }
243
+ }
244
+ /** Converts the portable subset of a Zod v3 schema into RFC 0004. */
245
+ export function zodToProtocolSchema(schema, path = '$') {
246
+ if (schema === undefined)
247
+ return validateProtocolSchema({ kind: 'any' });
248
+ return validateProtocolSchema(convert(schema, path));
249
+ }
@@ -1,19 +1,40 @@
1
1
  /**
2
2
  * Relationship helpers for dataset definitions.
3
3
  *
4
- * These helpers currently define semantic model metadata only. The shipped
5
- * semantic client does not yet resolve relationship paths into joined dataset queries
6
- * or cross-dataset metrics.
4
+ * To-one relationships (`belongsTo`, `hasOne`) are queryable one hop deep as
5
+ * `<relationship>.<dimension>` and execute as LEFT JOINs. `hasMany` is metadata
6
+ * only: joining it would fan out and corrupt aggregates, so it is refused at
7
+ * query time.
8
+ *
9
+ * Only the target's *dimensions* are reachable. A measure on the target dataset
10
+ * is not addressable through a relationship, so there are no cross-dataset
11
+ * metrics.
12
+ *
13
+ * Note that the `kind` passed here is a declaration, not something checked
14
+ * against the data — there is no uniqueness concept in the model, so nothing
15
+ * verifies that a `belongsTo` target column really is unique. What a
16
+ * mis-declaration costs depends on the builder. One that implements the
17
+ * optional `leftAnyJoin` (ClickHouse `LEFT ANY JOIN`) takes at most one target
18
+ * row per base row, so the aggregate cannot inflate — it just silently picks an
19
+ * arbitrary one of the matches. A builder without it falls back to `leftJoin`,
20
+ * where duplicate target keys fan out and do inflate the aggregate. Either way
21
+ * the declaration has to be right.
7
22
  *
8
23
  * @example
9
24
  * ```ts
10
25
  * const Orders = dataset("orders", {
11
26
  * source: "orders",
12
- * fields: { ... },
27
+ * dimensions: {
28
+ * id: dimension.string(),
29
+ * customerId: dimension.string({ column: "customer_id" }),
30
+ * },
13
31
  * relationships: {
14
32
  * customer: belongsTo(() => Customers, { from: "customerId", to: "id" }),
15
33
  * },
16
34
  * });
35
+ *
36
+ * // Groups by a dimension on Customers, joining through the relationship.
37
+ * await client.execute(Orders, { dimensions: ["customer.country"] });
17
38
  * ```
18
39
  */
19
40
  import type { RelationshipDefinition } from './types.js';
@@ -1 +1 @@
1
- {"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAoB,MAAM,YAAY,CAAC;AAmB3E,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,OAAO,SAAS;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC3E,MAAM,EAAE,MAAM,OAAO,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CAAC,OAAO,EAAE,WAAW,CAAC,CAE9C;AAED,qDAAqD;AACrD,wBAAgB,OAAO,CAAC,OAAO,SAAS;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzE,MAAM,EAAE,MAAM,OAAO,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CAAC,OAAO,EAAE,SAAS,CAAC,CAE5C;AAED,oDAAoD;AACpD,wBAAgB,MAAM,CAAC,OAAO,SAAS;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACxE,MAAM,EAAE,MAAM,OAAO,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAE3C"}
1
+ {"version":3,"file":"relationships.d.ts","sourceRoot":"","sources":["../src/relationships.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAoB,MAAM,YAAY,CAAC;AAmB3E,mDAAmD;AACnD,wBAAgB,SAAS,CAAC,OAAO,SAAS;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC3E,MAAM,EAAE,MAAM,OAAO,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CAAC,OAAO,EAAE,WAAW,CAAC,CAE9C;AAED,qDAAqD;AACrD,wBAAgB,OAAO,CAAC,OAAO,SAAS;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACzE,MAAM,EAAE,MAAM,OAAO,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CAAC,OAAO,EAAE,SAAS,CAAC,CAE5C;AAED,oDAAoD;AACpD,wBAAgB,MAAM,CAAC,OAAO,SAAS;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EACxE,MAAM,EAAE,MAAM,OAAO,EACrB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACjC,sBAAsB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAE3C"}
@@ -1,19 +1,40 @@
1
1
  /**
2
2
  * Relationship helpers for dataset definitions.
3
3
  *
4
- * These helpers currently define semantic model metadata only. The shipped
5
- * semantic client does not yet resolve relationship paths into joined dataset queries
6
- * or cross-dataset metrics.
4
+ * To-one relationships (`belongsTo`, `hasOne`) are queryable one hop deep as
5
+ * `<relationship>.<dimension>` and execute as LEFT JOINs. `hasMany` is metadata
6
+ * only: joining it would fan out and corrupt aggregates, so it is refused at
7
+ * query time.
8
+ *
9
+ * Only the target's *dimensions* are reachable. A measure on the target dataset
10
+ * is not addressable through a relationship, so there are no cross-dataset
11
+ * metrics.
12
+ *
13
+ * Note that the `kind` passed here is a declaration, not something checked
14
+ * against the data — there is no uniqueness concept in the model, so nothing
15
+ * verifies that a `belongsTo` target column really is unique. What a
16
+ * mis-declaration costs depends on the builder. One that implements the
17
+ * optional `leftAnyJoin` (ClickHouse `LEFT ANY JOIN`) takes at most one target
18
+ * row per base row, so the aggregate cannot inflate — it just silently picks an
19
+ * arbitrary one of the matches. A builder without it falls back to `leftJoin`,
20
+ * where duplicate target keys fan out and do inflate the aggregate. Either way
21
+ * the declaration has to be right.
7
22
  *
8
23
  * @example
9
24
  * ```ts
10
25
  * const Orders = dataset("orders", {
11
26
  * source: "orders",
12
- * fields: { ... },
27
+ * dimensions: {
28
+ * id: dimension.string(),
29
+ * customerId: dimension.string({ column: "customer_id" }),
30
+ * },
13
31
  * relationships: {
14
32
  * customer: belongsTo(() => Customers, { from: "customerId", to: "id" }),
15
33
  * },
16
34
  * });
35
+ *
36
+ * // Groups by a dimension on Customers, joining through the relationship.
37
+ * await client.execute(Orders, { dimensions: ["customer.country"] });
17
38
  * ```
18
39
  */
19
40
  function createRelationship(kind, target, join) {
@@ -1 +1 @@
1
- {"version":3,"file":"semantic-planner.d.ts","sourceRoot":"","sources":["../src/semantic-planner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,oBAAoB,CAAC;AAyL5B,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,kBAAkB,EACtB,KAAK,GAAE,YAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAaV;AA2FD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,KAAK,GAAE,WAAgB,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAYV"}
1
+ {"version":3,"file":"semantic-planner.d.ts","sourceRoot":"","sources":["../src/semantic-planner.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EACV,QAAQ,EAIT,MAAM,oBAAoB,CAAC;AAyL5B,wBAAgB,gBAAgB,CAC9B,EAAE,EAAE,kBAAkB,EACtB,KAAK,GAAE,YAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAgBV;AA2FD,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,KAAK,GAAE,WAAgB,EACvB,OAAO,CAAC,EAAE,gBAAgB,GACzB,QAAQ,CAYV"}
@@ -145,6 +145,9 @@ function aggregatePlan(ds, query, aggregations, context) {
145
145
  };
146
146
  }
147
147
  export function buildDatasetPlan(ds, query = {}, context) {
148
+ if ((query.measures ?? []).some(name => Object.hasOwn(ds.derivedMeasures, name))) {
149
+ throw new Error('Derived dataset measures require the queryBuilder execution path.');
150
+ }
148
151
  const validation = validateDatasetQuery(ds, query, context);
149
152
  if (!validation.valid) {
150
153
  throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
@@ -1,6 +1,7 @@
1
1
  import { type ZodTypeAny } from 'zod';
2
2
  import { type DatasetCatalog, type DatasetCatalogSource, type MetricCatalogEntry } from './catalog.js';
3
3
  import type { JsonSchema } from './tools.js';
4
+ import type { ProtocolSchema } from '@hypequery/protocol';
4
5
  export interface SemanticQuerySchemaLimits {
5
6
  defaultResultSize?: number;
6
7
  maxResultSize?: number;
@@ -57,4 +58,22 @@ export declare function toSemanticJsonSchema(schema: ZodTypeAny, options?: {
57
58
  requireSelection?: boolean;
58
59
  }): JsonSchema;
59
60
  export declare function buildCanonicalSemanticQuerySchemas(datasets: Record<string, SemanticQuerySchemaSource>, options?: SemanticQuerySchemaOptions): CanonicalSemanticQuerySchemas;
61
+ /**
62
+ * The same input schema, as a `ProtocolSchema`.
63
+ *
64
+ * A hosted registry advertises one endpoint per dataset and per metric, and it
65
+ * advertises them in the protocol's own schema format rather than Zod or JSON
66
+ * Schema. Without this it has to rebuild the shape by hand — which is a second
67
+ * generator of the knowledge `CORE-03` exists to keep in one place, and it
68
+ * drifts silently: an advertised field the validator rejects, or a ceiling the
69
+ * data plane does not apply.
70
+ *
71
+ * Derived from `buildDatasetInputSchema`, so there is one source for which
72
+ * dimensions are groupable, which measures exist, what the filter operators
73
+ * are, what the orderable fields are, which grains are supported, and what the
74
+ * row ceiling is.
75
+ */
76
+ export declare function buildDatasetInputProtocolSchema(dataset: SemanticQuerySchemaSource, options?: SemanticQuerySchemaOptions, path?: string): ProtocolSchema;
77
+ /** The metric form of {@link buildDatasetInputProtocolSchema}. */
78
+ export declare function buildMetricInputProtocolSchema(dataset: SemanticQuerySchemaSource, metricName: string, options?: SemanticQuerySchemaOptions, metricContract?: SemanticMetricQueryContract, path?: string): ProtocolSchema;
60
79
  //# sourceMappingURL=semantic-query-schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,iFAAiF;IACjF,UAAU,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,gGAAgG;AAChG,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAC9C,CAAC;AAEF,eAAO,MAAM,oCAAoC;;;;;;;EAO/C,CAAC;AAEH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AA6I9E,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE,0BAA+B,GACvC,UAAU,CAOZ;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,yBAAyB,EAClC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,0BAA+B,EACxC,cAAc,CAAC,EAAE,2BAA2B,GAC3C,UAAU,CAaZ;AAsBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,UAAU,CAMZ;AAyBD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,EACnD,OAAO,GAAE,0BAA+B,GACvC,6BAA6B,CAgD/B"}
1
+ {"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAI1D,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,iFAAiF;IACjF,UAAU,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,gGAAgG;AAChG,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAC9C,CAAC;AAEF,eAAO,MAAM,oCAAoC;;;;;;;EAO/C,CAAC;AAEH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AA8I9E,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE,0BAA+B,GACvC,UAAU,CAOZ;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,yBAAyB,EAClC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,0BAA+B,EACxC,cAAc,CAAC,EAAE,2BAA2B,GAC3C,UAAU,CAaZ;AAsBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,UAAU,CAMZ;AAyBD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,EACnD,OAAO,GAAE,0BAA+B,GACvC,6BAA6B,CAgD/B;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE,0BAA+B,EACxC,IAAI,SAAkB,GACrB,cAAc,CAEhB;AAED,kEAAkE;AAClE,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,yBAAyB,EAClC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,0BAA+B,EACxC,cAAc,CAAC,EAAE,2BAA2B,EAC5C,IAAI,SAAiB,GACpB,cAAc,CAKhB"}
@@ -4,6 +4,7 @@ import { z } from 'zod';
4
4
  import { zodToJsonSchema } from 'zod-to-json-schema';
5
5
  import { getDatasetCatalog, getGroupableRelationshipFields, getQueryableRelationshipFields, } from './catalog.js';
6
6
  import { SEMANTIC_FILTER_OPERATORS } from './constants.js';
7
+ import { zodToProtocolSchema } from './protocol-schema-adapter.js';
7
8
  import { compareStrings, stableStringify, uniqueSorted } from './utils/canonical-json.js';
8
9
  export const DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS = Object.freeze({
9
10
  maxResultSize: 10_000,
@@ -95,6 +96,7 @@ function queryShape(catalog, metricName, options, metric, localRelationshipField
95
96
  : uniqueSorted([
96
97
  ...dimensions,
97
98
  ...Object.keys(catalog.measures),
99
+ ...Object.keys(catalog.derivedMeasures ?? {}),
98
100
  ...(catalog.supportedGrains.length > 0 ? ['period'] : []),
99
101
  ]);
100
102
  const maxResultSize = options.enforceResultLimit === false
@@ -110,7 +112,7 @@ function queryShape(catalog, metricName, options, metric, localRelationshipField
110
112
  return {
111
113
  dimensions: boundedArray(fieldEnum(dimensions), lowerLimit(catalog.limits?.maxDimensions, limits.maxDimensions)),
112
114
  ...(metricName ? {} : {
113
- measures: boundedArray(fieldEnum(Object.keys(catalog.measures)), lowerLimit(catalog.limits?.maxMeasures, limits.maxMeasures)),
115
+ measures: boundedArray(fieldEnum([...Object.keys(catalog.measures), ...Object.keys(catalog.derivedMeasures ?? {})]), lowerLimit(catalog.limits?.maxMeasures, limits.maxMeasures)),
114
116
  }),
115
117
  filters: boundedArray(filterSchema(catalog, filterFields), lowerLimit(catalog.limits?.maxFilters, limits.maxFilters)),
116
118
  orderBy: boundedArray(z.object({
@@ -221,3 +223,25 @@ export function buildCanonicalSemanticQuerySchemas(datasets, options = {}) {
221
223
  manifestHash,
222
224
  });
223
225
  }
226
+ /**
227
+ * The same input schema, as a `ProtocolSchema`.
228
+ *
229
+ * A hosted registry advertises one endpoint per dataset and per metric, and it
230
+ * advertises them in the protocol's own schema format rather than Zod or JSON
231
+ * Schema. Without this it has to rebuild the shape by hand — which is a second
232
+ * generator of the knowledge `CORE-03` exists to keep in one place, and it
233
+ * drifts silently: an advertised field the validator rejects, or a ceiling the
234
+ * data plane does not apply.
235
+ *
236
+ * Derived from `buildDatasetInputSchema`, so there is one source for which
237
+ * dimensions are groupable, which measures exist, what the filter operators
238
+ * are, what the orderable fields are, which grains are supported, and what the
239
+ * row ceiling is.
240
+ */
241
+ export function buildDatasetInputProtocolSchema(dataset, options = {}, path = 'dataset.input') {
242
+ return zodToProtocolSchema(buildDatasetInputSchema(dataset, options), path);
243
+ }
244
+ /** The metric form of {@link buildDatasetInputProtocolSchema}. */
245
+ export function buildMetricInputProtocolSchema(dataset, metricName, options = {}, metricContract, path = 'metric.input') {
246
+ return zodToProtocolSchema(buildMetricInputSchema(dataset, metricName, options, metricContract), path);
247
+ }