@lightdash/query-sdk 0.2775.3 → 0.2777.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.
@@ -89,9 +89,21 @@ export function createApiTransport(config, adapter) {
89
89
  // Lightdash field IDs are `{table}_{column}`.
90
90
  // The SDK lets users write short names like 'driver_name'
91
91
  // and we qualify them to 'fct_race_results_driver_name'.
92
- const qualify = (fieldId) => fieldId.startsWith(`${table}_`)
93
- ? fieldId
94
- : `${table}_${fieldId}`;
92
+ //
93
+ // For joined table fields, use dot notation: 'customers.name'
94
+ // which resolves to 'customers_name' (not 'orders_customers_name').
95
+ const qualify = (fieldId) => {
96
+ // Dot notation: 'customers.name' → 'customers_name'
97
+ if (fieldId.includes('.')) {
98
+ return fieldId.replace('.', '_');
99
+ }
100
+ // Already qualified with explore name
101
+ if (fieldId.startsWith(`${table}_`)) {
102
+ return fieldId;
103
+ }
104
+ // Short name → qualify with explore name
105
+ return `${table}_${fieldId}`;
106
+ };
95
107
  const qualifiedDims = query.dimensions.map(qualify);
96
108
  const qualifiedMetrics = query.metrics.map(qualify);
97
109
  // Step 1: Execute async query
@@ -110,6 +122,12 @@ export function createApiTransport(config, adapter) {
110
122
  })),
111
123
  limit: query.limit,
112
124
  tableCalculations: query.tableCalculations,
125
+ ...(query.additionalMetrics.length > 0
126
+ ? { additionalMetrics: query.additionalMetrics }
127
+ : {}),
128
+ ...(query.customDimensions.length > 0
129
+ ? { customDimensions: query.customDimensions }
130
+ : {}),
113
131
  },
114
132
  };
115
133
  // Pass label as transport metadata (not in the API body)
package/dist/index.d.ts CHANGED
@@ -4,5 +4,5 @@ export { useLightdash } from './useLightdash';
4
4
  export { LightdashProvider, useLightdashClient } from './LightdashProvider';
5
5
  export { createApiTransport, type FetchAdapter } from './apiTransport';
6
6
  export { createPostMessageTransport } from './postMessageTransport';
7
- export type { Column, Filter, FilterOperator, FilterValue, FormatFunction, LightdashClientConfig, LightdashUser, QueryDefinition, QueryResult, Row, Sort, TableCalculation, Transport, UnitOfTime, } from './types';
7
+ export type { AdditionalMetric, Column, CustomDimension, Filter, FilterOperator, FilterValue, FormatFunction, LightdashClientConfig, LightdashUser, MetricType, QueryDefinition, QueryResult, Row, Sort, TableCalculation, Transport, UnitOfTime, } from './types';
8
8
  export type { SdkFetchRequest, SdkFetchResponse, SdkReadyMessage, } from './postMessageTransport';
package/dist/query.d.ts CHANGED
@@ -11,7 +11,22 @@
11
11
  *
12
12
  * The builder is immutable -- each method returns a new instance.
13
13
  */
14
- import type { Filter, InternalFilterDefinition, QueryDefinition, Sort, TableCalculation } from './types';
14
+ import type { AdditionalMetric, CustomDimension, Filter, InternalFilterDefinition, QueryDefinition, Sort, TableCalculation } from './types';
15
+ type BuilderState = {
16
+ explore: string;
17
+ dimensions: string[];
18
+ metrics: string[];
19
+ filters: InternalFilterDefinition[];
20
+ sorts: {
21
+ fieldId: string;
22
+ descending: boolean;
23
+ }[];
24
+ tableCalculations: TableCalculation[];
25
+ additionalMetrics: AdditionalMetric[];
26
+ customDimensions: CustomDimension[];
27
+ limit: number;
28
+ label: string | undefined;
29
+ };
15
30
  /**
16
31
  * Create a query builder for a model.
17
32
  *
@@ -23,18 +38,10 @@ import type { Filter, InternalFilterDefinition, QueryDefinition, Sort, TableCalc
23
38
  */
24
39
  export declare function query(modelName: string): QueryBuilder;
