@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
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"derived-cte-validation.d.ts","sourceRoot":"","sources":["../../src/utils/derived-cte-validation.ts"],"names":[],"mappings":"AAAA,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAU/D;AAED,wBAAgB,0BAA0B,CACxC,GAAG,EAAE,MAAM,EACX,gBAAgB,EAAE,MAAM,EAAE,EAC1B,eAAe,EAAE,MAAM,EAAE,GACxB,MAAM,EAAE,CAgCV"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function extractGroupByExpressions(sql) {
|
|
2
|
+
const match = sql.match(/\bGROUP BY\b\s+(.+?)(?:\bHAVING\b|\bORDER BY\b|\bLIMIT\b|\bOFFSET\b|$)/is);
|
|
3
|
+
if (!match) {
|
|
4
|
+
return [];
|
|
5
|
+
}
|
|
6
|
+
return match[1]
|
|
7
|
+
.split(',')
|
|
8
|
+
.map(part => part.trim())
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
}
|
|
11
|
+
export function validateDerivedCteGrouping(sql, aggregateAliases, intendedGroupBy) {
|
|
12
|
+
const errors = [];
|
|
13
|
+
const actualGroupBy = extractGroupByExpressions(sql);
|
|
14
|
+
if (intendedGroupBy.length === 0 && actualGroupBy.length > 0) {
|
|
15
|
+
errors.push('Derived metric planner emitted GROUP BY for an ungrouped query.');
|
|
16
|
+
}
|
|
17
|
+
const duplicates = actualGroupBy.filter((expression, index) => actualGroupBy.indexOf(expression) !== index);
|
|
18
|
+
if (duplicates.length > 0) {
|
|
19
|
+
errors.push(`Derived metric planner emitted duplicate GROUP BY expressions: ${Array.from(new Set(duplicates)).join(', ')}`);
|
|
20
|
+
}
|
|
21
|
+
const aggregateAliasSet = new Set(aggregateAliases);
|
|
22
|
+
const aggregateAliasesInGroupBy = actualGroupBy.filter(expression => aggregateAliasSet.has(expression));
|
|
23
|
+
if (aggregateAliasesInGroupBy.length > 0) {
|
|
24
|
+
errors.push(`Derived metric planner emitted aggregate aliases in GROUP BY: ${Array.from(new Set(aggregateAliasesInGroupBy)).join(', ')}`);
|
|
25
|
+
}
|
|
26
|
+
const intendedGroupBySet = new Set(intendedGroupBy);
|
|
27
|
+
const unexpectedGroupBy = actualGroupBy.filter(expression => !intendedGroupBySet.has(expression));
|
|
28
|
+
if (unexpectedGroupBy.length > 0) {
|
|
29
|
+
errors.push(`Derived metric planner emitted unexpected GROUP BY expressions: ${Array.from(new Set(unexpectedGroupBy)).join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
return errors;
|
|
32
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AggregationSpec, AnyDatasetInstance } from '../types.js';
|
|
2
|
+
type DatasetShape = AnyDatasetInstance;
|
|
3
|
+
export declare function applyFilteredAggregationExpression(ds: DatasetShape, spec: AggregationSpec, fieldOrExpr: string): string;
|
|
4
|
+
export {};
|
|
5
|
+
//# sourceMappingURL=filtered-aggregation-sql.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filtered-aggregation-sql.d.ts","sourceRoot":"","sources":["../../src/utils/filtered-aggregation-sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAgB,MAAM,aAAa,CAAC;AAGrF,KAAK,YAAY,GAAG,kBAAkB,CAAC;AAiEvC,wBAAgB,kCAAkC,CAChD,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,eAAe,EACrB,WAAW,EAAE,MAAM,GAClB,MAAM,CAsBR"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { resolveFilterField } from '../query-planner.js';
|
|
2
|
+
function renderMeasureFilterLiteral(value) {
|
|
3
|
+
if (value === null) {
|
|
4
|
+
return 'NULL';
|
|
5
|
+
}
|
|
6
|
+
if (typeof value === 'string') {
|
|
7
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
8
|
+
}
|
|
9
|
+
if (typeof value === 'number') {
|
|
10
|
+
if (!Number.isFinite(value)) {
|
|
11
|
+
throw new Error(`Invalid non-finite numeric literal in measure filter: ${value}`);
|
|
12
|
+
}
|
|
13
|
+
return String(value);
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'boolean') {
|
|
16
|
+
return value ? '1' : '0';
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Unsupported literal type in measure filter: ${typeof value}`);
|
|
19
|
+
}
|
|
20
|
+
function renderMeasureFilterCondition(ds, filter) {
|
|
21
|
+
const field = resolveFilterField(ds, filter.field);
|
|
22
|
+
switch (filter.operator) {
|
|
23
|
+
case 'eq':
|
|
24
|
+
return `${field} = ${renderMeasureFilterLiteral(filter.value)}`;
|
|
25
|
+
case 'neq':
|
|
26
|
+
return `${field} != ${renderMeasureFilterLiteral(filter.value)}`;
|
|
27
|
+
case 'gt':
|
|
28
|
+
return `${field} > ${renderMeasureFilterLiteral(filter.value)}`;
|
|
29
|
+
case 'gte':
|
|
30
|
+
return `${field} >= ${renderMeasureFilterLiteral(filter.value)}`;
|
|
31
|
+
case 'lt':
|
|
32
|
+
return `${field} < ${renderMeasureFilterLiteral(filter.value)}`;
|
|
33
|
+
case 'lte':
|
|
34
|
+
return `${field} <= ${renderMeasureFilterLiteral(filter.value)}`;
|
|
35
|
+
case 'like':
|
|
36
|
+
return `${field} LIKE ${renderMeasureFilterLiteral(filter.value)}`;
|
|
37
|
+
case 'in':
|
|
38
|
+
case 'notIn': {
|
|
39
|
+
if (!Array.isArray(filter.value) || filter.value.length === 0) {
|
|
40
|
+
throw new Error(`"${filter.operator}" measure filters require a non-empty array.`);
|
|
41
|
+
}
|
|
42
|
+
const values = filter.value.map(renderMeasureFilterLiteral).join(', ');
|
|
43
|
+
return `${field} ${filter.operator === 'in' ? 'IN' : 'NOT IN'} (${values})`;
|
|
44
|
+
}
|
|
45
|
+
case 'between': {
|
|
46
|
+
if (!Array.isArray(filter.value) || filter.value.length !== 2) {
|
|
47
|
+
throw new Error('"between" measure filters require a two-item array.');
|
|
48
|
+
}
|
|
49
|
+
return `${field} BETWEEN ${renderMeasureFilterLiteral(filter.value[0])} AND ${renderMeasureFilterLiteral(filter.value[1])}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function applyFilteredAggregationExpression(ds, spec, fieldOrExpr) {
|
|
54
|
+
if (!spec.filters?.length) {
|
|
55
|
+
return fieldOrExpr;
|
|
56
|
+
}
|
|
57
|
+
const combinedCondition = spec.filters
|
|
58
|
+
.map(filter => renderMeasureFilterCondition(ds, filter))
|
|
59
|
+
.map(condition => `(${condition})`)
|
|
60
|
+
.join(' AND ');
|
|
61
|
+
switch (spec.aggregation) {
|
|
62
|
+
case 'sum':
|
|
63
|
+
return `if(${combinedCondition}, ${fieldOrExpr}, 0)`;
|
|
64
|
+
case 'count':
|
|
65
|
+
case 'countDistinct':
|
|
66
|
+
case 'avg':
|
|
67
|
+
case 'min':
|
|
68
|
+
case 'max':
|
|
69
|
+
return `if(${combinedCondition}, ${fieldOrExpr}, NULL)`;
|
|
70
|
+
default:
|
|
71
|
+
return fieldOrExpr;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, ExecutionContext, GrainedMetricRef, MetricFilter, MetricQuery, MetricRef, TimeGrain } from '../types.js';
|
|
2
|
+
type DatasetShape = AnyDatasetInstance;
|
|
3
|
+
export type MetricHandle = MetricRef | GrainedMetricRef;
|
|
4
|
+
export declare function isMetricHandle(value: unknown): value is MetricHandle;
|
|
5
|
+
export declare function assertMetricHandle(value: unknown): asserts value is MetricHandle;
|
|
6
|
+
export declare function getMetricRef(metric: MetricHandle): MetricRef;
|
|
7
|
+
export declare function getMetricGrain(metric: MetricHandle, query: MetricQuery): TimeGrain | undefined;
|
|
8
|
+
export declare function getTenantRuntimeColumn(ds: DatasetShape, context?: ExecutionContext): string | undefined;
|
|
9
|
+
export declare function isTenantScopedFilter(ds: DatasetShape, filter: MetricFilter, context?: ExecutionContext): boolean;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=metric-handle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-handle.d.ts","sourceRoot":"","sources":["../../src/utils/metric-handle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,SAAS,EACT,SAAS,EACV,MAAM,aAAa,CAAC;AAGrB,KAAK,YAAY,GAAG,kBAAkB,CAAC;AAMvC,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,gBAAgB,CAAC;AAExD,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAKpE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,YAAY,CAOhF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,SAAS,CAE5D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,SAAS,CAE9F;AAED,wBAAgB,sBAAsB,CACpC,EAAE,EAAE,YAAY,EAChB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,YAAY,EAChB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAOT"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { resolveDimensionExpression, resolveFilterField, resolveTenantFilterColumn } from '../query-planner.js';
|
|
2
|
+
import { getRuntimeTenantPredicate } from './tenant-runtime.js';
|
|
3
|
+
function isMetricHandleType(value) {
|
|
4
|
+
return value === 'metric_ref' || value === 'grained_metric_ref';
|
|
5
|
+
}
|
|
6
|
+
export function isMetricHandle(value) {
|
|
7
|
+
return typeof value === 'object'
|
|
8
|
+
&& value !== null
|
|
9
|
+
&& '__type' in value
|
|
10
|
+
&& isMetricHandleType(value.__type);
|
|
11
|
+
}
|
|
12
|
+
export function assertMetricHandle(value) {
|
|
13
|
+
if (!isMetricHandle(value)) {
|
|
14
|
+
throw new Error('Metric queries only support MetricRef and GrainedMetricRef. ' +
|
|
15
|
+
'dataset.query(...) is not part of the public execution API.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function getMetricRef(metric) {
|
|
19
|
+
return metric.__type === 'grained_metric_ref' ? metric.metric : metric;
|
|
20
|
+
}
|
|
21
|
+
export function getMetricGrain(metric, query) {
|
|
22
|
+
return metric.__type === 'grained_metric_ref' ? metric.grain : query.by ?? undefined;
|
|
23
|
+
}
|
|
24
|
+
export function getTenantRuntimeColumn(ds, context) {
|
|
25
|
+
if (!getRuntimeTenantPredicate(context)) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return resolveTenantFilterColumn(ds, context);
|
|
29
|
+
}
|
|
30
|
+
export function isTenantScopedFilter(ds, filter, context) {
|
|
31
|
+
const tenantColumn = getTenantRuntimeColumn(ds, context);
|
|
32
|
+
if (!tenantColumn) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return resolveFilterField(ds, filter.field) === resolveDimensionExpression(ds, tenantColumn);
|
|
36
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MetricResultMeta } from '../types.js';
|
|
2
|
+
export type PaginationMeta = NonNullable<MetricResultMeta['pagination']>;
|
|
3
|
+
/**
|
|
4
|
+
* When a limit is set, request one extra row so the executor can report
|
|
5
|
+
* `hasMore` without issuing a separate COUNT query. Returns `undefined` when
|
|
6
|
+
* no limit is set (unbounded query — there is no "next page").
|
|
7
|
+
*/
|
|
8
|
+
export declare function overfetchLimit(limit?: number): number | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Trim an over-fetched result set back to `limit` rows and derive pagination
|
|
11
|
+
* metadata. The extra row (if present) signals that another page exists.
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyPagination<T>(rows: T[], limit?: number, offset?: number): {
|
|
14
|
+
data: T[];
|
|
15
|
+
pagination?: PaginationMeta;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pagination.d.ts","sourceRoot":"","sources":["../../src/utils/pagination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,MAAM,MAAM,cAAc,GAAG,WAAW,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC,CAAC;AAEzE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEjE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,IAAI,EAAE,CAAC,EAAE,EACT,KAAK,CAAC,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,GACd;IAAE,IAAI,EAAE,CAAC,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,cAAc,CAAA;CAAE,CAU5C"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* When a limit is set, request one extra row so the executor can report
|
|
3
|
+
* `hasMore` without issuing a separate COUNT query. Returns `undefined` when
|
|
4
|
+
* no limit is set (unbounded query — there is no "next page").
|
|
5
|
+
*/
|
|
6
|
+
export function overfetchLimit(limit) {
|
|
7
|
+
return limit != null ? limit + 1 : undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Trim an over-fetched result set back to `limit` rows and derive pagination
|
|
11
|
+
* metadata. The extra row (if present) signals that another page exists.
|
|
12
|
+
*/
|
|
13
|
+
export function applyPagination(rows, limit, offset) {
|
|
14
|
+
if (limit == null) {
|
|
15
|
+
return { data: rows };
|
|
16
|
+
}
|
|
17
|
+
const hasMore = rows.length > limit;
|
|
18
|
+
const data = hasMore ? rows.slice(0, limit) : rows;
|
|
19
|
+
return {
|
|
20
|
+
data,
|
|
21
|
+
pagination: { limit, offset: offset ?? 0, hasMore },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, ExecutionContext } from '../types.js';
|
|
2
|
+
export type TenantPredicate = {
|
|
3
|
+
operator: 'eq';
|
|
4
|
+
value: string;
|
|
5
|
+
} | {
|
|
6
|
+
operator: 'in';
|
|
7
|
+
value: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare function getRuntimeTenantPredicate(context?: ExecutionContext): TenantPredicate | undefined;
|
|
10
|
+
export declare function getRuntimeTenantId(context?: ExecutionContext): string | undefined;
|
|
11
|
+
export declare function isCrossTenantRuntime(context?: ExecutionContext): boolean;
|
|
12
|
+
export declare function hasTenantRuntime(context?: ExecutionContext): boolean;
|
|
13
|
+
export declare function validateTenantRuntime(ds: AnyDatasetInstance, context?: ExecutionContext): string | undefined;
|
|
14
|
+
//# sourceMappingURL=tenant-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-runtime.d.ts","sourceRoot":"","sources":["../../src/utils/tenant-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACjC;IAAE,QAAQ,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAExC,wBAAgB,yBAAyB,CACvC,OAAO,CAAC,EAAE,gBAAgB,GACzB,eAAe,GAAG,SAAS,CAe7B;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAGjF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAGxE;AAED,wBAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAEpE;AAED,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,kBAAkB,EACtB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAQpB"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export function getRuntimeTenantPredicate(context) {
|
|
2
|
+
const tenant = context?.runtime?.tenant;
|
|
3
|
+
if (!tenant) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
if (typeof tenant === 'string') {
|
|
7
|
+
return { operator: 'eq', value: tenant };
|
|
8
|
+
}
|
|
9
|
+
if ('id' in tenant) {
|
|
10
|
+
return { operator: 'eq', value: tenant.id };
|
|
11
|
+
}
|
|
12
|
+
if ('in' in tenant && tenant.in.length > 0) {
|
|
13
|
+
return { operator: 'in', value: tenant.in };
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
export function getRuntimeTenantId(context) {
|
|
18
|
+
const predicate = getRuntimeTenantPredicate(context);
|
|
19
|
+
return predicate?.operator === 'eq' ? predicate.value : undefined;
|
|
20
|
+
}
|
|
21
|
+
export function isCrossTenantRuntime(context) {
|
|
22
|
+
const tenant = context?.runtime?.tenant;
|
|
23
|
+
return !!tenant && typeof tenant === 'object' && 'scope' in tenant && tenant.scope === 'all';
|
|
24
|
+
}
|
|
25
|
+
export function hasTenantRuntime(context) {
|
|
26
|
+
return !!getRuntimeTenantPredicate(context) || isCrossTenantRuntime(context);
|
|
27
|
+
}
|
|
28
|
+
export function validateTenantRuntime(ds, context) {
|
|
29
|
+
if (!ds.tenantKey) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (!hasTenantRuntime(context)) {
|
|
33
|
+
return `Dataset "${ds.name}" requires runtime tenant scoping.`;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/datasets",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Semantic layer for defining datasets, metrics, and semantic queries",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
12
|
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./internal": {
|
|
15
|
+
"types": "./dist/internal.d.ts",
|
|
16
|
+
"import": "./dist/internal.js",
|
|
17
|
+
"require": "./dist/internal.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"typesVersions": {
|
|
21
|
+
"*": {
|
|
22
|
+
"internal": [
|
|
23
|
+
"./dist/internal.d.ts"
|
|
24
|
+
]
|
|
13
25
|
}
|
|
14
26
|
},
|
|
15
27
|
"license": "Apache-2.0",
|
|
@@ -41,7 +53,7 @@
|
|
|
41
53
|
"dev": "tsc --watch",
|
|
42
54
|
"test": "npm run test:types && npm run test:unit",
|
|
43
55
|
"test:unit": "vitest run",
|
|
44
|
-
"test:integration": "
|
|
56
|
+
"test:integration": "node scripts/run-integration-tests.js",
|
|
45
57
|
"test:types": "tsc --project tsconfig.type-tests.json",
|
|
46
58
|
"test:watch": "vitest watch",
|
|
47
59
|
"test:coverage": "vitest run --coverage"
|