@lightdash/query-sdk 0.2675.3 → 0.2676.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.
@@ -7,5 +7,20 @@
7
7
  * 2. Poll GET /api/v2/projects/{projectUuid}/query/{queryUuid}
8
8
  * → returns results when status is 'ready'
9
9
  */
10
- import type { LightdashClientConfig, Transport } from './types';
11
- export declare function createApiTransport(config: LightdashClientConfig): Transport;
10
+ import type { Column, LightdashClientConfig, Transport } from './types';
11
+ /**
12
+ * A function that performs HTTP requests and returns parsed JSON results.
13
+ * The default implementation uses `Authorization: ApiKey` header.
14
+ * Supply a custom adapter to use session cookies or other auth mechanisms.
15
+ */
16
+ export type FetchAdapter = <T>(method: string, path: string, body?: unknown) => Promise<T>;
17
+ export declare function mapColumnType(type: string): Column['type'];
18
+ /**
19
+ * Creates a transport that executes queries via the Lightdash REST API.
20
+ *
21
+ * @param config - Client configuration (projectUuid is required; apiKey/baseUrl
22
+ * are used by the default fetch adapter but ignored when a custom adapter is provided)
23
+ * @param adapter - Optional custom fetch adapter. When omitted, uses the default
24
+ * adapter that authenticates via `Authorization: ApiKey` header.
25
+ */
26
+ export declare function createApiTransport(config: LightdashClientConfig, adapter?: FetchAdapter): Transport;
@@ -33,36 +33,36 @@ function buildApiFilters(filters) {
33
33
  },
34
34
  };
35
35
  }
