@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,138 @@
|
|
|
1
|
+
import { isSafeSQLIdentifier } from '../sql-utils.js';
|
|
2
|
+
import { isBaseMeasure, isDerivedMeasure } from './dataset-measures.js';
|
|
3
|
+
function fail(datasetName, measureName, detail) {
|
|
4
|
+
throw new Error(`Invalid dataset "${datasetName}": derived measure "${measureName}" ${detail}`);
|
|
5
|
+
}
|
|
6
|
+
const BINARY_OPERATORS = new Set(['add', 'subtract', 'multiply', 'divide']);
|
|
7
|
+
const FUNCTION_ARITIES = {
|
|
8
|
+
nullIfZero: [1],
|
|
9
|
+
coalesce: [2],
|
|
10
|
+
round: [2],
|
|
11
|
+
floor: [1],
|
|
12
|
+
ceil: [1],
|
|
13
|
+
};
|
|
14
|
+
function collectReferences(datasetName, measureName, expression, references, budget, depth = 0) {
|
|
15
|
+
budget.nodes += 1;
|
|
16
|
+
if (depth > 16 || budget.nodes > 256) {
|
|
17
|
+
fail(datasetName, measureName, 'formula exceeds the expression limits.');
|
|
18
|
+
}
|
|
19
|
+
if (typeof expression !== 'object' || expression === null) {
|
|
20
|
+
fail(datasetName, measureName, 'formula contains an invalid expression.');
|
|
21
|
+
}
|
|
22
|
+
switch (expression.kind) {
|
|
23
|
+
case 'ref':
|
|
24
|
+
if (typeof expression.name !== 'string' || !isSafeSQLIdentifier(expression.name)) {
|
|
25
|
+
fail(datasetName, measureName, 'formula contains an invalid input reference.');
|
|
26
|
+
}
|
|
27
|
+
references.add(expression.name);
|
|
28
|
+
return;
|
|
29
|
+
case 'literal':
|
|
30
|
+
if (expression.value !== null
|
|
31
|
+
&& typeof expression.value !== 'string'
|
|
32
|
+
&& typeof expression.value !== 'boolean'
|
|
33
|
+
&& (typeof expression.value !== 'number' || !Number.isFinite(expression.value))) {
|
|
34
|
+
fail(datasetName, measureName, 'formula contains an invalid literal.');
|
|
35
|
+
}
|
|
36
|
+
return;
|
|
37
|
+
case 'binary':
|
|
38
|
+
if (!BINARY_OPERATORS.has(expression.operator)) {
|
|
39
|
+
fail(datasetName, measureName, 'formula contains an unsupported binary operator.');
|
|
40
|
+
}
|
|
41
|
+
collectReferences(datasetName, measureName, expression.left, references, budget, depth + 1);
|
|
42
|
+
collectReferences(datasetName, measureName, expression.right, references, budget, depth + 1);
|
|
43
|
+
return;
|
|
44
|
+
case 'function':
|
|
45
|
+
if (!Array.isArray(expression.args)
|
|
46
|
+
|| !FUNCTION_ARITIES[expression.name]?.includes(expression.args.length)) {
|
|
47
|
+
fail(datasetName, measureName, 'formula contains an unsupported function or arity.');
|
|
48
|
+
}
|
|
49
|
+
expression.args.forEach(argument => collectReferences(datasetName, measureName, argument, references, budget, depth + 1));
|
|
50
|
+
return;
|
|
51
|
+
default:
|
|
52
|
+
fail(datasetName, measureName, 'formula contains an unsupported expression kind.');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function evaluateFormula(datasetName, measureName, definition) {
|
|
56
|
+
const aliases = Object.keys(definition.uses);
|
|
57
|
+
let formula;
|
|
58
|
+
try {
|
|
59
|
+
formula = definition.formula(Object.fromEntries(aliases.map(alias => [alias, alias])));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
fail(datasetName, measureName, `formula threw: ${error instanceof Error ? error.message : String(error)}.`);
|
|
63
|
+
}
|
|
64
|
+
if (typeof formula !== 'object'
|
|
65
|
+
|| formula === null
|
|
66
|
+
|| formula.__type !== 'formula_expr'
|
|
67
|
+
|| typeof formula.toSQL !== 'function'
|
|
68
|
+
|| typeof formula.expression !== 'object'
|
|
69
|
+
|| formula.expression === null) {
|
|
70
|
+
fail(datasetName, measureName, 'formula must return a FormulaExpr.');
|
|
71
|
+
}
|
|
72
|
+
return formula;
|
|
73
|
+
}
|
|
74
|
+
function assertAcyclic(datasetName, derivedMeasures) {
|
|
75
|
+
const visiting = new Set();
|
|
76
|
+
const visited = new Set();
|
|
77
|
+
const visit = (name) => {
|
|
78
|
+
if (visiting.has(name))
|
|
79
|
+
fail(datasetName, name, 'contains a dependency cycle.');
|
|
80
|
+
if (visited.has(name))
|
|
81
|
+
return;
|
|
82
|
+
if (!Object.hasOwn(derivedMeasures, name))
|
|
83
|
+
return;
|
|
84
|
+
const definition = derivedMeasures[name];
|
|
85
|
+
visiting.add(name);
|
|
86
|
+
Object.values(definition.uses).forEach(dependency => visit(dependency));
|
|
87
|
+
visiting.delete(name);
|
|
88
|
+
visited.add(name);
|
|
89
|
+
};
|
|
90
|
+
Object.keys(derivedMeasures).forEach(visit);
|
|
91
|
+
}
|
|
92
|
+
export function validateDerivedMeasures(datasetName, measures) {
|
|
93
|
+
const baseMeasures = Object.fromEntries(Object.entries(measures).filter(([, definition]) => definition && isBaseMeasure(definition)));
|
|
94
|
+
const derivedMeasures = Object.fromEntries(Object.entries(measures).filter(([, definition]) => definition && isDerivedMeasure(definition)));
|
|
95
|
+
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
96
|
+
if (!isSafeSQLIdentifier(measureName)) {
|
|
97
|
+
fail(datasetName, measureName, 'name is not a safe identifier.');
|
|
98
|
+
}
|
|
99
|
+
if (typeof definition !== 'object' || definition === null || definition.__type !== 'derived_measure_definition') {
|
|
100
|
+
fail(datasetName, measureName, 'must be created with measure.derived().');
|
|
101
|
+
}
|
|
102
|
+
if (typeof definition.uses !== 'object' || definition.uses === null || Array.isArray(definition.uses)) {
|
|
103
|
+
fail(datasetName, measureName, 'must declare an input map.');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
assertAcyclic(datasetName, derivedMeasures);
|
|
107
|
+
for (const [measureName, definition] of Object.entries(derivedMeasures)) {
|
|
108
|
+
const uses = Object.entries(definition.uses);
|
|
109
|
+
if (uses.length === 0)
|
|
110
|
+
fail(datasetName, measureName, 'must reference at least one base measure.');
|
|
111
|
+
if (typeof definition.formula !== 'function')
|
|
112
|
+
fail(datasetName, measureName, 'must define a formula function.');
|
|
113
|
+
for (const [alias, dependencyName] of uses) {
|
|
114
|
+
if (!isSafeSQLIdentifier(alias))
|
|
115
|
+
fail(datasetName, measureName, `has invalid input alias "${alias}".`);
|
|
116
|
+
if (typeof dependencyName !== 'string' || dependencyName.includes('.')) {
|
|
117
|
+
fail(datasetName, measureName, `references cross-dataset measure "${String(dependencyName)}".`);
|
|
118
|
+
}
|
|
119
|
+
if (Object.hasOwn(derivedMeasures, dependencyName)) {
|
|
120
|
+
fail(datasetName, measureName, `references derived measure "${dependencyName}"; v1 inputs must be base measures.`);
|
|
121
|
+
}
|
|
122
|
+
if (!Object.hasOwn(baseMeasures, dependencyName))
|
|
123
|
+
fail(datasetName, measureName, `references missing measure "${dependencyName}".`);
|
|
124
|
+
}
|
|
125
|
+
const references = new Set();
|
|
126
|
+
collectReferences(datasetName, measureName, evaluateFormula(datasetName, measureName, definition).expression, references, { nodes: 0 });
|
|
127
|
+
for (const reference of references) {
|
|
128
|
+
if (!Object.hasOwn(definition.uses, reference)) {
|
|
129
|
+
fail(datasetName, measureName, `formula references undeclared input alias "${reference}".`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const [alias] of uses) {
|
|
133
|
+
if (!references.has(alias)) {
|
|
134
|
+
fail(datasetName, measureName, `declares unused input alias "${alias}".`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ProtocolExpression, type ProtocolMetricDerivation } from '@hypequery/protocol';
|
|
2
|
-
import type { AggregationSpec, DerivedMetricSpec, MetricFilter } from '../types.js';
|
|
2
|
+
import type { AggregationSpec, DerivedMeasureDefinition, DerivedMetricSpec, MetricFilter } from '../types.js';
|
|
3
3
|
export declare function filterExpression(filter: MetricFilter): ProtocolExpression;
|
|
4
|
+
export declare function derivedMeasureExpression(definition: DerivedMeasureDefinition): ProtocolExpression;
|
|
4
5
|
export declare function metricExpression(spec: AggregationSpec | DerivedMetricSpec): ProtocolExpression;
|
|
5
6
|
/**
|
|
6
7
|
* The formula in the shape it was authored in, beside the inlined form.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-metric-expressions.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-metric-expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAEgB,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAC5E,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"protocol-metric-expressions.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-metric-expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAEgB,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAC5E,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsC9G,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,kBAAkB,CAgBzE;AA+CD,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,wBAAwB,GAAG,kBAAkB,CAGjG;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,kBAAkB,CAQ9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CASlF"}
|
|
@@ -85,6 +85,10 @@ function semanticExpression(expression, references = {}) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
export function derivedMeasureExpression(definition) {
|
|
89
|
+
const aliases = Object.fromEntries(Object.keys(definition.uses).map(alias => [alias, alias]));
|
|
90
|
+
return semanticExpression(definition.formula(aliases).expression);
|
|
91
|
+
}
|
|
88
92
|
export function metricExpression(spec) {
|
|
89
93
|
if (spec.__type === 'aggregation_spec')
|
|
90
94
|
return aggregationExpression(spec);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DatasetConfig,
|
|
1
|
+
import type { DatasetConfig, DatasetMeasureDefinition, DimensionDefinition, RelationshipDefinition, SemanticMetadata } from '../types.js';
|
|
2
2
|
export declare function validateSemanticMetadata(datasetName: string, location: string, metadata: SemanticMetadata): void;
|
|
3
|
-
export declare function validateDatasetAgentMetadata<TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string,
|
|
3
|
+
export declare function validateDatasetAgentMetadata<TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string, DatasetMeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition>>(datasetName: string, config: DatasetConfig<TDimensions, TMeasures, TRelationships>): void;
|
|
4
4
|
//# sourceMappingURL=semantic-metadata-validation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-metadata-validation.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-metadata-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,
|
|
1
|
+
{"version":3,"file":"semantic-metadata-validation.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-metadata-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAoBrB,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,gBAAgB,GACzB,IAAI,CA8BN;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACvD,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,wBAAwB,CAAC,EAC1D,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAE7D,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAC5D,IAAI,CAuDN"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/datasets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Code-first TypeScript semantic layer for ClickHouse datasets, metrics, multi-tenancy, and AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"semantic-layer",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@hypequery/protocol": "^0.
|
|
45
|
+
"@hypequery/protocol": "^0.14.0",
|
|
46
46
|
"@noble/hashes": "^1.8.0",
|
|
47
47
|
"zod": "^3.22.4",
|
|
48
48
|
"zod-to-json-schema": "^3.23.5"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typescript": "^5.7.3",
|
|
61
61
|
"@vitest/coverage-v8": "^3.2.6",
|
|
62
62
|
"vitest": "^3.2.6",
|
|
63
|
-
"@hypequery/protocol-conformance": "^0.11.
|
|
63
|
+
"@hypequery/protocol-conformance": "^0.11.2"
|
|
64
64
|
},
|
|
65
65
|
"repository": {
|
|
66
66
|
"type": "git",
|