@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.
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/api.type-test.js +45 -1
- package/dist/catalog.d.ts +6 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +12 -0
- package/dist/dataset-query.d.ts +2 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +18 -21
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +4 -2
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +9 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/measure.d.ts +4 -1
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +12 -0
- package/dist/portable-executor.d.ts +4 -5
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +2 -20
- package/dist/protocol-adapter.d.ts +2 -2
- package/dist/protocol-adapter.js +2 -2
- package/dist/protocol-deployment-adapter.d.ts +8 -0
- package/dist/protocol-deployment-adapter.d.ts.map +1 -0
- package/dist/protocol-deployment-adapter.js +31 -0
- package/dist/protocol-rehydrate.d.ts +4 -2
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +44 -12
- package/dist/protocol-schema-adapter.d.ts +9 -0
- package/dist/protocol-schema-adapter.d.ts.map +1 -0
- package/dist/protocol-schema-adapter.js +249 -0
- package/dist/relationships.d.ts +25 -4
- package/dist/relationships.d.ts.map +1 -1
- package/dist/relationships.js +25 -4
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +3 -0
- package/dist/semantic-query-schema.d.ts +19 -0
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +25 -1
- package/dist/types.d.ts +46 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +6 -2
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +2 -2
- package/dist/utils/dataset-definition-validation.d.ts +2 -2
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
- package/dist/utils/dataset-definition-validation.js +13 -1
- package/dist/utils/dataset-derived-query.d.ts +12 -0
- package/dist/utils/dataset-derived-query.d.ts.map +1 -0
- package/dist/utils/dataset-derived-query.js +112 -0
- package/dist/utils/dataset-measures.d.ts +10 -0
- package/dist/utils/dataset-measures.d.ts.map +1 -0
- package/dist/utils/dataset-measures.js +24 -0
- package/dist/utils/dataset-normalization.d.ts +3 -3
- package/dist/utils/dataset-normalization.d.ts.map +1 -1
- package/dist/utils/dataset-query-result.d.ts +10 -0
- package/dist/utils/dataset-query-result.d.ts.map +1 -0
- package/dist/utils/dataset-query-result.js +20 -0
- package/dist/utils/dataset-query-validation.js +2 -2
- package/dist/utils/derived-measure-validation.d.ts +3 -0
- package/dist/utils/derived-measure-validation.d.ts.map +1 -0
- package/dist/utils/derived-measure-validation.js +138 -0
- package/dist/utils/protocol-metric-expressions.d.ts +2 -1
- package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
- package/dist/utils/protocol-metric-expressions.js +4 -0
- package/dist/utils/semantic-metadata-validation.d.ts +2 -2
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
- 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,
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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(
|
|
236
|
-
|
|
237
|
-
|
|
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
|
|
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
|
-
|
|
286
|
+
const published = { ...instance, metrics };
|
|
287
|
+
registry.set(name, published);
|
|
262
288
|
}
|
|
263
|
-
|
|
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
|
+
}
|
package/dist/relationships.d.ts
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Relationship helpers for dataset definitions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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"}
|
package/dist/relationships.js
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Relationship helpers for dataset definitions.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
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
|
-
*
|
|
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,
|
|
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"}
|
package/dist/semantic-planner.js
CHANGED
|
@@ -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;
|
|
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
|
+
}
|