36
- async function apiFetch(config, method, path, body) {
37
- // Use relative paths when a proxy is available (dev server),
38
- // or the full base URL when calling the API directly (production).
39
- const useProxy = config.useProxy ?? false;
40
- const baseUrl = useProxy ? '' : config.baseUrl.replace(/\/$/, '');
41
- const url = `${baseUrl}${path}`;
42
- const res = await fetch(url, {
43
- method,
44
- headers: {
45
- 'Content-Type': 'application/json',
46
- Authorization: `ApiKey ${config.apiKey}`,
47
- },
48
- ...(body ? { body: JSON.stringify(body) } : {}),
49
- });
50
- if (!res.ok) {
51
- const text = await res.text();
52
- let message;
53
- try {
54
- const parsed = JSON.parse(text);
55
- message = parsed.error?.message ?? parsed.message ?? text;
56
- }
57
- catch {
58
- message = text;
36
+ function createDefaultFetchAdapter(config) {
37
+ return async (method, path, body) => {
38
+ const useProxy = config.useProxy ?? false;
39
+ const baseUrl = useProxy ? '' : config.baseUrl.replace(/\/$/, '');
40
+ const url = `${baseUrl}${path}`;
41
+ const res = await fetch(url, {
42
+ method,
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ Authorization: `ApiKey ${config.apiKey}`,
46
+ },
47
+ ...(body ? { body: JSON.stringify(body) } : {}),
48
+ });
49
+ if (!res.ok) {
50
+ const text = await res.text();
51
+ let message;
52
+ try {
53
+ const parsed = JSON.parse(text);
54
+ message = parsed.error?.message ?? parsed.message ?? text;
55
+ }
56
+ catch {
57
+ message = text;
58
+ }
59
+ throw new Error(`Lightdash API error (${res.status}): ${message}`);
59
60
  }
60
- throw new Error(`Lightdash API error (${res.status}): ${message}`);
61
- }
62
- const json = (await res.json());
63
- return json.results;
61
+ const json = (await res.json());
62
+ return json.results;
63
+ };
64
64
  }
65
- function mapColumnType(type) {
65
+ export function mapColumnType(type) {
66
66
  if (/timestamp/i.test(type))
67
67
  return 'timestamp';
68
68
  if (/date/i.test(type))
@@ -73,7 +73,16 @@ function mapColumnType(type) {
73
73
  return 'boolean';
74
74
  return 'string';
75
75
  }
76
- export function createApiTransport(config) {
76
+ /**
77
+ * Creates a transport that executes queries via the Lightdash REST API.
78
+ *
79
+ * @param config - Client configuration (projectUuid is required; apiKey/baseUrl
80
+ * are used by the default fetch adapter but ignored when a custom adapter is provided)
81
+ * @param adapter - Optional custom fetch adapter. When omitted, uses the default
82
+ * adapter that authenticates via `Authorization: ApiKey` header.
83
+ */
84
+ export function createApiTransport(config, adapter) {
85
+ const fetchFn = adapter ?? createDefaultFetchAdapter(config);
77
86
  return {
78
87
  async executeQuery(query) {
79
88
  const table = query.exploreName;
@@ -103,12 +112,12 @@ export function createApiTransport(config) {
103
112
  tableCalculations: [],
104
113
  },
105
114
  };
106
- const execResult = await apiFetch(config, 'POST', `/api/v2/projects/${config.projectUuid}/query/metric-query`, body);
115
+ const execResult = await fetchFn('POST', `/api/v2/projects/${config.projectUuid}/query/metric-query`, body);
107
116
  const { queryUuid, fields } = execResult;
108
117
  // Step 2: Poll for results
109
118
  let attempts = 0;
110
119
  while (attempts < MAX_POLL_ATTEMPTS) {
111
- const pollResult = await apiFetch(config, 'GET', `/api/v2/projects/${config.projectUuid}/query/${queryUuid}`);
120
+ const pollResult = await fetchFn('GET', `/api/v2/projects/${config.projectUuid}/query/${queryUuid}`);
112
121
  if (pollResult.status === 'ready') {
113
122
  // Build a mapping from qualified → short field names
114
123
  // so app code uses row.driver_name, not row.fct_race_results_driver_name
@@ -192,7 +201,7 @@ export function createApiTransport(config) {
192
201
  throw new Error('Query timed out waiting for results');
193
202
  },
194
203
  async getUser() {
195
- const user = await apiFetch(config, 'GET', '/api/v1/user');
204
+ const user = await fetchFn('GET', '/api/v1/user');
196
205
  return {
197
206
  name: `${user.firstName} ${user.lastName}`.trim(),
198
207
  email: user.email,
package/dist/client.d.ts CHANGED
@@ -2,15 +2,7 @@
2
2
  * Lightdash client.
3
3
  *
4
4
  * Usage:
5
- * // Auto-configure from env vars (Vite reads .env automatically)
6
- * const lightdash = createClient()
7
- *
8
- * // Or explicit config
9
- * const lightdash = createClient({
10
- * apiKey: 'pat_xxx',
11
- * baseUrl: 'https://app.lightdash.cloud',
12
- * projectUuid: 'uuid',
13
- * })
5
+ * const lightdash = createClient() // auto-detects from environment
14
6
  */
15
7
  import { QueryBuilder } from './query';
16
8
  import type { LightdashClientConfig, LightdashUser, Transport } from './types';
@@ -20,17 +12,14 @@ export declare class LightdashClient {
20
12
  readonly auth: {
21
13
  getUser: () => Promise<LightdashUser>;
22
14
  };
23
- constructor(config: LightdashClientConfig, transport?: Transport);
15
+ constructor(config: LightdashClientConfig, transport: Transport);
24
16
  /** Start building a query against a model */
25
17
  model(exploreName: string): QueryBuilder;
26
18
  }
27
19
  /**
28
- * Create a Lightdash client.
29
- *
30
- * With no args, reads from env vars:
31
- * const lightdash = createClient()
20
+ * Create a Lightdash client. Auto-detects the transport from the environment:
32
21
  *
33
- * With explicit config (used when token comes from parent frame):
34
- * const lightdash = createClient({ apiKey, baseUrl, projectUuid })
22
+ * 1. Hash fragment #transport=postMessage postMessage bridge (Lightdash iframe)
23
+ * 2. Env vars (VITE_LIGHTDASH_* or LIGHTDASH_*) direct API calls (local dev)
35
24
  */
36
- export declare function createClient(config?: LightdashClientConfig): LightdashClient;
25
+ export declare function createClient(): LightdashClient;
package/dist/client.js CHANGED
@@ -2,22 +2,15 @@
2
2
  * Lightdash client.
3
3
  *
4
4
  * Usage:
5
- * // Auto-configure from env vars (Vite reads .env automatically)
6
- * const lightdash = createClient()
7
- *
8
- * // Or explicit config
9
- * const lightdash = createClient({
10
- * apiKey: 'pat_xxx',
11
- * baseUrl: 'https://app.lightdash.cloud',
12
- * projectUuid: 'uuid',
13
- * })
5
+ * const lightdash = createClient() // auto-detects from environment
14
6
  */
15
7
  import { createApiTransport } from './apiTransport';
8
+ import { createPostMessageTransport } from './postMessageTransport';
16
9
  import { QueryBuilder } from './query';
17
10
  export class LightdashClient {
18
11
  constructor(config, transport) {
19
12
  this.config = config;
20
- this.transport = transport ?? createApiTransport(config);
13
+ this.transport = transport;
21
14
  this.auth = {
22
15
  getUser: () => this.transport.getUser(),
23
16
  };
@@ -31,14 +24,10 @@ export class LightdashClient {
31
24
  * Resolve config from env vars.
32
25
  *
33
26
  * Vite (.env file, statically replaced at build time):
34
- * VITE_LIGHTDASH_API_KEY=pat_xxx
35
- * VITE_LIGHTDASH_URL=https://app.lightdash.cloud
36
- * VITE_LIGHTDASH_PROJECT_UUID=uuid
27
+ * VITE_LIGHTDASH_API_KEY, VITE_LIGHTDASH_URL, VITE_LIGHTDASH_PROJECT_UUID
37
28
  *
38
29
  * Node/E2B (runtime):
39
- * LIGHTDASH_API_KEY=pat_xxx
40
- * LIGHTDASH_URL=https://app.lightdash.cloud
41
- * LIGHTDASH_PROJECT_UUID=uuid
30
+ * LIGHTDASH_API_KEY, LIGHTDASH_URL, LIGHTDASH_PROJECT_UUID
42
31
  */
43
32
  function configFromEnv() {
44
33
  // Vite statically replaces import.meta.env.VITE_X at build time.
@@ -60,19 +49,25 @@ function configFromEnv() {
60
49
  return { apiKey, baseUrl, projectUuid, useProxy };
61
50
  }
62
51
  /**
63
- * Create a Lightdash client.
64
- *
65
- * With no args, reads from env vars:
66
- * const lightdash = createClient()
52
+ * Create a Lightdash client. Auto-detects the transport from the environment:
67
53
  *
68
- * With explicit config (used when token comes from parent frame):
69
- * const lightdash = createClient({ apiKey, baseUrl, projectUuid })
54
+ * 1. Hash fragment #transport=postMessage postMessage bridge (Lightdash iframe)
55
+ * 2. Env vars (VITE_LIGHTDASH_* or LIGHTDASH_*) direct API calls (local dev)
70
56
  */
71
- export function createClient(config) {
72
- const resolved = config ?? configFromEnv();
73
- if (!resolved) {
74
- throw new Error('Missing Lightdash client config. Either pass { apiKey, baseUrl, projectUuid } ' +
75
- 'or set env vars: VITE_LIGHTDASH_API_KEY, VITE_LIGHTDASH_URL, VITE_LIGHTDASH_PROJECT_UUID');
57
+ export function createClient() {
58
+ // 1. postMessage transport (iframe hosted by Lightdash parent)
59
+ if (typeof window !== 'undefined' && window.location.hash) {
60
+ const params = new URLSearchParams(window.location.hash.replace(/^#/, ''));
61
+ if (params.get('transport') === 'postMessage') {
62
+ const projectUuid = params.get('projectUuid') ?? '';
63
+ return new LightdashClient({ apiKey: '', baseUrl: '', projectUuid }, createPostMessageTransport({ targetWindow: window.parent, projectUuid }));
64
+ }
65
+ }
66
+ // 2. Env vars → API transport
67
+ const config = configFromEnv();
68
+ if (!config) {
69
+ throw new Error('Missing Lightdash client config. ' +
70
+ 'Set env vars: VITE_LIGHTDASH_API_KEY, VITE_LIGHTDASH_URL, VITE_LIGHTDASH_PROJECT_UUID');
76
71
  }
77
- return new LightdashClient(resolved);
72
+ return new LightdashClient(config, createApiTransport(config));
78
73
  }
package/dist/index.d.ts CHANGED
@@ -2,4 +2,7 @@ export { query } from './query';
2
2
  export { createClient, LightdashClient } from './client';
3
3
  export { useLightdash } from './useLightdash';
4
4
  export { LightdashProvider, useLightdashClient } from './LightdashProvider';
5
- export type { Column, Filter, FilterOperator, FilterValue, FormatFunction, LightdashClientConfig, LightdashUser, Row, Sort, UnitOfTime, } from './types';
5
+ export { createApiTransport, type FetchAdapter } from './apiTransport';
6
+ export { createPostMessageTransport } from './postMessageTransport';
7
+ export type { Column, Filter, FilterOperator, FilterValue, FormatFunction, LightdashClientConfig, LightdashUser, QueryDefinition, QueryResult, Row, Sort, Transport, UnitOfTime, } from './types';
8
+ export type { SdkFetchRequest, SdkFetchResponse, SdkReadyMessage, } from './postMessageTransport';
package/dist/index.js CHANGED
@@ -6,3 +6,6 @@ export { createClient, LightdashClient } from './client';
6
6
  export { useLightdash } from './useLightdash';
7
7
  // Provider
8
8
  export { LightdashProvider, useLightdashClient } from './LightdashProvider';
9
+ // Transports
10
+ export { createApiTransport } from './apiTransport';
11
+ export { createPostMessageTransport } from './postMessageTransport';
@@ -0,0 +1,41 @@
1
+ /**
2
+ * postMessage transport — routes HTTP requests through the parent window
3
+ * via postMessage instead of calling the API directly.
4
+ *
5
+ * Used when the SDK runs inside a sandboxed iframe (sandbox="allow-scripts")
6
+ * that cannot make direct API calls. The parent receives fetch requests,
7
+ * executes them with its own session cookies, and sends the raw API
8
+ * response back. All query logic (field qualification, polling, result
9
+ * mapping) stays in the SDK's apiTransport.
10
+ */
11
+ import type { Transport } from './types';
12
+ export type SdkFetchRequest = {
13
+ type: 'lightdash:sdk:fetch';
14
+ id: string;
15
+ method: string;
16
+ path: string;
17
+ body?: unknown;
18
+ };
19
+ export type SdkFetchResponse = {
20
+ type: 'lightdash:sdk:fetch-response';
21
+ id: string;
22
+ result?: unknown;
23
+ error?: string;
24
+ };
25
+ export type SdkReadyMessage = {
26
+ type: 'lightdash:sdk:ready';
27
+ };
28
+ type PostMessageTransportConfig = {
29
+ targetWindow: Window;
30
+ projectUuid: string;
31
+ timeoutMs?: number;
32
+ };
33
+ /**
34
+ * Creates a Transport that routes all API calls through the parent window
35
+ * via postMessage. The parent acts as a fetch proxy using session cookies.
36
+ *
37
+ * All query logic (field qualification, polling, result mapping) is handled
38
+ * by the SDK's apiTransport — the postMessage layer is just the HTTP adapter.
39
+ */
40
+ export declare function createPostMessageTransport(config: PostMessageTransportConfig): Transport;
41
+ export {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * postMessage transport — routes HTTP requests through the parent window
3
+ * via postMessage instead of calling the API directly.
4
+ *
5
+ * Used when the SDK runs inside a sandboxed iframe (sandbox="allow-scripts")
6
+ * that cannot make direct API calls. The parent receives fetch requests,
7
+ * executes them with its own session cookies, and sends the raw API
8
+ * response back. All query logic (field qualification, polling, result
9
+ * mapping) stays in the SDK's apiTransport.
10
+ */
11
+ import { createApiTransport } from './apiTransport';
12
+ const DEFAULT_TIMEOUT_MS = 120000;
13
+ const READY_TIMEOUT_MS = 10000;
14
+ /**
15
+ * Creates a FetchAdapter that sends HTTP requests to the parent window
16
+ * via postMessage and waits for responses.
17
+ */
18
+ function createPostMessageFetchAdapter(config) {
19
+ const { targetWindow, timeoutMs = DEFAULT_TIMEOUT_MS } = config;
20
+ const pending = new Map();
21
+ let readyResolve = null;
22
+ const readyPromise = new Promise((resolve) => {
23
+ readyResolve = resolve;
24
+ });
25
+ const readyTimer = setTimeout(() => {
26
+ readyResolve?.();
27
+ }, READY_TIMEOUT_MS);
28
+ window.addEventListener('message', (event) => {
29
+ const { data } = event;
30
+ if (!data || typeof data !== 'object' || typeof data.type !== 'string') {
31
+ return;
32
+ }
33
+ if (data.type === 'lightdash:sdk:ready') {
34
+ clearTimeout(readyTimer);
35
+ readyResolve?.();
36
+ readyResolve = null;
37
+ return;
38
+ }
39
+ if (data.type === 'lightdash:sdk:fetch-response') {
40
+ const msg = data;
41
+ const req = pending.get(msg.id);
42
+ if (!req)
43
+ return;
44
+ clearTimeout(req.timer);
45
+ pending.delete(msg.id);
46
+ if (msg.error) {
47
+ req.reject(new Error(msg.error));
48
+ }
49
+ else {
50
+ req.resolve(msg.result);
51
+ }
52
+ }
53
+ });
54
+ return async (method, path, body) => {
55
+ await readyPromise;
56
+ const id = crypto.randomUUID();
57
+ return new Promise((resolve, reject) => {
58
+ const timer = setTimeout(() => {
59
+ pending.delete(id);
60
+ reject(new Error(`SDK fetch timed out after ${timeoutMs}ms: ${method} ${path}`));
61
+ }, timeoutMs);
62
+ pending.set(id, {
63
+ resolve: resolve,
64
+ reject,
65
+ timer,
66
+ });
67
+ const message = {
68
+ type: 'lightdash:sdk:fetch',
69
+ id,
70
+ method,
71
+ path,
72
+ body,
73
+ };
74
+ targetWindow.postMessage(message, '*');
75
+ });
76
+ };
77
+ }
78
+ /**
79
+ * Creates a Transport that routes all API calls through the parent window
80
+ * via postMessage. The parent acts as a fetch proxy using session cookies.
81
+ *
82
+ * All query logic (field qualification, polling, result mapping) is handled
83
+ * by the SDK's apiTransport — the postMessage layer is just the HTTP adapter.
84
+ */
85
+ export function createPostMessageTransport(config) {
86
+ const adapter = createPostMessageFetchAdapter({
87
+ targetWindow: config.targetWindow,
88
+ timeoutMs: config.timeoutMs,
89
+ });
90
+ return createApiTransport({ apiKey: '', baseUrl: '', projectUuid: config.projectUuid }, adapter);
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightdash/query-sdk",
3
- "version": "0.2675.3",
3
+ "version": "0.2676.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SDK for building custom data apps against the Lightdash semantic layer",