@hypequery/datasets 0.28.0 → 0.29.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.
@@ -1 +1 @@
1
- {"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"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"}
@@ -8,6 +8,7 @@ import { zodToProtocolSchema } from './protocol-schema-adapter.js';
8
8
  import { compareStrings, stableStringify, uniqueSorted } from './utils/canonical-json.js';
9
9
  import { zodToDraft7JsonSchema } from './utils/zod-json-schema.js';
10
10
  import { datasetTimeKeys } from './utils/dataset-time-keys.js';
11
+ import { effectiveFilters } from './utils/effective-filters.js';
11
12
  export const DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS = Object.freeze({
12
13
  maxResultSize: 10_000,
13
14
  maxOffset: 10_000,
@@ -24,6 +25,11 @@ function resolveCatalog(source) {
24
25
  ? source
25
26
  : getDatasetCatalog(source);
26
27
  }
28
+ function localFilterPolicy(source) {
29
+ return isDatasetCatalog(source)
30
+ ? effectiveFilters(source)
31
+ : effectiveFilters(source);
32
+ }
27
33
  function relationshipFilterPolicy(source) {
28
34
  const fields = new Map();
29
35
  // A serialized catalog has no target filter allowlist. Do not infer one
@@ -34,8 +40,9 @@ function relationshipFilterPolicy(source) {
34
40
  if (relationship.kind === 'hasMany')
35
41
  continue;
36
42
  const target = relationship.target();
43
+ const filters = effectiveFilters(target);
37
44
  for (const [field, dimension] of Object.entries(target.dimensions)) {
38
- const definition = target.filters[field];
45
+ const definition = filters[field];
39
46
  if (!dimension.sql && definition?.field === field) {
40
47
  fields.set(`${name}.${field}`, definition.operators
41
48
  ? [...definition.operators]
@@ -59,13 +66,13 @@ function boundedArray(item, maximum) {
59
66
  const array = z.array(item);
60
67
  return (maximum === undefined ? array : array.max(maximum)).optional();
61
68
  }
62
- function filterSchema(catalog, fields, relationshipOperators) {
69
+ function filterSchema(localFilters, fields, relationshipOperators) {
63
70
  const filterValue = z.unknown().refine(value => value !== undefined, 'Required');
64
71
  // A null check takes no value (RFC 0023), so it gets its own value-free shape.
65
72
  const variants = uniqueSorted(fields).flatMap((field) => {
66
73
  const operators = relationshipOperators.has(field)
67
74
  ? relationshipOperators.get(field) ?? []
68
- : catalog.filters[field]?.operators ?? [...SEMANTIC_FILTER_OPERATORS];
75
+ : localFilters[field]?.operators ?? [...SEMANTIC_FILTER_OPERATORS];
69
76
  const valueOperators = operators.filter(operator => !isNullCheckFilterOperator(operator));
70
77
  const nullChecks = operators.filter(isNullCheckFilterOperator);
71
78
  return [
@@ -105,7 +112,7 @@ function havingSchema(measures, maximum) {
105
112
  return conditions.describe('Conditions on aggregated measure values, applied after grouping (SQL HAVING). '
106
113
  + 'Each condition must name a measure selected in this query.');
107
114
  }
108
- function queryShape(catalog, metricName, options, relationshipOperators, metric, localRelationshipFields = []) {
115
+ function queryShape(catalog, metricName, options, relationshipOperators, localFilters = effectiveFilters(catalog), metric, localRelationshipFields = []) {
109
116
  const limits = { ...DEFAULT_SEMANTIC_QUERY_SCHEMA_LIMITS, ...options };
110
117
  const relationshipFields = getQueryableRelationshipFields(catalog);
111
118
  const filterableRelationshipFields = [...relationshipOperators.keys()];
@@ -129,16 +136,17 @@ function queryShape(catalog, metricName, options, relationshipOperators, metric,
129
136
  ...Object.keys(catalog.dimensions).filter(isGroupable),
130
137
  ...relationshipFields.filter(isGroupable),
131
138
  ]);
132
- const declaredFilters = Object.keys(catalog.filters);
133
- const declaredFilterSet = new Set(declaredFilters);
139
+ const localFilterNames = Object.keys(localFilters);
140
+ const localFilterSet = new Set(localFilterNames);
141
+ // A metric's own filter list stays authoritative: a hosted catalog may narrow it.
134
142
  const filterFields = metric
135
143
  ? uniqueSorted([
136
144
  ...metric.filters.filter(field => field.includes('.')
137
145
  ? filterableRelationshipSet.has(field)
138
- : declaredFilterSet.has(field)),
146
+ : localFilterSet.has(field)),
139
147
  ...localRelationshipFields.filter(field => filterableRelationshipSet.has(field)),
140
148
  ])
141
- : uniqueSorted([...declaredFilters, ...filterableRelationshipFields]);
149
+ : uniqueSorted([...localFilterNames, ...filterableRelationshipFields]);
142
150
  const grains = metric
143
151
  ? metric.grain ? [metric.grain] : metric.grains
144
152
  : catalog.supportedGrains;
@@ -170,7 +178,7 @@ function queryShape(catalog, metricName, options, relationshipOperators, metric,
170
178
  ...(metricName ? {} : {
171
179
  measures: boundedArray(fieldEnum(selectableMeasures), lowerLimit(catalog.limits?.maxMeasures, limits.maxMeasures)),
172
180
  }),
173
- filters: boundedArray(filterSchema(catalog, filterFields, relationshipOperators), lowerLimit(catalog.limits?.maxFilters, limits.maxFilters)),
181
+ filters: boundedArray(filterSchema(localFilters, filterFields, relationshipOperators), lowerLimit(catalog.limits?.maxFilters, limits.maxFilters)),
174
182
  // Opt-in, so existing schemas and manifest hashes are unchanged and a
175
183
  // surface only advertises conditions its executor can run.
176
184
  ...(!metricName && options.having ? {
@@ -202,7 +210,7 @@ function queryShape(catalog, metricName, options, relationshipOperators, metric,
202
210
  };
203
211
  }
204
212
  export function buildDatasetInputSchema(dataset, options = {}) {
205
- const schema = z.object(queryShape(resolveCatalog(dataset), undefined, options, relationshipFilterPolicy(dataset))).strict();
213
+ const schema = z.object(queryShape(resolveCatalog(dataset), undefined, options, relationshipFilterPolicy(dataset), localFilterPolicy(dataset))).strict();
206
214
  if (options.requireSelection === false)
207
215
  return schema;
208
216
  return requireSelection(schema);
@@ -213,7 +221,7 @@ export function buildMetricInputSchema(dataset, metricName, options = {}, metric
213
221
  const localRelationshipFields = isDatasetCatalog(dataset)
214
222
  ? []
215
223
  : getQueryableRelationshipFields(catalog);
216
- return z.object(queryShape(catalog, metricName, options, relationshipFilterPolicy(dataset), metric, localRelationshipFields)).strict();
224
+ return z.object(queryShape(catalog, metricName, options, relationshipFilterPolicy(dataset), localFilterPolicy(dataset), metric, localRelationshipFields)).strict();
217
225
  }
218
226
  function unionSchemas(schemas, emptyShape) {
219
227
  if (schemas.length === 0)
@@ -1,14 +1,39 @@
1
1
  import type { DatasetInstance, DimensionDefinition, MetricFilter, MetricFilterOperator, MetricValueFilterOperator, RelationshipDefinition, SemanticFilterDefinition, SemanticFiltersDefinition } from '../types.js';
2
+ type GeneratedFilter<K extends string> = Omit<SemanticFilterDefinition, 'operators'> & {
3
+ field: K;
4
+ /** Generated filters list no operators, so they admit no null checks (RFC 0023). */
5
+ operators?: undefined;
6
+ };
2
7
  /** The generated allowlist mirrors normalizeFilters; explicit maps replace it. */
3
8
  export type NormalizedDatasetFilters<TDimensions extends Record<string, DimensionDefinition>, TFilters extends SemanticFiltersDefinition | undefined> = TFilters extends SemanticFiltersDefinition ? TFilters : {
4
9
  [K in keyof TDimensions as TDimensions[K] extends {
5
10
  filterable: false;
6
- } ? never : K]: Omit<SemanticFilterDefinition, 'operators'> & {
7
- field: K & string;
8
- /** Generated filters list no operators, so they admit no null checks (RFC 0023). */
9
- operators?: undefined;
10
- };
11
+ } ? never : K]: GeneratedFilter<K & string>;
12
+ };
13
+ /** The declared filters whose field is `K`. */
14
+ type DeclaredFiltersOn<TFilters extends SemanticFiltersDefinition, K> = {
15
+ [F in keyof TFilters]: TFilters[F] extends {
16
+ field: K;
17
+ } ? TFilters[F] : never;
18
+ }[keyof TFilters];
19
+ type DimensionFilter<K extends string, TDeclared> = [TDeclared] extends [never] ? GeneratedFilter<K> : [Exclude<TDeclared, {
20
+ operators: readonly unknown[];
21
+ }>] extends [never] ? Omit<SemanticFilterDefinition, 'field' | 'operators'> & {
22
+ field: K;
23
+ operators: TDeclared extends {
24
+ operators: infer TOperators;
25
+ } ? TOperators : never;
26
+ } : GeneratedFilter<K>;
27
+ /**
28
+ * The filters a caller may apply, mirroring effectiveFilters: the declared
29
+ * filters plus one per filterable dimension no declared filter is named after.
30
+ */
31
+ export type EffectiveDatasetFilters<TDimensions extends Record<string, DimensionDefinition>, TFilters extends SemanticFiltersDefinition> = TFilters & {
32
+ [K in keyof TDimensions as K extends keyof TFilters ? never : TDimensions[K] extends {
33
+ filterable: false;
34
+ } ? never : K]: DimensionFilter<K & string, DeclaredFiltersOn<TFilters, K>>;
11
35
  };
36
+ type FiltersOf<TDataset extends DatasetInstance<any, any, any, any>> = EffectiveDatasetFilters<TDataset['dimensions'], TDataset['filters']>;
12
37
  type FilterOperators<TDefinition> = TDefinition extends {
13
38
  operators: readonly (infer TOperator extends MetricFilterOperator)[];
14
39
  } ? TOperator : TDefinition extends {
@@ -19,14 +44,14 @@ type LocalFilterFor<TFilters extends SemanticFiltersDefinition> = {
19
44
  }[keyof TFilters & string];
20
45
  type RelationshipFilterFor<TRelationships extends Record<string, RelationshipDefinition>> = {
21
46
  [R in keyof TRelationships & string]: TRelationships[R] extends RelationshipDefinition<infer TTarget, infer TKind> ? TKind extends 'hasMany' ? never : TTarget extends DatasetInstance<any, any, any, any> ? {
22
- [K in keyof TTarget['filters'] & keyof TTarget['dimensions'] & string]: TTarget['dimensions'][K] extends {
47
+ [K in keyof FiltersOf<TTarget> & keyof TTarget['dimensions'] & string]: TTarget['dimensions'][K] extends {
23
48
  sql: string;
24
- } ? never : K extends TTarget['filters'][K]['field'] ? MetricFilter<`${R}.${K}`, unknown, FilterOperators<TTarget['filters'][K]>> : never;
25
- }[keyof TTarget['filters'] & keyof TTarget['dimensions'] & string] : never : never;
49
+ } ? never : K extends FiltersOf<TTarget>[K]['field'] ? MetricFilter<`${R}.${K}`, unknown, FilterOperators<FiltersOf<TTarget>[K]>> : never;
50
+ }[keyof FiltersOf<TTarget> & keyof TTarget['dimensions'] & string] : never : never;
26
51
  }[keyof TRelationships & string];
27
52
  /** Filter names and operator policies exposed by a dataset and its to-one targets. */
28
- export type DatasetFilterFor<TDataset extends DatasetInstance<any, any, any, any>> = LocalFilterFor<TDataset['filters']> | RelationshipFilterFor<TDataset['relationships']>;
29
- /** Names in the normalized filter allowlist, including one-hop relationship filters. */
53
+ export type DatasetFilterFor<TDataset extends DatasetInstance<any, any, any, any>> = LocalFilterFor<FiltersOf<TDataset>> | RelationshipFilterFor<TDataset['relationships']>;
54
+ /** Names a caller may filter on, including one-hop relationship filters. */
30
55
  export type DatasetFilterNames<TDataset extends DatasetInstance<any, any, any, any>> = DatasetFilterFor<TDataset>['field'];
31
56
  export {};
32
57
  //# sourceMappingURL=dataset-filter-types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dataset-filter-types.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-filter-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dataset-filter-types.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-filter-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"dataset-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-validation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAsB9E,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAqLlB"}
1
+ {"version":3,"file":"dataset-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-query-validation.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAuB9E,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAsLlB"}
@@ -15,6 +15,7 @@ import { enforcedConstraintErrors } from './enforced-constraints.js';
15
15
  import { analyzeTimeMeasureAxis } from './time-measure-axis.js';
16
16
  import { datasetHavingErrors } from './dataset-having.js';
17
17
  import { datasetOnTimeKey, selectedTimeKeyError } from './dataset-time-keys.js';
18
+ import { effectiveFilters } from './effective-filters.js';
18
19
  export function validateDatasetQueryInput(dataset, query, context) {
19
20
  // Validate against the selected time axis, so grain and window checks see it.
20
21
  const timeKeyError = selectedTimeKeyError(dataset, query.timeKey);
@@ -33,7 +34,8 @@ export function validateDatasetQueryInput(dataset, query, context) {
33
34
  errors.push(`Output "${name}" cannot be selected as both a dimension and a measure. Select one or give the definitions distinct names.`);
34
35
  }
35
36
  }
36
- const filterNames = Object.keys(ds.filters);
37
+ const filters = effectiveFilters(ds);
38
+ const filterNames = Object.keys(filters);
37
39
  const orderableFields = new Set([
38
40
  ...selectedDimensions,
39
41
  ...selectedMeasures,
@@ -100,12 +102,12 @@ export function validateDatasetQueryInput(dataset, query, context) {
100
102
  errors.push(`Unknown filter fields: ${filter.field}. Available: ${filterNames.join(', ')}`);
101
103
  continue;
102
104
  }
103
- const filterDefinition = ds.filters[filter.field];
105
+ const filterDefinition = filters[filter.field];
104
106
  if (!filterAllowsOperator(filterDefinition, filter.operator)) {
105
107
  errors.push(`Filter "${filter.field}" does not allow operator "${filter.operator}". Allowed: ${(filterDefinition?.operators ?? SEMANTIC_FILTER_OPERATORS).join(', ')}`);
106
108
  continue;
107
109
  }
108
- const resolvedField = ds.filters[filter.field]?.field ?? filter.field;
110
+ const resolvedField = filterDefinition.field;
109
111
  const resolvedDimension = ds.dimensions[resolvedField];
110
112
  const resolvedColumn = resolvedDimension?.sql
111
113
  ? undefined
@@ -0,0 +1,27 @@
1
+ import type { SemanticFilterDefinition } from '../types.js';
2
+ /** The parts of a filter definition that decide what a caller may filter on. */
3
+ export interface FilterPolicy {
4
+ readonly field: string;
5
+ readonly operators?: readonly string[];
6
+ }
7
+ interface FilterPolicySource<TFilter extends FilterPolicy> {
8
+ readonly filters: Readonly<Record<string, TFilter>>;
9
+ readonly dimensions: Readonly<Record<string, {
10
+ readonly filterable?: boolean;
11
+ }>>;
12
+ }
13
+ /**
14
+ * The filters a caller may apply: the declared filters, plus one per
15
+ * filterable dimension that no declared filter is named after.
16
+ *
17
+ * Declared filters add to the dimensions marked filterable rather than
18
+ * replacing them. A declared filter keeps its own name, field and operators,
19
+ * and can expose a dimension marked `filterable: false`. A dimension's own
20
+ * filter takes the operators of the declared filters on its field, so a
21
+ * declaration that narrows a field's operators cannot be bypassed by naming
22
+ * the dimension directly. Without one it gets the default list, which has no
23
+ * null checks (RFC 0023).
24
+ */
25
+ export declare function effectiveFilters<TFilter extends FilterPolicy>(source: FilterPolicySource<TFilter>): Record<string, TFilter | SemanticFilterDefinition>;
26
+ export {};
27
+ //# sourceMappingURL=effective-filters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"effective-filters.d.ts","sourceRoot":"","sources":["../../src/utils/effective-filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAE5D,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACxC;AAED,UAAU,kBAAkB,CAAC,OAAO,SAAS,YAAY;IACvD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACpD,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAC;CAClF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,SAAS,YAAY,EAC3D,MAAM,EAAE,kBAAkB,CAAC,OAAO,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,wBAAwB,CAAC,CAYpD"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The filters a caller may apply: the declared filters, plus one per
3
+ * filterable dimension that no declared filter is named after.
4
+ *
5
+ * Declared filters add to the dimensions marked filterable rather than
6
+ * replacing them. A declared filter keeps its own name, field and operators,
7
+ * and can expose a dimension marked `filterable: false`. A dimension's own
8
+ * filter takes the operators of the declared filters on its field, so a
9
+ * declaration that narrows a field's operators cannot be bypassed by naming
10
+ * the dimension directly. Without one it gets the default list, which has no
11
+ * null checks (RFC 0023).
12
+ */
13
+ export function effectiveFilters(source) {
14
+ const filters = { ...source.filters };
15
+ for (const [name, dimension] of Object.entries(source.dimensions)) {
16
+ if (dimension.filterable === false || Object.hasOwn(filters, name))
17
+ continue;
18
+ const operators = declaredOperators(Object.values(source.filters), name);
19
+ filters[name] = {
20
+ __type: 'filter_definition',
21
+ field: name,
22
+ ...(operators ? { operators: operators } : {}),
23
+ };
24
+ }
25
+ return filters;
26
+ }
27
+ /**
28
+ * The operators the declared filters on `field` allow between them, or
29
+ * undefined when none is declared or one allows the default list.
30
+ */
31
+ function declaredOperators(filters, field) {
32
+ const declared = filters.filter(filter => filter.field === field);
33
+ if (declared.length === 0 || declared.some(filter => !filter.operators))
34
+ return undefined;
35
+ return [...new Set(declared.flatMap(filter => filter.operators ?? []))];
36
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"enforced-constraints.d.ts","sourceRoot":"","sources":["../../src/utils/enforced-constraints.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAgB,MAAM,aAAa,CAAC;AAMtF,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAiB3C;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,aAAa,EAAE,MAAM,GACpB,MAAM,EAAE,CAaV;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAGlG"}
1
+ {"version":3,"file":"enforced-constraints.d.ts","sourceRoot":"","sources":["../../src/utils/enforced-constraints.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAgB,MAAM,aAAa,CAAC;AAOtF,oFAAoF;AACpF,eAAO,MAAM,wBAAwB,KAAK,CAAC;AAkB3C;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,EAAE,EAAE,kBAAkB,EACtB,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,aAAa,EAAE,MAAM,GACpB,MAAM,EAAE,CAaV;AAED,yEAAyE;AACzE,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,IAAI,CAGlG"}
@@ -1,6 +1,7 @@
1
1
  import { isSemanticFilterOperator } from '../constants.js';
2
2
  import { validateFilterValue } from '../validation.js';
3
3
  import { filterAllowsOperator } from './filter-operator-policy.js';
4
+ import { effectiveFilters } from './effective-filters.js';
4
5
  import { isQualifiedField, resolveQualifiedField } from './relationship-fields.js';
5
6
  import { validateQualifiedFilter } from './relationship-validation.js';
6
7
  /** Constraints are policy, not queries; a handful per call is plenty (RFC 0019). */
@@ -16,12 +17,13 @@ function constraintError(ds, constraint, context) {
16
17
  return true;
17
18
  return validateQualifiedFilter(ds, filter, context) !== null;
18
19
  }
19
- if (!Object.hasOwn(ds.filters, filter.field))
20
+ const filters = effectiveFilters(ds);
21
+ if (!Object.hasOwn(filters, filter.field))
20
22
  return true;
21
- const definition = ds.filters[filter.field];
23
+ const definition = filters[filter.field];
22
24
  if (!filterAllowsOperator(definition, filter.operator))
23
25
  return true;
24
- const fieldType = ds.dimensions[definition.field ?? filter.field]?.fieldType;
26
+ const fieldType = ds.dimensions[definition.field]?.fieldType;
25
27
  return !!fieldType && validateFilterValue(filter, fieldType) !== null;
26
28
  }
27
29
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI9E,OAAO,EAAsD,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAU3G,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAuIlB"}
1
+ {"version":3,"file":"metric-query-validation.d.ts","sourceRoot":"","sources":["../../src/utils/metric-query-validation.ts"],"names":[],"mappings":"AAEA,gFAAgF;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAI9E,OAAO,EAAsD,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAW3G,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,gBAAgB,CAwIlB"}
@@ -13,6 +13,7 @@ import { datasetOnTimeKey, selectedTimeKeyError } from './dataset-time-keys.js';
13
13
  import { enforcedConstraintErrors } from './enforced-constraints.js';
14
14
  import { filterAllowsOperator } from './filter-operator-policy.js';
15
15
  import { SEMANTIC_FILTER_OPERATORS } from '../constants.js';
16
+ import { effectiveFilters } from './effective-filters.js';
16
17
  export function validateMetricQueryInput(metric, query, context) {
17
18
  const errors = [...semanticQuerySyntaxErrors(query), ...protocolMetricCapabilityErrors(metric, query), ...queryTimezoneErrors(query.timezone)];
18
19
  const ref = getMetricRef(metric);
@@ -21,7 +22,8 @@ export function validateMetricQueryInput(metric, query, context) {
21
22
  errors.push(timeKeyError);
22
23
  const ds = timeKeyError ? ref.dataset : datasetOnTimeKey(ref.dataset, query.timeKey);
23
24
  const dimensionNames = Object.keys(ds.dimensions);
24
- const filterNames = Object.keys(ds.filters);
25
+ const filters = effectiveFilters(ds);
26
+ const filterNames = Object.keys(filters);
25
27
  const grain = getMetricGrain(metric, query);
26
28
  const orderableFields = new Set([
27
29
  ...(query.dimensions ?? []),
@@ -72,12 +74,12 @@ export function validateMetricQueryInput(metric, query, context) {
72
74
  errors.push(`Unknown filter field "${filter.field}". Available: ${filterNames.join(', ')}`);
73
75
  continue;
74
76
  }
75
- const filterDefinition = ds.filters[filter.field];
77
+ const filterDefinition = filters[filter.field];
76
78
  if (!filterAllowsOperator(filterDefinition, filter.operator)) {
77
79
  errors.push(`Filter "${filter.field}" does not allow operator "${filter.operator}". Allowed: ${(filterDefinition?.operators ?? SEMANTIC_FILTER_OPERATORS).join(', ')}`);
78
80
  continue;
79
81
  }
80
- const resolvedField = ds.filters[filter.field]?.field ?? filter.field;
82
+ const resolvedField = filterDefinition.field;
81
83
  if (isTenantScopedFilter(ds, filter, context)) {
82
84
  errors.push(`Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`);
83
85
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AAEA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAYrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAqBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,CA4Bf"}
1
+ {"version":3,"file":"relationship-validation.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-validation.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACZ,MAAM,aAAa,CAAC;AAYrB,KAAK,iBAAiB,GAAG,IAAI,CAC3B,YAAY,GAAG,WAAW,EAC1B,YAAY,GAAG,SAAS,GAAG,SAAS,CACrC,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;AAEnC;;;;GAIG;AACH,wBAAgB,iCAAiC,CAC/C,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,iBAAiB,EACxB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,SAAS,CAqBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CACrC,EAAE,EAAE,kBAAkB,EACtB,MAAM,EAAE,YAAY,EACpB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,GAAG,IAAI,CA4Bf"}
@@ -1,5 +1,6 @@
1
1
  import { resolveRelationshipReference } from './relationship-measures.js';
2
2
  import { filterAllowsOperator } from './filter-operator-policy.js';
3
+ import { effectiveFilters } from './effective-filters.js';
3
4
  import { validateFilterValue } from '../validation.js';
4
5
  import { getRuntimeTenantPredicate, hasTenantRuntime, } from './tenant-runtime.js';
5
6
  import { runtimeConstraints } from './row-scope.js';
@@ -48,10 +49,10 @@ export function validateQualifiedFilter(ds, filter, context) {
48
49
  if (getRuntimeTenantPredicate(context) && target.tenantKey && targetColumn === target.tenantKey) {
49
50
  return `Cannot filter on tenant field "${filter.field}" when runtime tenancy enforcement is active.`;
50
51
  }
51
- // A relationship does not turn every target dimension into an exposed
52
- // filter. Apply the target's explicit or generated filter allowlist and
53
- // operator policy. An explicit filter can override filterable: false.
54
- const definition = target.filters[targetDimensionName];
52
+ // A relationship exposes the target's filters named after a dimension:
53
+ // its filterable dimensions, and declared filters, which can override
54
+ // filterable: false. A filter alias is not reachable through the hop.
55
+ const definition = effectiveFilters(target)[targetDimensionName];
55
56
  if (!definition
56
57
  || definition.field !== targetDimensionName) {
57
58
  return `Filter "${filter.field}" is not exposed by target dataset "${target.name}".`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/datasets",
3
- "version": "0.28.0",
3
+ "version": "0.29.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",