@lightdash/query-sdk 1.85.1 → 1.86.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/README.md CHANGED
@@ -92,10 +92,18 @@ lightdash
92
92
  { field: 'amount', operator: 'greaterThan', value: 1000 },
93
93
  { field: 'order_date', operator: 'inThePast', value: 90, unit: 'days' },
94
94
  ])
95
+ .metricFilters([
96
+ { field: 'total_revenue', operator: 'greaterThanOrEqual', value: 5000 },
97
+ ])
95
98
  .sorts([{ field: 'total_revenue', direction: 'desc' }])
96
99
  .limit(100);
97
100
  ```
98
101
 
102
+ `.filters()` accepts dimensions and serializes them as WHERE filters.
103
+ `.metricFilters()` accepts metrics and serializes them as HAVING filters. A
104
+ metric can be used only as a filter and does not need to be selected with
105
+ `.metrics()`.
106
+
99
107
  Supported filter operators: `equals`, `notEquals`, `greaterThan`, `lessThan`, `greaterThanOrEqual`, `lessThanOrEqual`, `inThePast`, `notInThePast`, `inTheNext`, `inTheCurrent`, `notInTheCurrent`, `inBetween`, `notInBetween`, `isNull`, `notNull`, `startsWith`, `endsWith`, `include`, `doesNotInclude`.
100
108
 
101
109
  ## Parameters
@@ -20,27 +20,31 @@ const sleep = (ms) => new Promise((resolve) => {
20
20
  setTimeout(resolve, ms);
21
21
  });
22
22
  /**
23
- * Convert SDK filter definitions into the Lightdash API filter format.
24
- * The API expects { dimensions: { id, and: [...rules] } }
23
+ * Convert SDK dimension and metric filter definitions into the Lightdash API
24
+ * filter format.
25
25
  */
26
- function buildApiFilters(filters) {
27
- if (filters.length === 0) {
28
- return {};
29
- }
30
- // For now, all filters are AND-ed on dimensions.
31
- // TODO: support metric filters and OR groups
32
- const rules = filters.map((f, i) => ({
33
- id: `sdk-filter-${i}`,
34
- target: { fieldId: f.fieldId },
35
- operator: f.operator,
36
- values: f.values,
37
- ...(f.settings ? { settings: f.settings } : {}),
38
- }));
26
+ function buildApiFilters(dimensionFilters, metricFilters = []) {
27
+ const buildGroup = (filters, rootId, ruleIdPrefix) => ({
28
+ id: rootId,
29
+ and: filters.map((filter, index) => ({
30
+ id: `${ruleIdPrefix}-${index}`,
31
+ target: { fieldId: filter.fieldId },
32
+ operator: filter.operator,
33
+ values: filter.values,
34
+ ...(filter.settings ? { settings: filter.settings } : {}),
35
+ })),
36
+ });
39
37
  return {
40
- dimensions: {
41
- id: 'sdk-root',
42
- and: rules,
43
- },
38
+ ...(dimensionFilters.length > 0
39
+ ? {
40
+ dimensions: buildGroup(dimensionFilters, 'sdk-root', 'sdk-filter'),
41
+ }
42
+ : {}),
43
+ ...(metricFilters.length > 0
44
+ ? {
45
+ metrics: buildGroup(metricFilters, 'sdk-metric-root', 'sdk-metric-filter'),
46
+ }
47
+ : {}),
44
48
  };
45
49
  }
46
50
  const createFieldQualifier = (table) => (fieldId) => {
@@ -123,6 +127,9 @@ function buildMetricQueryBody(query, qualify, limit = query.limit) {
123
127
  filters: buildApiFilters(query.filters.map((f) => ({
124
128
  ...f,
125
129
  fieldId: qualify(f.fieldId),
130
+ })), (query.metricFilters ?? []).map((f) => ({
131
+ ...f,
132
+ fieldId: qualify(f.fieldId),
126
133
  }))),
127
134
  sorts: query.sorts.map((s) => ({
128
135
  fieldId: qualify(s.fieldId),
package/dist/features.js CHANGED
@@ -13,6 +13,12 @@ export const SDK_FEATURES = [
13
13
  label: 'Semantic layer queries',
14
14
  description: 'Run metric and dimension queries against the Lightdash semantic layer.',
15
15
  },
16
+ {
17
+ key: 'metric-filters',
18
+ label: 'Metric filters',
19
+ description: 'Filter grouped query results by metric values, including metrics used only as filters.',
20
+ wiring: 'Pass metric filter rules to query(...).metricFilters([...]); keep dimension filter rules in .filters([...]).',
21
+ },
16
22
  {
17
23
  key: 'saved-chart',
18
24
  label: 'Saved chart queries',
@@ -1 +1 @@
1
- export declare const SDK_VERSION = "1.85.1";
1
+ export declare const SDK_VERSION = "1.86.0";
@@ -1,2 +1,2 @@
1
1
  // Generated by scripts/generateSdkVersion.mjs (prebuild) — do not edit.
2
- export const SDK_VERSION = '1.85.1';
2
+ export const SDK_VERSION = '1.86.0';
package/dist/query.d.ts CHANGED
@@ -6,6 +6,7 @@
6
6
  * .dimensions(['customer_segment', 'order_date'])
7
7
  * .metrics(['total_revenue', 'order_count'])
8
8
  * .filters([{ field: 'order_date', operator: 'inThePast', value: 90, unit: 'days' }])
9
+ * .metricFilters([{ field: 'total_revenue', operator: 'greaterThan', value: 1000 }])
9
10
  * .sorts([{ field: 'total_revenue', direction: 'desc' }])
10
11
  * .limit(100)
11
12
  *
@@ -17,6 +18,7 @@ type BuilderState = {
17
18
  dimensions: string[];
18
19
  metrics: string[];
19
20
  filters: InternalFilterDefinition[];
21
+ metricFilters: InternalFilterDefinition[];
20
22
  sorts: {
21
23
  fieldId: string;
22
24
  descending: boolean;
@@ -49,8 +51,10 @@ export declare class QueryBuilder {
49
51
  dimensions(fields: string[]): QueryBuilder;
50
52
  /** Set metric fields (aggregations) */
51
53
  metrics(fields: string[]): QueryBuilder;
52
- /** Add filters */
54
+ /** Add dimension (WHERE) filters */
53
55
  filters(filters: Filter[]): QueryBuilder;
56
+ /** Add metric (HAVING) filters. The metrics do not need to be selected. */
57
+ metricFilters(filters: Filter[]): QueryBuilder;
54
58
  /** Add sorts */
55
59
  sorts(sorts: Sort[]): QueryBuilder;
56
60
  /** Add table calculations (computed columns evaluated after the query) */
package/dist/query.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * .dimensions(['customer_segment', 'order_date'])
7
7
  * .metrics(['total_revenue', 'order_count'])
8
8
  * .filters([{ field: 'order_date', operator: 'inThePast', value: 90, unit: 'days' }])
9
+ * .metricFilters([{ field: 'total_revenue', operator: 'greaterThan', value: 1000 }])
9
10
  * .sorts([{ field: 'total_revenue', direction: 'desc' }])
10
11
  * .limit(100)
11
12
  *
@@ -32,6 +33,7 @@ export class QueryBuilder {
32
33
  dimensions: [],
33
34
  metrics: [],
34
35
  filters: [],
36
+ metricFilters: [],
35
37
  sorts: [],
36
38
  tableCalculations: [],
37
39
  additionalMetrics: [],
@@ -73,12 +75,21 @@ export class QueryBuilder {
73
75
  metrics: [...this._state.metrics, ...fields],
74
76
  });
75
77
  }
76
- /** Add filters */
78
+ /** Add dimension (WHERE) filters */
77
79
  filters(filters) {
78
80
  return this._clone({
79
81
  filters: [...this._state.filters, ...toInternalFilters(filters)],
80
82
  });
81
83
  }
84
+ /** Add metric (HAVING) filters. The metrics do not need to be selected. */
85
+ metricFilters(filters) {
86
+ return this._clone({
87
+ metricFilters: [
88
+ ...this._state.metricFilters,
89
+ ...toInternalFilters(filters),
90
+ ],
91
+ });
92
+ }
82
93
  /** Add sorts */
83
94
  sorts(sorts) {
84
95
  const converted = sorts.map((s) => ({
@@ -135,6 +146,7 @@ export class QueryBuilder {
135
146
  dimensions: this._state.dimensions,
136
147
  metrics: this._state.metrics,
137
148
  filters: this._state.filters,
149
+ metricFilters: this._state.metricFilters,
138
150
  sorts: this._state.sorts,
139
151
  tableCalculations: this._state.tableCalculations,
140
152
  additionalMetrics: this._state.additionalMetrics,
@@ -6,7 +6,7 @@ import type { AdditionalMetric, CustomDimension, Filter, InternalFilterDefinitio
6
6
  *
7
7
  * CRITICAL: this mirrors the ENTIRE chainable `QueryBuilder` surface. Generated
8
8
  * apps chain builder methods on every query (`.dimensions().metrics().filters()