25
40
  export declare class QueryBuilder {
26
- private readonly _explore;
27
- private readonly _dimensions;
28
- private readonly _metrics;
29
- private readonly _filters;
30
- private readonly _sorts;
31
- private readonly _tableCalculations;
32
- private readonly _limit;
33
- private readonly _label;
34
- constructor(explore: string, dimensions?: string[], metrics?: string[], filters?: InternalFilterDefinition[], sorts?: {
35
- fieldId: string;
36
- descending: boolean;
37
- }[], limit?: number, label?: string, tableCalculations?: TableCalculation[]);
41
+ private readonly _state;
42
+ constructor(explore: string);
43
+ constructor(state: BuilderState);
44
+ private _clone;
38
45
  /** Human-readable label for dev tools / query inspector */
39
46
  label(name: string): QueryBuilder;
40
47
  /** Set dimension fields (GROUP BY columns) */
@@ -47,8 +54,18 @@ export declare class QueryBuilder {
47
54
  sorts(sorts: Sort[]): QueryBuilder;
48
55
  /** Add table calculations (computed columns evaluated after the query) */
49
56
  tableCalculations(calcs: TableCalculation[]): QueryBuilder;
57
+ /**
58
+ * Add additional metrics (ad-hoc aggregations defined at query time).
59
+ * Use this for metrics on joined tables or custom aggregations not in the YAML.
60
+ */
61
+ additionalMetrics(metrics: AdditionalMetric[]): QueryBuilder;
62
+ /**
63
+ * Add custom dimensions (ad-hoc dimensions defined at query time).
64
+ */
65
+ customDimensions(dims: CustomDimension[]): QueryBuilder;
50
66
  /** Set the maximum number of rows to return (default: 500) */
51
67
  limit(n: number): QueryBuilder;
52
68
  /** Convert to a plain QueryDefinition object */
53
69
  build(): QueryDefinition;
54
70
  }
71
+ export {};
package/dist/query.js CHANGED
@@ -24,27 +24,43 @@ export function query(modelName) {
24
24
  return new QueryBuilder(modelName);
25
25
  }
26
26
  export class QueryBuilder {
27
- constructor(explore, dimensions = [], metrics = [], filters = [], sorts = [], limit = 500, label, tableCalculations = []) {
28
- this._explore = explore;
29
- this._dimensions = dimensions;
30
- this._metrics = metrics;
31
- this._filters = filters;
32
- this._sorts = sorts;
33
- this._tableCalculations = tableCalculations;
34
- this._limit = limit;
35
- this._label = label;
27
+ constructor(exploreOrState) {
28
+ if (typeof exploreOrState === 'string') {
29
+ this._state = {
30
+ explore: exploreOrState,
31
+ dimensions: [],
32
+ metrics: [],
33
+ filters: [],
34
+ sorts: [],
35
+ tableCalculations: [],
36
+ additionalMetrics: [],
37
+ customDimensions: [],
38
+ limit: 500,
39
+ label: undefined,
40
+ };
41
+ }
42
+ else {
43
+ this._state = exploreOrState;
44
+ }
45
+ }
46
+ _clone(overrides) {
47
+ return new QueryBuilder({ ...this._state, ...overrides });
36
48
  }
37
49
  /** Human-readable label for dev tools / query inspector */
38
50
  label(name) {
39
- return new QueryBuilder(this._explore, this._dimensions, this._metrics, this._filters, this._sorts, this._limit, name, this._tableCalculations);
51
+ return this._clone({ label: name });
40
52
  }
41
53
  /** Set dimension fields (GROUP BY columns) */
42
54
  dimensions(fields) {
43
- return new QueryBuilder(this._explore, [...this._dimensions, ...fields], this._metrics, this._filters, this._sorts, this._limit, this._label, this._tableCalculations);
55
+ return this._clone({
56
+ dimensions: [...this._state.dimensions, ...fields],
57
+ });
44
58
  }
45
59
  /** Set metric fields (aggregations) */
46
60
  metrics(fields) {
47
- return new QueryBuilder(this._explore, this._dimensions, [...this._metrics, ...fields], this._filters, this._sorts, this._limit, this._label, this._tableCalculations);
61
+ return this._clone({
62
+ metrics: [...this._state.metrics, ...fields],
63
+ });
48
64
  }
49
65
  /** Add filters */
50
66
  filters(filters) {
@@ -65,7 +81,9 @@ export class QueryBuilder {
65
81
  settings: f.unit ? { unitOfTime: f.unit } : null,
66
82
  };
67
83
  });
68
- return new QueryBuilder(this._explore, this._dimensions, this._metrics, [...this._filters, ...converted], this._sorts, this._limit, this._label, this._tableCalculations);
84
+ return this._clone({
85
+ filters: [...this._state.filters, ...converted],
86
+ });
69
87
  }
70
88
  /** Add sorts */
71
89
  sorts(sorts) {
@@ -73,27 +91,50 @@ export class QueryBuilder {
73
91
  fieldId: s.field,
74
92
  descending: s.direction === 'desc',
75
93
  }));
