@hypequery/datasets 0.17.1 → 0.19.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 +9 -0
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/aggregations.d.ts +2 -0
- package/dist/aggregations.d.ts.map +1 -1
- package/dist/aggregations.js +4 -0
- package/dist/api.type-test.js +40 -1
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +17 -0
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +12 -2
- package/dist/cloud-publishing.d.ts +23 -0
- package/dist/cloud-publishing.d.ts.map +1 -0
- package/dist/cloud-publishing.js +57 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -0
- package/dist/contract.d.ts +5 -1
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +7 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +6 -1
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +3 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +15 -4
- package/dist/in-memory-backend.d.ts.map +1 -1
- package/dist/in-memory-backend.js +34 -7
- package/dist/index.d.ts +10 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/measure.d.ts +5 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +5 -0
- package/dist/portable-execution-errors.d.ts +4 -0
- package/dist/portable-execution-errors.d.ts.map +1 -1
- package/dist/portable-execution-errors.js +4 -1
- package/dist/portable-executor.d.ts.map +1 -1
- package/dist/portable-executor.js +6 -3
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +15 -7
- package/dist/protocol-rehydrate.d.ts +5 -1
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +20 -10
- package/dist/query-builder-protocol.d.ts +7 -3
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +5 -0
- package/dist/relationship-check.d.ts +47 -0
- package/dist/relationship-check.d.ts.map +1 -0
- package/dist/relationship-check.js +59 -0
- package/dist/relationships.d.ts +10 -10
- package/dist/relationships.js +10 -10
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +5 -1
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +5 -0
- package/dist/sql-portability.d.ts.map +1 -1
- package/dist/sql-portability.js +88 -19
- package/dist/types.d.ts +39 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +6 -0
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +4 -0
- package/dist/utils/approximate-measures.d.ts +10 -0
- package/dist/utils/approximate-measures.d.ts.map +1 -0
- package/dist/utils/approximate-measures.js +15 -0
- package/dist/utils/collect-cloud-datasets.d.ts +3 -0
- package/dist/utils/collect-cloud-datasets.d.ts.map +1 -0
- package/dist/utils/collect-cloud-datasets.js +12 -0
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +2 -2
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
- package/dist/utils/dataset-metric-ref.js +5 -0
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +6 -3
- package/dist/utils/dataset-time-grains.d.ts +15 -0
- package/dist/utils/dataset-time-grains.d.ts.map +1 -0
- package/dist/utils/dataset-time-grains.js +17 -0
- package/dist/utils/filtered-aggregation-sql.d.ts.map +1 -1
- package/dist/utils/filtered-aggregation-sql.js +1 -0
- package/dist/utils/portable-aggregations.d.ts +14 -0
- package/dist/utils/portable-aggregations.d.ts.map +1 -0
- package/dist/utils/portable-aggregations.js +15 -0
- package/dist/utils/portable-grains.d.ts +22 -0
- package/dist/utils/portable-grains.d.ts.map +1 -0
- package/dist/utils/portable-grains.js +35 -0
- package/dist/utils/portable-semantic-query.d.ts.map +1 -1
- package/dist/utils/portable-semantic-query.js +2 -1
- package/dist/utils/protocol-metric-expressions.d.ts.map +1 -1
- package/dist/utils/protocol-metric-expressions.js +2 -1
- package/dist/utils/relationship-builder-plan.d.ts +6 -3
- package/dist/utils/relationship-builder-plan.d.ts.map +1 -1
- package/dist/utils/relationship-builder-plan.js +13 -5
- package/dist/utils/relationship-key-check.d.ts +43 -0
- package/dist/utils/relationship-key-check.d.ts.map +1 -0
- package/dist/utils/relationship-key-check.js +72 -0
- package/dist/utils/segments.d.ts +31 -0
- package/dist/utils/segments.d.ts.map +1 -0
- package/dist/utils/segments.js +91 -0
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -1
- package/dist/utils/semantic-metadata-validation.js +22 -1
- package/dist/utils/unsupported-contract-reasons.d.ts +32 -0
- package/dist/utils/unsupported-contract-reasons.d.ts.map +1 -0
- package/dist/utils/unsupported-contract-reasons.js +30 -0
- package/package.json +3 -3
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for `checkRelationships`: which relationships a check covers,
|
|
3
|
+
* and how a key-count row becomes a finding.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Lists the to-one relationships to check. `hasMany` is excluded because it
|
|
7
|
+
* never joins, so its key has no uniqueness requirement. An explicit
|
|
8
|
+
* `only` list must name declared to-one relationships.
|
|
9
|
+
*/
|
|
10
|
+
export function listToOneRelationships(ds, only) {
|
|
11
|
+
const declared = Object.entries(ds.relationships);
|
|
12
|
+
if (only) {
|
|
13
|
+
for (const name of only) {
|
|
14
|
+
const relationship = Object.hasOwn(ds.relationships, name) ? ds.relationships[name] : undefined;
|
|
15
|
+
if (!relationship) {
|
|
16
|
+
throw new Error(`Unknown relationship "${name}" on dataset "${ds.name}".`);
|
|
17
|
+
}
|
|
18
|
+
if (relationship.kind === 'hasMany') {
|
|
19
|
+
throw new Error(`Relationship "${name}" on dataset "${ds.name}" is hasMany; only to-one relationships have a key to check.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return declared
|
|
24
|
+
.filter(([name, relationship]) => relationship.kind !== 'hasMany' && (!only || only.includes(name)))
|
|
25
|
+
.map(([name, relationship]) => ({
|
|
26
|
+
relationship: name,
|
|
27
|
+
kind: relationship.kind,
|
|
28
|
+
target: relationship.target(),
|
|
29
|
+
column: relationship.to,
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
/** Reads a count without losing precision when a driver returns UInt64 as a string. */
|
|
33
|
+
export function readCount(value) {
|
|
34
|
+
if (value == null) {
|
|
35
|
+
return 0n;
|
|
36
|
+
}
|
|
37
|
+
if (typeof value === 'bigint' && value >= 0n) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
41
|
+
return BigInt(value);
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === 'string' && /^\d+$/.test(value)) {
|
|
44
|
+
return BigInt(value);
|
|
45
|
+
}
|
|
46
|
+
throw new Error(`Expected a non-negative integer row count, received ${JSON.stringify(String(value))}.`);
|
|
47
|
+
}
|
|
48
|
+
function displayCount(count) {
|
|
49
|
+
return count <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(count) : count.toString();
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Returns the finding for a to-one relationship whose target key repeats, or
|
|
53
|
+
* undefined when every non-NULL key is unique.
|
|
54
|
+
*/
|
|
55
|
+
export function relationshipKeyIssue(entry, rows, distinctKeys) {
|
|
56
|
+
if (rows <= distinctKeys) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const source = entry.target.source;
|
|
60
|
+
return {
|
|
61
|
+
relationship: entry.relationship,
|
|
62
|
+
kind: entry.kind,
|
|
63
|
+
target: entry.target.name,
|
|
64
|
+
source,
|
|
65
|
+
column: entry.column,
|
|
66
|
+
rows: displayCount(rows),
|
|
67
|
+
distinctKeys: displayCount(distinctKeys),
|
|
68
|
+
message: `Relationship "${entry.relationship}" is declared ${entry.kind}, but "${source}.${entry.column}" ` +
|
|
69
|
+
`has ${rows} rows for ${distinctKeys} distinct keys. Joins pick an arbitrary matching row; ` +
|
|
70
|
+
'make the key unique or declare the relationship as hasMany.',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataset segments (RFC 0015): named, author-defined row filters that a query
|
|
3
|
+
* selects by name.
|
|
4
|
+
*
|
|
5
|
+
* A segment compares the dataset's own dimensions with literals. It may use a
|
|
6
|
+
* dimension that is not exposed as a filter: the filter allow-list governs
|
|
7
|
+
* what a caller may construct, not what an author may declare. It may not
|
|
8
|
+
* traverse a relationship or constrain the tenant column, which only trusted
|
|
9
|
+
* runtime context scopes.
|
|
10
|
+
*/
|
|
11
|
+
import type { AnyDatasetInstance, DimensionDefinition, MetricFilter, SegmentDefinition } from '../types.js';
|
|
12
|
+
interface SegmentOwner {
|
|
13
|
+
readonly tenantKey?: string;
|
|
14
|
+
readonly dimensions: Readonly<Record<string, DimensionDefinition>>;
|
|
15
|
+
}
|
|
16
|
+
/** Throws when a declared segment is malformed; returns a frozen copy otherwise. */
|
|
17
|
+
export declare function normalizeSegments(datasetName: string, owner: SegmentOwner, segments: Record<string, SegmentDefinition> | undefined): Record<string, SegmentDefinition>;
|
|
18
|
+
/**
|
|
19
|
+
* Publishing emits deployment contract 2, which cannot carry segments
|
|
20
|
+
* (RFC 0015), so a dataset that declares them is refused by name.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assertNoPublishedSegments(ds: Pick<AnyDatasetInstance, 'name' | 'segments'>): void;
|
|
23
|
+
/** Validation errors for a query's `segments` selection. */
|
|
24
|
+
export declare function segmentSelectionErrors(ds: AnyDatasetInstance, names: unknown): string[];
|
|
25
|
+
/**
|
|
26
|
+
* The conditions a selection applies, as filters over dimension names. Order
|
|
27
|
+
* follows the selection, then each segment's authored filters.
|
|
28
|
+
*/
|
|
29
|
+
export declare function segmentFilters(ds: AnyDatasetInstance, names: readonly string[] | undefined): MetricFilter[];
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=segments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"segments.d.ts","sourceRoot":"","sources":["../../src/utils/segments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAM5G,UAAU,YAAY;IACpB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC,CAAC;CACpE;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,YAAY,EACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,GAAG,SAAS,GACtD,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAkCnC;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE,MAAM,GAAG,UAAU,CAAC,GAAG,IAAI,CAQjG;AAED,4DAA4D;AAC5D,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAiBvF;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,YAAY,EAAE,CAE3G"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dataset segments (RFC 0015): named, author-defined row filters that a query
|
|
3
|
+
* selects by name.
|
|
4
|
+
*
|
|
5
|
+
* A segment compares the dataset's own dimensions with literals. It may use a
|
|
6
|
+
* dimension that is not exposed as a filter: the filter allow-list governs
|
|
7
|
+
* what a caller may construct, not what an author may declare. It may not
|
|
8
|
+
* traverse a relationship or constrain the tenant column, which only trusted
|
|
9
|
+
* runtime context scopes.
|
|
10
|
+
*/
|
|
11
|
+
import { SEMANTIC_FILTER_OPERATORS } from '../constants.js';
|
|
12
|
+
import { validateFilterValue } from '../validation.js';
|
|
13
|
+
const SEGMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
14
|
+
const OPERATORS = new Set(SEMANTIC_FILTER_OPERATORS);
|
|
15
|
+
/** Throws when a declared segment is malformed; returns a frozen copy otherwise. */
|
|
16
|
+
export function normalizeSegments(datasetName, owner, segments) {
|
|
17
|
+
const fail = (name, message) => {
|
|
18
|
+
throw new Error(`Invalid segment "${name}" on dataset "${datasetName}": ${message}`);
|
|
19
|
+
};
|
|
20
|
+
const result = [];
|
|
21
|
+
for (const [name, segment] of Object.entries(segments ?? {})) {
|
|
22
|
+
if (!SEGMENT_NAME.test(name))
|
|
23
|
+
fail(name, 'names must be identifiers (letters, digits, underscores).');
|
|
24
|
+
if (!Array.isArray(segment?.filters) || segment.filters.length === 0) {
|
|
25
|
+
fail(name, 'filters must be a non-empty array.');
|
|
26
|
+
}
|
|
27
|
+
for (const filter of segment.filters) {
|
|
28
|
+
const dimension = Object.hasOwn(owner.dimensions, filter.field) ? owner.dimensions[filter.field] : undefined;
|
|
29
|
+
if (!dimension) {
|
|
30
|
+
fail(name, `"${filter.field}" is not a dimension of this dataset; segments cannot use relationships.`);
|
|
31
|
+
}
|
|
32
|
+
const column = dimension.column ?? filter.field;
|
|
33
|
+
if (owner.tenantKey !== undefined && (column === owner.tenantKey || filter.field === owner.tenantKey)) {
|
|
34
|
+
fail(name, `"${filter.field}" is the tenant column, which only runtime tenant scoping may constrain.`);
|
|
35
|
+
}
|
|
36
|
+
if (!OPERATORS.has(filter.operator))
|
|
37
|
+
fail(name, `unsupported operator "${String(filter.operator)}".`);
|
|
38
|
+
const valueError = validateFilterValue(filter, dimension.fieldType);
|
|
39
|
+
if (valueError)
|
|
40
|
+
fail(name, valueError);
|
|
41
|
+
}
|
|
42
|
+
result.push([name, Object.freeze({
|
|
43
|
+
filters: Object.freeze(segment.filters.map(filter => Object.freeze({
|
|
44
|
+
...filter,
|
|
45
|
+
value: Array.isArray(filter.value) ? Object.freeze([...filter.value]) : filter.value,
|
|
46
|
+
}))),
|
|
47
|
+
...(segment.label !== undefined ? { label: segment.label } : {}),
|
|
48
|
+
...(segment.description !== undefined ? { description: segment.description } : {}),
|
|
49
|
+
})]);
|
|
50
|
+
}
|
|
51
|
+
// Object.fromEntries preserves names such as "__proto__" as own properties.
|
|
52
|
+
return Object.freeze(Object.fromEntries(result));
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Publishing emits deployment contract 2, which cannot carry segments
|
|
56
|
+
* (RFC 0015), so a dataset that declares them is refused by name.
|
|
57
|
+
*/
|
|
58
|
+
export function assertNoPublishedSegments(ds) {
|
|
59
|
+
const names = Object.keys(ds.segments ?? {});
|
|
60
|
+
if (names.length > 0) {
|
|
61
|
+
throw new Error(`Dataset "${ds.name}" declares segments (${names.join(', ')}), which cannot be published yet: ` +
|
|
62
|
+
'segments need deployment contract 3 (RFC 0015). Use them for local queries until publishing emits contract 3.');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Validation errors for a query's `segments` selection. */
|
|
66
|
+
export function segmentSelectionErrors(ds, names) {
|
|
67
|
+
if (names === undefined)
|
|
68
|
+
return [];
|
|
69
|
+
if (!Array.isArray(names))
|
|
70
|
+
return ['segments must be an array of segment names.'];
|
|
71
|
+
const errors = [];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
const available = Object.keys(ds.segments ?? {});
|
|
74
|
+
for (const name of names) {
|
|
75
|
+
if (typeof name !== 'string' || !Object.hasOwn(ds.segments ?? {}, name)) {
|
|
76
|
+
errors.push(`Unknown segment "${String(name)}" on dataset "${ds.name}". Available: ${available.join(', ') || '(none)'}`);
|
|
77
|
+
}
|
|
78
|
+
else if (seen.has(name)) {
|
|
79
|
+
errors.push(`Segment "${name}" is selected more than once.`);
|
|
80
|
+
}
|
|
81
|
+
seen.add(String(name));
|
|
82
|
+
}
|
|
83
|
+
return errors;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The conditions a selection applies, as filters over dimension names. Order
|
|
87
|
+
* follows the selection, then each segment's authored filters.
|
|
88
|
+
*/
|
|
89
|
+
export function segmentFilters(ds, names) {
|
|
90
|
+
return (names ?? []).flatMap(name => ds.segments?.[name]?.filters ?? []);
|
|
91
|
+
}
|
|
@@ -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,wBAAwB,EACxB,mBAAmB,EACnB,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,aAAa,CAAC;
|
|
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;AAsBrB,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,CA4EN"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { SUPPORTED_TIME_GRAINS } from '../constants.js';
|
|
1
2
|
const MAX_METADATA_ITEMS = 100;
|
|
2
3
|
const MAX_METADATA_TEXT_BYTES = 4_096;
|
|
3
4
|
const SENSITIVITIES = new Set(['public', 'internal', 'confidential', 'restricted']);
|
|
4
|
-
const TIME_GRAINS = new Set(
|
|
5
|
+
const TIME_GRAINS = new Set(SUPPORTED_TIME_GRAINS);
|
|
5
6
|
const textEncoder = new TextEncoder();
|
|
6
7
|
function fail(datasetName, location, message) {
|
|
7
8
|
throw new Error(`Invalid dataset "${datasetName}": ${location} ${message}`);
|
|
@@ -90,6 +91,26 @@ export function validateDatasetAgentMetadata(datasetName, config) {
|
|
|
90
91
|
if (dimensions.length === 0 && config.defaults.timeGrain === undefined) {
|
|
91
92
|
fail(datasetName, 'defaults', 'must define dimensions or timeGrain.');
|
|
92
93
|
}
|
|
94
|
+
if (config.defaults.timeGrain !== undefined && config.timeGrains !== undefined
|
|
95
|
+
&& !config.timeGrains.includes(config.defaults.timeGrain)) {
|
|
96
|
+
fail(datasetName, 'defaults.timeGrain', 'is not one of the dataset timeGrains.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (config.timeGrains !== undefined) {
|
|
100
|
+
if (!Array.isArray(config.timeGrains) || config.timeGrains.length === 0) {
|
|
101
|
+
fail(datasetName, 'timeGrains', 'must be a non-empty array.');
|
|
102
|
+
}
|
|
103
|
+
if (config.timeKey === undefined) {
|
|
104
|
+
fail(datasetName, 'timeGrains', 'requires the dataset to define timeKey.');
|
|
105
|
+
}
|
|
106
|
+
if (new Set(config.timeGrains).size !== config.timeGrains.length) {
|
|
107
|
+
fail(datasetName, 'timeGrains', 'must not contain duplicates.');
|
|
108
|
+
}
|
|
109
|
+
for (const grain of config.timeGrains) {
|
|
110
|
+
if (!TIME_GRAINS.has(grain)) {
|
|
111
|
+
fail(datasetName, 'timeGrains', `contains unsupported time grain "${String(grain)}".`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
93
114
|
}
|
|
94
115
|
for (const [name, dimension] of Object.entries(config.dimensions)) {
|
|
95
116
|
validateSemanticMetadata(datasetName, `dimensions.${name}`, dimension);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why portable execution excluded a contract surface.
|
|
3
|
+
*
|
|
4
|
+
* Decision 0005 excludes any surface a rebuilt catalog cannot reproduce
|
|
5
|
+
* byte-for-byte rather than approximating it. Every exclusion names one of
|
|
6
|
+
* these reasons, so an operator can tell which surface a deployment tripped on
|
|
7
|
+
* without parsing the message, and the corpus can assert that nothing is
|
|
8
|
+
* refused for an unnamed reason. The wire failure stays
|
|
9
|
+
* `HQ_SEMANTIC_UNSUPPORTED_CAPABILITY`; the reason travels beside it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const UNSUPPORTED_CONTRACT_REASONS: {
|
|
12
|
+
/** A measure's fixed filter is not a field/operator/literal comparison. */
|
|
13
|
+
readonly measureFilterNotComparison: "HQ_PORTABLE_MEASURE_FILTER_NOT_COMPARISON";
|
|
14
|
+
/** A metric input or base metric is not an aggregate expression. */
|
|
15
|
+
readonly expressionNotAggregate: "HQ_PORTABLE_EXPRESSION_NOT_AGGREGATE";
|
|
16
|
+
/** No declared measure has the aggregation a metric expression names. */
|
|
17
|
+
readonly noMatchingMeasure: "HQ_PORTABLE_NO_MATCHING_MEASURE";
|
|
18
|
+
/** Two measures share an aggregation but emit different SQL. */
|
|
19
|
+
readonly ambiguousMeasureSql: "HQ_PORTABLE_AMBIGUOUS_MEASURE_SQL";
|
|
20
|
+
/** A derived metric arrived without the authored formula and aliases. */
|
|
21
|
+
readonly derivedMetricWithoutFormula: "HQ_PORTABLE_DERIVED_METRIC_WITHOUT_FORMULA";
|
|
22
|
+
/** A derived formula uses an operator, function, or shape with no builder. */
|
|
23
|
+
readonly unsupportedFormula: "HQ_PORTABLE_UNSUPPORTED_FORMULA";
|
|
24
|
+
/** A relationship names a dataset outside the supplied contract. */
|
|
25
|
+
readonly relationshipTargetMissing: "HQ_PORTABLE_RELATIONSHIP_TARGET_MISSING";
|
|
26
|
+
/** An invocation filter is not a field/operator/literal comparison. */
|
|
27
|
+
readonly queryFilterNotComparison: "HQ_PORTABLE_QUERY_FILTER_NOT_COMPARISON";
|
|
28
|
+
/** The invoked dataset is not part of the activated contract. */
|
|
29
|
+
readonly datasetNotActivated: "HQ_PORTABLE_DATASET_NOT_ACTIVATED";
|
|
30
|
+
};
|
|
31
|
+
export type UnsupportedContractReason = typeof UNSUPPORTED_CONTRACT_REASONS[keyof typeof UNSUPPORTED_CONTRACT_REASONS];
|
|
32
|
+
//# sourceMappingURL=unsupported-contract-reasons.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unsupported-contract-reasons.d.ts","sourceRoot":"","sources":["../../src/utils/unsupported-contract-reasons.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B;IACvC,2EAA2E;;IAE3E,oEAAoE;;IAEpE,yEAAyE;;IAEzE,gEAAgE;;IAEhE,yEAAyE;;IAEzE,8EAA8E;;IAE9E,oEAAoE;;IAEpE,uEAAuE;;IAEvE,iEAAiE;;CAEzD,CAAC;AAEX,MAAM,MAAM,yBAAyB,GACnC,OAAO,4BAA4B,CAAC,MAAM,OAAO,4BAA4B,CAAC,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why portable execution excluded a contract surface.
|
|
3
|
+
*
|
|
4
|
+
* Decision 0005 excludes any surface a rebuilt catalog cannot reproduce
|
|
5
|
+
* byte-for-byte rather than approximating it. Every exclusion names one of
|
|
6
|
+
* these reasons, so an operator can tell which surface a deployment tripped on
|
|
7
|
+
* without parsing the message, and the corpus can assert that nothing is
|
|
8
|
+
* refused for an unnamed reason. The wire failure stays
|
|
9
|
+
* `HQ_SEMANTIC_UNSUPPORTED_CAPABILITY`; the reason travels beside it.
|
|
10
|
+
*/
|
|
11
|
+
export const UNSUPPORTED_CONTRACT_REASONS = {
|
|
12
|
+
/** A measure's fixed filter is not a field/operator/literal comparison. */
|
|
13
|
+
measureFilterNotComparison: 'HQ_PORTABLE_MEASURE_FILTER_NOT_COMPARISON',
|
|
14
|
+
/** A metric input or base metric is not an aggregate expression. */
|
|
15
|
+
expressionNotAggregate: 'HQ_PORTABLE_EXPRESSION_NOT_AGGREGATE',
|
|
16
|
+
/** No declared measure has the aggregation a metric expression names. */
|
|
17
|
+
noMatchingMeasure: 'HQ_PORTABLE_NO_MATCHING_MEASURE',
|
|
18
|
+
/** Two measures share an aggregation but emit different SQL. */
|
|
19
|
+
ambiguousMeasureSql: 'HQ_PORTABLE_AMBIGUOUS_MEASURE_SQL',
|
|
20
|
+
/** A derived metric arrived without the authored formula and aliases. */
|
|
21
|
+
derivedMetricWithoutFormula: 'HQ_PORTABLE_DERIVED_METRIC_WITHOUT_FORMULA',
|
|
22
|
+
/** A derived formula uses an operator, function, or shape with no builder. */
|
|
23
|
+
unsupportedFormula: 'HQ_PORTABLE_UNSUPPORTED_FORMULA',
|
|
24
|
+
/** A relationship names a dataset outside the supplied contract. */
|
|
25
|
+
relationshipTargetMissing: 'HQ_PORTABLE_RELATIONSHIP_TARGET_MISSING',
|
|
26
|
+
/** An invocation filter is not a field/operator/literal comparison. */
|
|
27
|
+
queryFilterNotComparison: 'HQ_PORTABLE_QUERY_FILTER_NOT_COMPARISON',
|
|
28
|
+
/** The invoked dataset is not part of the activated contract. */
|
|
29
|
+
datasetNotActivated: 'HQ_PORTABLE_DATASET_NOT_ACTIVATED',
|
|
30
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/datasets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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.16.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.
|
|
63
|
+
"@hypequery/protocol-conformance": "^0.13.0"
|
|
64
64
|
},
|
|
65
65
|
"repository": {
|
|
66
66
|
"type": "git",
|