9
- * .sorts().label().limit()…`); calling a method that doesn't exist on a plain
9
+ * .metricFilters().sorts().label().limit()…`); calling a method that doesn't exist on a plain
10
10
  * object throws `TypeError: X is not a function` and crashes the whole app. So a
11
11
  * linked chart must be exactly as crash-safe as a normal query — every method
12
12
  * below exists and is chainable.
@@ -41,6 +41,7 @@ export type SavedChartQuery = {
41
41
  tableCalculations: (calcs: TableCalculation[]) => SavedChartQuery;
42
42
  additionalMetrics: (metrics: AdditionalMetric[]) => SavedChartQuery;
43
43
  customDimensions: (dims: CustomDimension[]) => SavedChartQuery;
44
+ metricFilters: (filters: Filter[]) => SavedChartQuery;
44
45
  };
45
46
  export declare function savedChart(chartUuid: string, label?: string): SavedChartQuery;
46
47
  /** Identity key for a saved-chart query — anything that changes the run must change the key. */
@@ -31,6 +31,7 @@ export function savedChart(chartUuid, label) {
31
31
  tableCalculations: () => self,
32
32
  additionalMetrics: () => self,
33
33
  customDimensions: () => self,
34
+ metricFilters: () => self,
34
35
  };
35
36
  return self;
36
37
  };
package/dist/types.d.ts CHANGED
@@ -73,7 +73,10 @@ export type QueryDefinition = {
73
73
  exploreName: string;
74
74
  dimensions: string[];
75
75
  metrics: string[];
76
+ /** Dimension (WHERE) filters added with `.filters()`. */
76
77
  filters: InternalFilterDefinition[];
78
+ /** Metric (HAVING) filters added with `.metricFilters()`. Optional for definitions built by older SDK versions. */
79
+ metricFilters?: InternalFilterDefinition[];
77
80
  sorts: {
78
81
  fieldId: string;
79
82
  descending: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/query-sdk",
3
- "version": "1.85.1",
3
+ "version": "1.86.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SDK for building custom data apps against the Lightdash semantic layer",
@@ -34,7 +34,7 @@
34
34
  "jsdom": "26.1.0",
35
35
  "typescript": "7.0.2",
36
36
  "vitest": "4.1.6",
37
- "@lightdash/common": "1.85.1"
37
+ "@lightdash/common": "1.86.0"
38
38
  },
39
39
  "scripts": {
40
40
  "prebuild": "node ./scripts/generateSdkVersion.mjs",