76
- return new QueryBuilder(this._explore, this._dimensions, this._metrics, this._filters, [...this._sorts, ...converted], this._limit, this._label, this._tableCalculations);
94
+ return this._clone({
95
+ sorts: [...this._state.sorts, ...converted],
96
+ });
77
97
  }
78
98
  /** Add table calculations (computed columns evaluated after the query) */
79
99
  tableCalculations(calcs) {
80
- return new QueryBuilder(this._explore, this._dimensions, this._metrics, this._filters, this._sorts, this._limit, this._label, [...this._tableCalculations, ...calcs]);
100
+ return this._clone({
101
+ tableCalculations: [...this._state.tableCalculations, ...calcs],
102
+ });
103
+ }
104
+ /**
105
+ * Add additional metrics (ad-hoc aggregations defined at query time).
106
+ * Use this for metrics on joined tables or custom aggregations not in the YAML.
107
+ */
108
+ additionalMetrics(metrics) {
109
+ return this._clone({
110
+ additionalMetrics: [...this._state.additionalMetrics, ...metrics],
111
+ });
112
+ }
113
+ /**
114
+ * Add custom dimensions (ad-hoc dimensions defined at query time).
115
+ */
116
+ customDimensions(dims) {
117
+ return this._clone({
118
+ customDimensions: [...this._state.customDimensions, ...dims],
119
+ });
81
120
  }
82
121
  /** Set the maximum number of rows to return (default: 500) */
83
122
  limit(n) {
84
- return new QueryBuilder(this._explore, this._dimensions, this._metrics, this._filters, this._sorts, n, this._label, this._tableCalculations);
123
+ return this._clone({ limit: n });
85
124
  }
86
125
  /** Convert to a plain QueryDefinition object */
87
126
  build() {
88
127
  return {
89
- exploreName: this._explore,
90
- dimensions: this._dimensions,
91
- metrics: this._metrics,
92
- filters: this._filters,
93
- sorts: this._sorts,
94
- tableCalculations: this._tableCalculations,
95
- limit: this._limit,
96
- ...(this._label ? { label: this._label } : {}),
128
+ exploreName: this._state.explore,
129
+ dimensions: this._state.dimensions,
130
+ metrics: this._state.metrics,
131
+ filters: this._state.filters,
132
+ sorts: this._state.sorts,
133
+ tableCalculations: this._state.tableCalculations,
134
+ additionalMetrics: this._state.additionalMetrics,
135
+ customDimensions: this._state.customDimensions,
136
+ limit: this._state.limit,
137
+ ...(this._state.label ? { label: this._state.label } : {}),
97
138
  };
98
139
  }
99
140
  }
package/dist/types.d.ts CHANGED
@@ -22,6 +22,33 @@ export type TableCalculation = {
22
22
  /** SQL expression for the calculation */
23
23
  sql: string;
24
24
  };
25
+ export type MetricType = 'average' | 'count' | 'count_distinct' | 'sum' | 'sum_distinct' | 'min' | 'max' | 'number' | 'median' | 'percentile';
26
+ export type AdditionalMetric = {
27
+ /** Internal name of the metric (used as the field ID in results) */
28
+ name: string;
29
+ /** Display label */
30
+ label?: string;
31
+ /** Table the metric belongs to */
32
+ table: string;
33
+ /** Aggregation type */
34
+ type: MetricType;
35
+ /** SQL expression (e.g., ${TABLE}.column_name) */
36
+ sql: string;
37
+ /** Description of what the metric measures */
38
+ description?: string;
39
+ };
40
+ export type CustomDimension = {
41
+ /** Unique ID for the custom dimension */
42
+ id: string;
43
+ /** Internal name */
44
+ name: string;
45
+ /** Table the dimension belongs to */
46
+ table: string;
47
+ /** SQL expression */
48
+ sql: string;
49
+ /** The dimension type */
50
+ dimensionId: string;
51
+ };
25
52
  export type InternalFilterDefinition = {
26
53
  fieldId: string;
27
54
  operator: string;
@@ -40,6 +67,8 @@ export type QueryDefinition = {
40
67
  descending: boolean;
41
68
  }[];
42
69
  tableCalculations: TableCalculation[];
70
+ additionalMetrics: AdditionalMetric[];
71
+ customDimensions: CustomDimension[];
43
72
  limit: number;
44
73
  /** Human-readable label for dev tools / query inspector (not sent to the API) */
45
74
  label?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/query-sdk",
3
- "version": "0.2775.3",
3
+ "version": "0.2777.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SDK for building custom data apps against the Lightdash semantic layer",