@hypequery/datasets 0.1.0 → 0.2.1
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/README.md +498 -0
- package/dist/api.type-test.d.ts +2 -0
- package/dist/api.type-test.d.ts.map +1 -0
- package/dist/api.type-test.js +103 -0
- package/dist/catalog.d.ts +68 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +105 -0
- package/dist/constants.d.ts +13 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +26 -0
- package/dist/dataset-query.d.ts +16 -0
- package/dist/dataset-query.d.ts.map +1 -0
- package/dist/dataset-query.js +56 -0
- package/dist/dataset.d.ts +1 -1
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +22 -157
- package/dist/executor.d.ts +42 -14
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +188 -36
- package/dist/formulas.d.ts +1 -1
- package/dist/formulas.d.ts.map +1 -1
- package/dist/formulas.js +27 -12
- package/dist/in-memory-backend.d.ts +5 -0
- package/dist/in-memory-backend.d.ts.map +1 -0
- package/dist/in-memory-backend.js +221 -0
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -5
- package/dist/internal.d.ts +23 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +19 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +1 -0
- package/dist/query-builder-protocol.d.ts +2 -2
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-builder-protocol.js +1 -1
- package/dist/query-helpers.d.ts +12 -12
- package/dist/query-helpers.d.ts.map +1 -1
- package/dist/query-planner.d.ts +9 -7
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +26 -9
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +1 -1
- package/dist/relationships.d.ts +1 -1
- package/dist/relationships.js +1 -1
- package/dist/semantic-plan.d.ts +82 -0
- package/dist/semantic-plan.d.ts.map +1 -0
- package/dist/semantic-plan.js +1 -0
- package/dist/semantic-planner.d.ts +5 -0
- package/dist/semantic-planner.d.ts.map +1 -0
- package/dist/semantic-planner.js +155 -0
- package/dist/sql-utils.d.ts +1 -1
- package/dist/sql-utils.js +4 -4
- package/dist/tools.d.ts +53 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +322 -0
- package/dist/types.d.ts +130 -52
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/dataset-contract.d.ts +3 -0
- package/dist/utils/dataset-contract.d.ts.map +1 -0
- package/dist/utils/dataset-contract.js +30 -0
- package/dist/utils/dataset-metric-ref.d.ts +9 -0
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -0
- package/dist/utils/dataset-metric-ref.js +39 -0
- package/dist/utils/dataset-normalization.d.ts +10 -0
- package/dist/utils/dataset-normalization.d.ts.map +1 -0
- package/dist/utils/dataset-normalization.js +35 -0
- package/dist/utils/dataset-query-validation.d.ts +4 -0
- package/dist/utils/dataset-query-validation.d.ts.map +1 -0
- package/dist/utils/dataset-query-validation.js +96 -0
- package/dist/utils/dataset-validation.d.ts +6 -0
- package/dist/utils/dataset-validation.d.ts.map +1 -0
- package/dist/utils/dataset-validation.js +42 -0
- package/dist/utils/derived-cte-validation.d.ts +3 -0
- package/dist/utils/derived-cte-validation.d.ts.map +1 -0
- package/dist/utils/derived-cte-validation.js +32 -0
- package/dist/utils/filtered-aggregation-sql.d.ts +5 -0
- package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -0
- package/dist/utils/filtered-aggregation-sql.js +73 -0
- package/dist/utils/metric-handle.d.ts +11 -0
- package/dist/utils/metric-handle.d.ts.map +1 -0
- package/dist/utils/metric-handle.js +36 -0
- package/dist/utils/pagination.d.ts +17 -0
- package/dist/utils/pagination.d.ts.map +1 -0
- package/dist/utils/pagination.js +23 -0
- package/dist/utils/tenant-runtime.d.ts +14 -0
- package/dist/utils/tenant-runtime.d.ts.map +1 -0
- package/dist/utils/tenant-runtime.js +36 -0
- package/package.json +14 -2
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { SEMANTIC_FILTER_OPERATORS, SUPPORTED_TIME_GRAINS } from './constants.js';
|
|
2
|
+
function dimensionToCatalog(dimension) {
|
|
3
|
+
return {
|
|
4
|
+
type: dimension.fieldType,
|
|
5
|
+
column: dimension.column,
|
|
6
|
+
sql: dimension.sql,
|
|
7
|
+
label: dimension.label,
|
|
8
|
+
description: dimension.description,
|
|
9
|
+
filterable: dimension.filterable !== false,
|
|
10
|
+
groupable: dimension.groupable !== false,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function measureToCatalog(measure) {
|
|
14
|
+
return {
|
|
15
|
+
aggregation: measure.aggregation,
|
|
16
|
+
field: measure.field,
|
|
17
|
+
sql: measure.sql,
|
|
18
|
+
label: measure.label,
|
|
19
|
+
description: measure.description,
|
|
20
|
+
filterCount: measure.filters?.length ?? 0,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function filterToCatalog(filter, dimensions) {
|
|
24
|
+
return {
|
|
25
|
+
field: filter.field,
|
|
26
|
+
label: filter.label,
|
|
27
|
+
description: filter.description,
|
|
28
|
+
operators: filter.operators ? [...filter.operators] : [...SEMANTIC_FILTER_OPERATORS],
|
|
29
|
+
valueType: dimensions[filter.field]?.fieldType,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function metricToCatalog(metric) {
|
|
33
|
+
const contract = metric.contract();
|
|
34
|
+
return {
|
|
35
|
+
kind: contract.kind,
|
|
36
|
+
dataset: contract.dataset,
|
|
37
|
+
valueType: contract.valueType,
|
|
38
|
+
label: contract.label,
|
|
39
|
+
description: contract.description,
|
|
40
|
+
dimensions: contract.dimensions,
|
|
41
|
+
measures: contract.measures,
|
|
42
|
+
filters: contract.filters,
|
|
43
|
+
grains: contract.grains,
|
|
44
|
+
grain: contract.grain,
|
|
45
|
+
requires: contract.requires,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function relationshipToCatalog(relationship) {
|
|
49
|
+
return {
|
|
50
|
+
kind: relationship.kind,
|
|
51
|
+
target: relationship.target().name,
|
|
52
|
+
from: relationship.from,
|
|
53
|
+
to: relationship.to,
|
|
54
|
+
execution: 'metadata_only',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export function getDatasetCatalog(dataset) {
|
|
58
|
+
const dimensionNames = Object.keys(dataset.dimensions);
|
|
59
|
+
const measureNames = Object.keys(dataset.measures);
|
|
60
|
+
const metricNames = Object.keys(dataset.metrics ?? {});
|
|
61
|
+
const supportedGrains = dataset.timeKey ? [...SUPPORTED_TIME_GRAINS] : [];
|
|
62
|
+
const maxLimit = dataset.limits?.maxResultSize;
|
|
63
|
+
return {
|
|
64
|
+
name: dataset.name,
|
|
65
|
+
source: dataset.source,
|
|
66
|
+
tenantKey: dataset.tenantKey,
|
|
67
|
+
timeKey: dataset.timeKey,
|
|
68
|
+
dimensions: Object.fromEntries(Object.entries(dataset.dimensions).map(([name, dimension]) => [
|
|
69
|
+
name,
|
|
70
|
+
dimensionToCatalog(dimension),
|
|
71
|
+
])),
|
|
72
|
+
measures: Object.fromEntries(Object.entries(dataset.measures).map(([name, measure]) => [
|
|
73
|
+
name,
|
|
74
|
+
measureToCatalog(measure),
|
|
75
|
+
])),
|
|
76
|
+
metrics: Object.fromEntries(Object.entries(dataset.metrics ?? {}).map(([name, metric]) => [
|
|
77
|
+
name,
|
|
78
|
+
metricToCatalog(metric),
|
|
79
|
+
])),
|
|
80
|
+
filters: Object.fromEntries(Object.entries(dataset.filters).map(([name, filter]) => [
|
|
81
|
+
name,
|
|
82
|
+
filterToCatalog(filter, dataset.dimensions),
|
|
83
|
+
])),
|
|
84
|
+
relationships: Object.fromEntries(Object.entries(dataset.relationships).map(([name, relationship]) => [
|
|
85
|
+
name,
|
|
86
|
+
relationshipToCatalog(relationship),
|
|
87
|
+
])),
|
|
88
|
+
limits: dataset.limits,
|
|
89
|
+
requiresTenant: !!dataset.tenantKey,
|
|
90
|
+
supportedGrains,
|
|
91
|
+
orderableFields: [
|
|
92
|
+
...dimensionNames,
|
|
93
|
+
...measureNames,
|
|
94
|
+
...metricNames,
|
|
95
|
+
...(dataset.timeKey ? ['period'] : []),
|
|
96
|
+
],
|
|
97
|
+
maxLimit,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export function getDatasetCatalogs(datasets) {
|
|
101
|
+
return Object.fromEntries(Object.entries(datasets).map(([name, dataset]) => [
|
|
102
|
+
name,
|
|
103
|
+
getDatasetCatalog(dataset),
|
|
104
|
+
]));
|
|
105
|
+
}
|
package/dist/constants.d.ts
CHANGED
|
@@ -6,4 +6,17 @@ import type { TimeGrain } from './types.js';
|
|
|
6
6
|
* Maps time grain to ClickHouse date truncation functions.
|
|
7
7
|
*/
|
|
8
8
|
export declare const GRAIN_FUNCTIONS: Record<TimeGrain, string>;
|
|
9
|
+
/**
|
|
10
|
+
* The set of time grains supported by the planner. Derived from
|
|
11
|
+
* {@link GRAIN_FUNCTIONS} so the two never drift apart.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SUPPORTED_TIME_GRAINS: TimeGrain[];
|
|
14
|
+
/**
|
|
15
|
+
* The filter operators accepted by semantic dataset and metric inputs.
|
|
16
|
+
*/
|
|
17
|
+
export declare const SEMANTIC_FILTER_OPERATORS: readonly ["eq", "neq", "gt", "gte", "lt", "lte", "in", "notIn", "between", "like"];
|
|
18
|
+
/**
|
|
19
|
+
* Narrowing guard for a runtime-provided grain value.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isSupportedTimeGrain(grain: unknown): grain is TimeGrain;
|
|
9
22
|
//# sourceMappingURL=constants.d.ts.map
|
package/dist/constants.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAgB,SAAS,EAAE,MAAM,YAAY,CAAC;AAE1D;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAMrD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAmC,SAAS,EAAE,CAAC;AAEjF;;GAEG;AACH,eAAO,MAAM,yBAAyB,oFAWkB,CAAC;AAEzD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAEvE"}
|
package/dist/constants.js
CHANGED
|
@@ -11,3 +11,29 @@ export const GRAIN_FUNCTIONS = {
|
|
|
11
11
|
quarter: 'toStartOfQuarter',
|
|
12
12
|
year: 'toStartOfYear',
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* The set of time grains supported by the planner. Derived from
|
|
16
|
+
* {@link GRAIN_FUNCTIONS} so the two never drift apart.
|
|
17
|
+
*/
|
|
18
|
+
export const SUPPORTED_TIME_GRAINS = Object.keys(GRAIN_FUNCTIONS);
|
|
19
|
+
/**
|
|
20
|
+
* The filter operators accepted by semantic dataset and metric inputs.
|
|
21
|
+
*/
|
|
22
|
+
export const SEMANTIC_FILTER_OPERATORS = [
|
|
23
|
+
'eq',
|
|
24
|
+
'neq',
|
|
25
|
+
'gt',
|
|
26
|
+
'gte',
|
|
27
|
+
'lt',
|
|
28
|
+
'lte',
|
|
29
|
+
'in',
|
|
30
|
+
'notIn',
|
|
31
|
+
'between',
|
|
32
|
+
'like',
|
|
33
|
+
];
|
|
34
|
+
/**
|
|
35
|
+
* Narrowing guard for a runtime-provided grain value.
|
|
36
|
+
*/
|
|
37
|
+
export function isSupportedTimeGrain(grain) {
|
|
38
|
+
return typeof grain === 'string' && Object.hasOwn(GRAIN_FUNCTIONS, grain);
|
|
39
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery, DatasetQueryResult, ExecutionContext } from './types.js';
|
|
2
|
+
import type { QueryBuilderFactoryLike, QueryBuilderLike } from './query-builder-protocol.js';
|
|
3
|
+
import { type ValidationResult } from './validation.js';
|
|
4
|
+
export interface DatasetQueryExecutionOptions {
|
|
5
|
+
builderFactory: QueryBuilderFactoryLike;
|
|
6
|
+
context?: ExecutionContext;
|
|
7
|
+
/**
|
|
8
|
+
* Overrides the SQL `LIMIT` without affecting validation (which still uses
|
|
9
|
+
* `query.limit`). Used to over-fetch one row for pagination's `hasMore`.
|
|
10
|
+
*/
|
|
11
|
+
executionLimit?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function validateDatasetQuery(ds: AnyDatasetInstance, query: DatasetQuery, context?: ExecutionContext): ValidationResult;
|
|
14
|
+
export declare function buildDatasetQueryBuilder(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions): QueryBuilderLike;
|
|
15
|
+
export declare function runDatasetQuery(ds: AnyDatasetInstance, query: DatasetQuery, options: DatasetQueryExecutionOptions): Promise<DatasetQueryResult>;
|
|
16
|
+
//# sourceMappingURL=dataset-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dataset-query.d.ts","sourceRoot":"","sources":["../src/dataset-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAQ7F,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAoBxD,MAAM,WAAW,4BAA4B;IAC3C,cAAc,EAAE,uBAAuB,CAAC;IACxC,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAElB;AAED,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,GACpC,gBAAgB,CAwClB;AAED,wBAAsB,eAAe,CACnC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,kBAAkB,CAAC,CAa7B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { appendOrderLimitOffset, applyMeasureDefinition, buildDimensionSelectionPlan, resolveFilterField, resolveTenantFilterColumn, } from './query-planner.js';
|
|
2
|
+
import { validateDatasetQueryInput } from './utils/dataset-query-validation.js';
|
|
3
|
+
import { getRuntimeTenantId, getRuntimeTenantPredicate, } from './utils/tenant-runtime.js';
|
|
4
|
+
import { applyPagination, overfetchLimit } from './utils/pagination.js';
|
|
5
|
+
function toResultMeta(qb, timingMs, context) {
|
|
6
|
+
return {
|
|
7
|
+
sql: qb.toSQLWithParams().sql,
|
|
8
|
+
timingMs,
|
|
9
|
+
tenant: getRuntimeTenantId(context),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function validateDatasetQuery(ds, query, context) {
|
|
13
|
+
return validateDatasetQueryInput(ds, query, context);
|
|
14
|
+
}
|
|
15
|
+
export function buildDatasetQueryBuilder(ds, query, options) {
|
|
16
|
+
const validation = validateDatasetQuery(ds, query, options.context);
|
|
17
|
+
if (!validation.valid) {
|
|
18
|
+
throw new Error(`Invalid dataset query: ${validation.errors.join('; ')}`);
|
|
19
|
+
}
|
|
20
|
+
let qb = options.builderFactory.table(ds.source);
|
|
21
|
+
const { selectParts, groupByParts } = buildDimensionSelectionPlan(ds, query.dimensions ?? [], query.by);
|
|
22
|
+
const measureNames = query.measures ?? Object.keys(ds.measures);
|
|
23
|
+
if (selectParts.length > 0) {
|
|
24
|
+
qb = qb.select(selectParts);
|
|
25
|
+
}
|
|
26
|
+
for (const measureName of measureNames) {
|
|
27
|
+
qb = applyMeasureDefinition(qb, ds, measureName, ds.measures[measureName]);
|
|
28
|
+
}
|
|
29
|
+
if (groupByParts.length > 0) {
|
|
30
|
+
qb = qb.groupBy(groupByParts);
|
|
31
|
+
}
|
|
32
|
+
const tenantColumn = resolveTenantFilterColumn(ds, options.context);
|
|
33
|
+
const tenantPredicate = getRuntimeTenantPredicate(options.context);
|
|
34
|
+
if (tenantPredicate && tenantColumn) {
|
|
35
|
+
qb = qb.where(tenantColumn, tenantPredicate.operator, tenantPredicate.value);
|
|
36
|
+
}
|
|
37
|
+
for (const filter of query.filters ?? []) {
|
|
38
|
+
const resolvedField = resolveFilterField(ds, filter.field);
|
|
39
|
+
qb = qb.where(resolvedField, filter.operator, filter.value);
|
|
40
|
+
}
|
|
41
|
+
return appendOrderLimitOffset(qb, query.orderBy, query.by, options.executionLimit ?? query.limit, query.offset);
|
|
42
|
+
}
|
|
43
|
+
export async function runDatasetQuery(ds, query, options) {
|
|
44
|
+
const start = Date.now();
|
|
45
|
+
// Over-fetch one row so we can report `hasMore` without a count query.
|
|
46
|
+
const qb = buildDatasetQueryBuilder(ds, query, {
|
|
47
|
+
...options,
|
|
48
|
+
executionLimit: overfetchLimit(query.limit),
|
|
49
|
+
});
|
|
50
|
+
const rows = await qb.execute();
|
|
51
|
+
const { data, pagination } = applyPagination(rows, query.limit, query.offset);
|
|
52
|
+
return {
|
|
53
|
+
data,
|
|
54
|
+
meta: { ...toResultMeta(qb, Date.now() - start, options.context), rowCount: data.length, pagination },
|
|
55
|
+
};
|
|
56
|
+
}
|
package/dist/dataset.d.ts
CHANGED
|
@@ -29,5 +29,5 @@
|
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
31
|
import type { DatasetConfig, DatasetInstance, DimensionDefinition, MeasureDefinition, RelationshipDefinition } from './types.js';
|
|
32
|
-
export declare function dataset<TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string, MeasureDefinition> = Record<string, never>, TRelationships extends Record<string, RelationshipDefinition> = Record<string, never>>(name:
|
|
32
|
+
export declare function dataset<TDatasetName extends string, TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string, MeasureDefinition> = Record<string, never>, TRelationships extends Record<string, RelationshipDefinition> = Record<string, never>>(name: TDatasetName, config: DatasetConfig<TDimensions, TMeasures, TRelationships>): DatasetInstance<TDimensions, TMeasures, TRelationships, TDatasetName>;
|
|
33
33
|
//# sourceMappingURL=dataset.d.ts.map
|
package/dist/dataset.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset.d.ts","sourceRoot":"","sources":["../src/dataset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"dataset.d.ts","sourceRoot":"","sources":["../src/dataset.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EACV,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EAKvB,MAAM,YAAY,CAAC;AAkBpB,wBAAgB,OAAO,CACrB,YAAY,SAAS,MAAM,EAC3B,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACvD,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC3E,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAErF,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAC5D,eAAe,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,CAAC,CA2DvE"}
|
package/dist/dataset.js
CHANGED
|
@@ -28,135 +28,29 @@
|
|
|
28
28
|
* });
|
|
29
29
|
* ```
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
return 'uses' in config && 'formula' in config;
|
|
35
|
-
}
|
|
36
|
-
function buildContract(metricName, ds, spec, label, description, grain) {
|
|
37
|
-
const dimensionNames = Object.keys(ds.dimensions);
|
|
38
|
-
const measureNames = Object.keys(ds.measures);
|
|
39
|
-
const filterNames = Object.keys(ds.filters).length > 0
|
|
40
|
-
? Object.keys(ds.filters)
|
|
41
|
-
: dimensionNames.filter((name) => ds.dimensions[name]?.filterable !== false);
|
|
42
|
-
const kind = grain
|
|
43
|
-
? 'grained_metric'
|
|
44
|
-
: spec.__type === 'derived_metric_spec'
|
|
45
|
-
? 'derived_metric'
|
|
46
|
-
: 'metric';
|
|
47
|
-
return {
|
|
48
|
-
kind,
|
|
49
|
-
name: metricName,
|
|
50
|
-
dataset: ds.name,
|
|
51
|
-
valueType: 'number',
|
|
52
|
-
label,
|
|
53
|
-
description,
|
|
54
|
-
dimensions: dimensionNames,
|
|
55
|
-
measures: measureNames,
|
|
56
|
-
filters: filterNames,
|
|
57
|
-
grains: ds.timeKey ? ALL_GRAINS : [],
|
|
58
|
-
grain,
|
|
59
|
-
requires: spec.__type === 'derived_metric_spec'
|
|
60
|
-
? Object.keys(spec.uses)
|
|
61
|
-
: undefined,
|
|
62
|
-
tenantScoped: !!ds.tenantKey,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function createMetricRef(ds, name, spec, label, description) {
|
|
66
|
-
const ref = {
|
|
67
|
-
__type: 'metric_ref',
|
|
68
|
-
datasetName: ds.name,
|
|
69
|
-
name,
|
|
70
|
-
spec,
|
|
71
|
-
label,
|
|
72
|
-
description,
|
|
73
|
-
dataset: ds,
|
|
74
|
-
by(grain) {
|
|
75
|
-
if (!ds.timeKey) {
|
|
76
|
-
throw new Error(`Cannot apply .by("${grain}") to metric "${name}" — ` +
|
|
77
|
-
`dataset "${ds.name}" has no timeKey defined.`);
|
|
78
|
-
}
|
|
79
|
-
return {
|
|
80
|
-
__type: 'grained_metric_ref',
|
|
81
|
-
metric: ref,
|
|
82
|
-
grain,
|
|
83
|
-
contract() {
|
|
84
|
-
return buildContract(name, ds, spec, label, description, grain);
|
|
85
|
-
},
|
|
86
|
-
};
|
|
87
|
-
},
|
|
88
|
-
contract() {
|
|
89
|
-
return buildContract(name, ds, spec, label, description);
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
return ref;
|
|
93
|
-
}
|
|
94
|
-
function validateBaseMetric(ds, metricName, spec, options) {
|
|
95
|
-
const dimension = ds.dimensions[spec.field];
|
|
96
|
-
if (!dimension && !options?.allowHiddenField) {
|
|
97
|
-
throw new Error(`Invalid metric "${metricName}": dimension "${spec.field}" does not exist on dataset "${ds.name}".`);
|
|
98
|
-
}
|
|
99
|
-
if (dimension &&
|
|
100
|
-
(spec.aggregation === 'sum' || spec.aggregation === 'avg') &&
|
|
101
|
-
!NUMERIC_FIELD_TYPES.has(dimension.fieldType)) {
|
|
102
|
-
throw new Error(`Invalid metric "${metricName}": ${spec.aggregation}() requires a numeric dimension, but "${spec.field}" is ${dimension.fieldType}.`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function validateDerivedMetric(ds, metricName, config) {
|
|
106
|
-
const usedMetrics = Object.entries(config.uses);
|
|
107
|
-
if (usedMetrics.length === 0) {
|
|
108
|
-
throw new Error(`Invalid metric "${metricName}": derived metrics must reference at least one base metric.`);
|
|
109
|
-
}
|
|
110
|
-
for (const [alias, metric] of usedMetrics) {
|
|
111
|
-
if (metric.datasetName !== ds.name) {
|
|
112
|
-
throw new Error(`Invalid metric "${metricName}": referenced metric "${alias}" belongs to dataset "${metric.datasetName}", expected "${ds.name}".`);
|
|
113
|
-
}
|
|
114
|
-
if (metric.spec.__type !== 'aggregation_spec') {
|
|
115
|
-
throw new Error(`Invalid metric "${metricName}": referenced metric "${alias}" must be a base aggregation on dataset "${ds.name}".`);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function normalizeDimensions(config) {
|
|
120
|
-
return config.dimensions;
|
|
121
|
-
}
|
|
122
|
-
function normalizeFilters(dimensions, filters) {
|
|
123
|
-
if (filters) {
|
|
124
|
-
return filters;
|
|
125
|
-
}
|
|
126
|
-
return Object.fromEntries(Object.entries(dimensions)
|
|
127
|
-
.filter(([, definition]) => definition.filterable !== false)
|
|
128
|
-
.map(([name]) => [
|
|
129
|
-
name,
|
|
130
|
-
{
|
|
131
|
-
__type: 'filter_definition',
|
|
132
|
-
field: name,
|
|
133
|
-
},
|
|
134
|
-
]));
|
|
135
|
-
}
|
|
136
|
-
function measureToAggregationSpec(measureName, definition) {
|
|
137
|
-
if (!definition.field) {
|
|
138
|
-
throw new Error(`Invalid measure "${measureName}": a backing field is required.`);
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
__type: 'aggregation_spec',
|
|
142
|
-
aggregation: definition.aggregation,
|
|
143
|
-
field: definition.field,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
function buildDatasetQueryContract(ds, config) {
|
|
147
|
-
return {
|
|
148
|
-
dataset: ds.name,
|
|
149
|
-
dimensions: config.dimensions ? [...config.dimensions] : Object.keys(ds.dimensions),
|
|
150
|
-
measures: config.measures ? [...config.measures] : Object.keys(ds.measures),
|
|
151
|
-
filters: Object.keys(ds.filters),
|
|
152
|
-
grains: ds.timeKey ? ALL_GRAINS : [],
|
|
153
|
-
tenantScoped: !!ds.tenantKey,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
31
|
+
import { createDerivedMetricSpec, createMetricRef, isDerivedMetricConfig, } from './utils/dataset-metric-ref.js';
|
|
32
|
+
import { measureToAggregationSpec, normalizeDimensions, normalizeFilters, normalizeMeasures, normalizeRelationships, } from './utils/dataset-normalization.js';
|
|
33
|
+
import { validateBaseMetric, validateDerivedMetric, } from './utils/dataset-validation.js';
|
|
156
34
|
export function dataset(name, config) {
|
|
157
35
|
const dimensions = normalizeDimensions(config);
|
|
158
|
-
const measures = (config.measures
|
|
36
|
+
const measures = normalizeMeasures(config.measures);
|
|
159
37
|
const filters = normalizeFilters(dimensions, config.filters);
|
|
38
|
+
const relationships = normalizeRelationships(config.relationships);
|
|
39
|
+
function metric(metricName, metricConfig) {
|
|
40
|
+
if (isDerivedMetricConfig(metricConfig)) {
|
|
41
|
+
validateDerivedMetric(ds, metricName, metricConfig);
|
|
42
|
+
const derivedSpec = createDerivedMetricSpec(metricConfig);
|
|
43
|
+
return createMetricRef(ds, metricName, derivedSpec, metricConfig.label, metricConfig.description);
|
|
44
|
+
}
|
|
45
|
+
const measureName = metricConfig.measure;
|
|
46
|
+
const measure = ds.measures[measureName];
|
|
47
|
+
if (!measure) {
|
|
48
|
+
throw new Error(`Invalid metric "${metricName}": measure "${measureName}" does not exist on dataset "${ds.name}".`);
|
|
49
|
+
}
|
|
50
|
+
const spec = measureToAggregationSpec(measureName, measure);
|
|
51
|
+
validateBaseMetric(ds, metricName, spec, { allowHiddenField: true });
|
|
52
|
+
return createMetricRef(ds, metricName, spec, metricConfig.label ?? measure.label, metricConfig.description ?? measure.description);
|
|
53
|
+
}
|
|
160
54
|
const ds = {
|
|
161
55
|
__type: 'dataset',
|
|
162
56
|
name,
|
|
@@ -166,38 +60,9 @@ export function dataset(name, config) {
|
|
|
166
60
|
dimensions,
|
|
167
61
|
measures,
|
|
168
62
|
filters,
|
|
169
|
-
relationships
|
|
63
|
+
relationships,
|
|
170
64
|
limits: config.limits,
|
|
171
|
-
metric
|
|
172
|
-
if (isDerivedMetricConfig(metricConfig)) {
|
|
173
|
-
validateDerivedMetric(ds, metricName, metricConfig);
|
|
174
|
-
const derivedSpec = {
|
|
175
|
-
__type: 'derived_metric_spec',
|
|
176
|
-
uses: metricConfig.uses,
|
|
177
|
-
formula: metricConfig.formula,
|
|
178
|
-
};
|
|
179
|
-
return createMetricRef(ds, metricName, derivedSpec, metricConfig.label, metricConfig.description);
|
|
180
|
-
}
|
|
181
|
-
// Base metric (measure-based)
|
|
182
|
-
const measureName = metricConfig.measure;
|
|
183
|
-
const measure = ds.measures[measureName];
|
|
184
|
-
if (!measure) {
|
|
185
|
-
throw new Error(`Invalid metric "${metricName}": measure "${measureName}" does not exist on dataset "${ds.name}".`);
|
|
186
|
-
}
|
|
187
|
-
const spec = measureToAggregationSpec(measureName, measure);
|
|
188
|
-
validateBaseMetric(ds, metricName, spec, { allowHiddenField: true });
|
|
189
|
-
return createMetricRef(ds, metricName, spec, metricConfig.label ?? measure.label, metricConfig.description ?? measure.description);
|
|
190
|
-
},
|
|
191
|
-
query(queryConfig) {
|
|
192
|
-
return {
|
|
193
|
-
__type: 'dataset_query_ref',
|
|
194
|
-
dataset: ds,
|
|
195
|
-
config: queryConfig,
|
|
196
|
-
contract() {
|
|
197
|
-
return buildDatasetQueryContract(ds, queryConfig);
|
|
198
|
-
},
|
|
199
|
-
};
|
|
200
|
-
}
|
|
65
|
+
metric,
|
|
201
66
|
};
|
|
202
67
|
return ds;
|
|
203
68
|
}
|
package/dist/executor.d.ts
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Semantic dataset client internals.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* executor builds queries via the builder's fluent API, then calls `.execute()`.
|
|
7
|
-
* 2. **Raw adapter** (deprecated) — pass a `MetricAdapter` with a `rawQuery` function
|
|
8
|
-
* and the executor generates SQL strings manually.
|
|
9
|
-
*
|
|
10
|
-
* The executor stays DB-agnostic via duck-typed protocol interfaces.
|
|
4
|
+
* Public callers use `createDatasetClient(...).execute(...)`. The implementation
|
|
5
|
+
* stays database-agnostic via query-builder and backend protocol interfaces.
|
|
11
6
|
*/
|
|
12
|
-
import type { MetricRef, GrainedMetricRef, MetricQuery, MetricResult, ExecutionContext } from './types.js';
|
|
7
|
+
import type { MetricRef, GrainedMetricRef, MetricQuery, MetricResult, DatasetQuery, DatasetQueryResult, ExecutionContext, AnyDatasetInstance } from './types.js';
|
|
13
8
|
import type { QueryBuilderFactoryLike } from './query-builder-protocol.js';
|
|
9
|
+
import type { PlanNode, SemanticBackend } from './semantic-plan.js';
|
|
14
10
|
import { type ValidationResult } from './validation.js';
|
|
15
|
-
|
|
11
|
+
import { type DatasetQueryExecutionOptions } from './dataset-query.js';
|
|
12
|
+
export interface MetricQueryEngineOptions {
|
|
16
13
|
/** Query builder factory for executing metrics. */
|
|
17
14
|
builderFactory: QueryBuilderFactoryLike;
|
|
18
15
|
}
|
|
19
|
-
export
|
|
16
|
+
export interface CreateDatasetClientOptions {
|
|
17
|
+
/** Query builder factory for executing semantic metric and dataset queries. */
|
|
18
|
+
queryBuilder?: QueryBuilderFactoryLike;
|
|
19
|
+
/** Semantic backend for executing neutral semantic plans. */
|
|
20
|
+
backend?: SemanticBackend;
|
|
21
|
+
}
|
|
22
|
+
export type SemanticTarget = MetricRef | GrainedMetricRef | AnyDatasetInstance;
|
|
23
|
+
export type SemanticQuery<TTarget extends SemanticTarget> = TTarget extends AnyDatasetInstance ? DatasetQuery : MetricQuery;
|
|
24
|
+
export type SemanticResult<TTarget extends SemanticTarget, TRow = Record<string, unknown>> = TTarget extends AnyDatasetInstance ? DatasetQueryResult<TRow> : MetricResult<TRow>;
|
|
25
|
+
export interface DatasetClient {
|
|
26
|
+
execute<TRow = Record<string, unknown>, TTarget extends SemanticTarget = SemanticTarget>(target: TTarget, query?: SemanticQuery<TTarget>, context?: ExecutionContext): Promise<SemanticResult<TTarget, TRow>>;
|
|
27
|
+
toSQL<TTarget extends SemanticTarget>(target: TTarget, query?: SemanticQuery<TTarget>, context?: ExecutionContext): string;
|
|
28
|
+
validate<TTarget extends SemanticTarget>(target: TTarget, query?: SemanticQuery<TTarget>, context?: ExecutionContext): ValidationResult;
|
|
29
|
+
}
|
|
30
|
+
export declare class MetricQueryEngine {
|
|
20
31
|
private builderFactory;
|
|
21
|
-
constructor(options:
|
|
22
|
-
getBuilderFactory(): QueryBuilderFactoryLike;
|
|
32
|
+
constructor(options: MetricQueryEngineOptions);
|
|
33
|
+
protected getBuilderFactory(): QueryBuilderFactoryLike;
|
|
23
34
|
/**
|
|
24
35
|
* Execute a metric query. Generates SQL, applies tenant/filter context, executes.
|
|
25
36
|
*/
|
|
@@ -31,9 +42,26 @@ export declare class MetricExecutor {
|
|
|
31
42
|
/**
|
|
32
43
|
* Validate a metric query against the metric's contract.
|
|
33
44
|
*/
|
|
34
|
-
validate(metric: MetricRef | GrainedMetricRef, query: MetricQuery): ValidationResult;
|
|
45
|
+
validate(metric: MetricRef | GrainedMetricRef, query: MetricQuery, context?: ExecutionContext): ValidationResult;
|
|
35
46
|
private runViaBuilder;
|
|
36
47
|
private buildBaseQuery;
|
|
37
48
|
private buildDerivedSQLViaBuilder;
|
|
38
49
|
}
|
|
50
|
+
export declare class DatasetClientImpl extends MetricQueryEngine implements DatasetClient {
|
|
51
|
+
private backend?;
|
|
52
|
+
constructor(options: CreateDatasetClientOptions);
|
|
53
|
+
planMetric(metric: MetricRef | GrainedMetricRef, query?: MetricQuery, context?: ExecutionContext): PlanNode;
|
|
54
|
+
planDataset(ds: AnyDatasetInstance, query?: DatasetQuery, context?: ExecutionContext): PlanNode;
|
|
55
|
+
/**
|
|
56
|
+
* Execute a semantic target.
|
|
57
|
+
*/
|
|
58
|
+
execute<TRow = Record<string, unknown>, TTarget extends SemanticTarget = SemanticTarget>(target: TTarget, query?: SemanticQuery<TTarget>, context?: ExecutionContext): Promise<SemanticResult<TTarget, TRow>>;
|
|
59
|
+
toSQL<TTarget extends SemanticTarget>(target: TTarget, query?: SemanticQuery<TTarget>, context?: ExecutionContext): string;
|
|
60
|
+
validate<TTarget extends SemanticTarget>(target: TTarget, query?: SemanticQuery<TTarget>, context?: ExecutionContext): ValidationResult;
|
|
61
|
+
private executeMetric;
|
|
62
|
+
private executeDataset;
|
|
63
|
+
private toDatasetSQL;
|
|
64
|
+
}
|
|
65
|
+
export declare function createDatasetClient(options: CreateDatasetClientOptions): DatasetClient;
|
|
66
|
+
export type { DatasetQueryExecutionOptions };
|
|
39
67
|
//# sourceMappingURL=executor.d.ts.map
|
package/dist/executor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"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"}
|