@lightdash/query-sdk 3.97.0 → 3.98.1
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/apiTransport.d.ts +23 -0
- package/dist/apiTransport.js +97 -1
- package/dist/features.js +7 -0
- package/dist/generated/sdkVersion.d.ts +1 -1
- package/dist/generated/sdkVersion.js +1 -1
- package/dist/hostContext.d.ts +15 -3
- package/dist/hostContext.js +37 -5
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/postMessageTransport.js +13 -2
- package/dist/spaceCharts.d.ts +26 -0
- package/dist/spaceCharts.js +82 -0
- package/dist/types.d.ts +36 -0
- package/dist/types.js +7 -0
- package/package.json +2 -2
package/dist/apiTransport.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* → returns results when status is 'ready'
|
|
9
9
|
*/
|
|
10
10
|
import type { Column, LightdashClientConfig, Transport } from './types';
|
|
11
|
+
import { type SpaceChartsResult } from './types';
|
|
11
12
|
/**
|
|
12
13
|
* A function that performs HTTP requests and returns parsed JSON results.
|
|
13
14
|
* The default implementation uses `Authorization: ApiKey` header.
|
|
@@ -24,10 +25,32 @@ export declare const createFieldQualifier: (exploreName: string, baseTable: stri
|
|
|
24
25
|
export type ExploreBaseTableResolver = (exploreName: string) => Promise<string | null>;
|
|
25
26
|
export type ApiTransportOptions = {
|
|
26
27
|
resolveExploreBaseTable: ExploreBaseTableResolver | null;
|
|
28
|
+
/** Omitted/null = the transport reports the capability unavailable. */
|
|
29
|
+
listSpaceCharts?: SpaceChartsResolver | null;
|
|
27
30
|
};
|
|
28
31
|
export declare const createApiExploreBaseTableResolver: (fetchFn: FetchAdapter, projectUuid: string) => ExploreBaseTableResolver;
|
|
29
32
|
/** Asks the host (app bridge or CLI preview proxy) via its virtual route. */
|
|
30
33
|
export declare const createHostExploreBaseTableResolver: (fetchFn: FetchAdapter) => ExploreBaseTableResolver;
|
|
34
|
+
/** Lists the saved charts the viewer can see in a space (slug or uuid). */
|
|
35
|
+
export type SpaceChartsResolver = (space: string) => Promise<SpaceChartsResult>;
|
|
36
|
+
/**
|
|
37
|
+
* Thrown when the transport has the capability but the running host does not
|
|
38
|
+
* (a current SDK bundle on an older Lightdash). `useSpaceCharts` maps it to
|
|
39
|
+
* `available: false` instead of an error, so galleries hide cleanly.
|
|
40
|
+
*/
|
|
41
|
+
export declare class SpaceChartsUnsupportedError extends Error {
|
|
42
|
+
constructor();
|
|
43
|
+
}
|
|
44
|
+
/** Normalize an untrusted body into a `SpaceChartsResult`. */
|
|
45
|
+
export declare const normalizeSpaceChartsResult: (value: unknown) => SpaceChartsResult;
|
|
46
|
+
/** Asks the host (app bridge or CLI preview proxy) via its virtual route. */
|
|
47
|
+
export declare const createHostSpaceChartsResolver: (fetchFn: FetchAdapter) => SpaceChartsResolver;
|
|
48
|
+
/**
|
|
49
|
+
* Resolves against the real API (local dev with a PAT): the spaces list to
|
|
50
|
+
* match the slug/uuid, then the space detail for its chart summaries — the
|
|
51
|
+
* same two calls the host makes when answering the virtual route.
|
|
52
|
+
*/
|
|
53
|
+
export declare const createApiSpaceChartsResolver: (fetchFn: FetchAdapter, projectUuid: string) => SpaceChartsResolver;
|
|
31
54
|
/**
|
|
32
55
|
* Thrown for a non-2xx response. `status` lets hosts tell a recoverable
|
|
33
56
|
* 429 (rate limited, retry later) from a real failure.
|
package/dist/apiTransport.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* → returns results when status is 'ready'
|
|
9
9
|
*/
|
|
10
10
|
import { AppStateError } from './appState';
|
|
11
|
-
import { VIZ_DRILL_DOWN_PATH, VIZ_POINT_MENU_PATH, VIZ_UNDERLYING_DATA_OPEN_PATH, VIZ_UNDERLYING_DATA_PATH, VIZ_SUBTOTALS_PATH, EXPLORE_BASE_TABLE_PATH, } from './types';
|
|
11
|
+
import { VIZ_DRILL_DOWN_PATH, VIZ_POINT_MENU_PATH, VIZ_UNDERLYING_DATA_OPEN_PATH, VIZ_UNDERLYING_DATA_PATH, VIZ_SUBTOTALS_PATH, EXPLORE_BASE_TABLE_PATH, SPACE_CHARTS_PATH, } from './types';
|
|
12
12
|
// Mirrors the explorer's `useInfiniteQueryResults` polling rhythm so the
|
|
13
13
|
// SDK behaves like a normal Lightdash chart: 500-row pages, exponential
|
|
14
14
|
// backoff starting at 250ms, capped at 1000ms.
|
|
@@ -98,6 +98,92 @@ export const createHostExploreBaseTableResolver = (fetchFn) => async (exploreNam
|
|
|
98
98
|
const result = await fetchFn('POST', EXPLORE_BASE_TABLE_PATH, body);
|
|
99
99
|
return toBaseTable(result?.baseTable);
|
|
100
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* Thrown when the transport has the capability but the running host does not
|
|
103
|
+
* (a current SDK bundle on an older Lightdash). `useSpaceCharts` maps it to
|
|
104
|
+
* `available: false` instead of an error, so galleries hide cleanly.
|
|
105
|
+
*/
|
|
106
|
+
export class SpaceChartsUnsupportedError extends Error {
|
|
107
|
+
constructor() {
|
|
108
|
+
super('Space charts listing is not supported by this Lightdash host');
|
|
109
|
+
this.name = 'SpaceChartsUnsupportedError';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const EMPTY_SPACE_CHARTS = { space: null, charts: [] };
|
|
113
|
+
const toNullableString = (value) => typeof value === 'string' && value.length > 0 ? value : null;
|
|
114
|
+
/** Normalize an untrusted body into a `SpaceChartsResult`. */
|
|
115
|
+
export const normalizeSpaceChartsResult = (value) => {
|
|
116
|
+
if (typeof value !== 'object' || value === null)
|
|
117
|
+
return EMPTY_SPACE_CHARTS;
|
|
118
|
+
const { space, charts } = value;
|
|
119
|
+
let spaceInfo = null;
|
|
120
|
+
if (typeof space === 'object' && space !== null) {
|
|
121
|
+
const { uuid, name, slug } = space;
|
|
122
|
+
if (typeof uuid === 'string' &&
|
|
123
|
+
typeof name === 'string' &&
|
|
124
|
+
typeof slug === 'string') {
|
|
125
|
+
spaceInfo = { uuid, name, slug };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const chartList = (Array.isArray(charts) ? charts : []).flatMap((chart) => {
|
|
129
|
+
if (typeof chart !== 'object' || chart === null)
|
|
130
|
+
return [];
|
|
131
|
+
const entry = chart;
|
|
132
|
+
if (typeof entry.uuid !== 'string' || typeof entry.name !== 'string') {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
uuid: entry.uuid,
|
|
138
|
+
name: entry.name,
|
|
139
|
+
description: toNullableString(entry.description),
|
|
140
|
+
chartKind: toNullableString(entry.chartKind),
|
|
141
|
+
slug: toNullableString(entry.slug),
|
|
142
|
+
updatedAt: toNullableString(entry.updatedAt),
|
|
143
|
+
},
|
|
144
|
+
];
|
|
145
|
+
});
|
|
146
|
+
return { space: spaceInfo, charts: chartList };
|
|
147
|
+
};
|
|
148
|
+
/** Asks the host (app bridge or CLI preview proxy) via its virtual route. */
|
|
149
|
+
export const createHostSpaceChartsResolver = (fetchFn) => async (space) => {
|
|
150
|
+
const body = { space };
|
|
151
|
+
const result = await fetchFn('POST', SPACE_CHARTS_PATH, body);
|
|
152
|
+
return normalizeSpaceChartsResult(result);
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Resolves against the real API (local dev with a PAT): the spaces list to
|
|
156
|
+
* match the slug/uuid, then the space detail for its chart summaries — the
|
|
157
|
+
* same two calls the host makes when answering the virtual route.
|
|
158
|
+
*/
|
|
159
|
+
export const createApiSpaceChartsResolver = (fetchFn, projectUuid) => async (space) => {
|
|
160
|
+
try {
|
|
161
|
+
const spaces = await fetchFn('GET', `/api/v1/projects/${projectUuid}/spaces`);
|
|
162
|
+
const match = (Array.isArray(spaces) ? spaces : []).find((entry) => {
|
|
163
|
+
if (typeof entry !== 'object' || entry === null)
|
|
164
|
+
return false;
|
|
165
|
+
const { uuid, slug } = entry;
|
|
166
|
+
return uuid === space || slug === space;
|
|
167
|
+
});
|
|
168
|
+
if (typeof match?.uuid !== 'string')
|
|
169
|
+
return EMPTY_SPACE_CHARTS;
|
|
170
|
+
const detail = await fetchFn('GET', `/api/v1/projects/${projectUuid}/spaces/${match.uuid}`);
|
|
171
|
+
if (typeof detail !== 'object' || detail === null)
|
|
172
|
+
return EMPTY_SPACE_CHARTS;
|
|
173
|
+
return normalizeSpaceChartsResult({
|
|
174
|
+
space: detail,
|
|
175
|
+
charts: detail.queries,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
catch (e) {
|
|
179
|
+
// Same face as the host route: no access answers like not found.
|
|
180
|
+
if (e instanceof LightdashApiError &&
|
|
181
|
+
(e.status === 403 || e.status === 404)) {
|
|
182
|
+
return EMPTY_SPACE_CHARTS;
|
|
183
|
+
}
|
|
184
|
+
throw e;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
101
187
|
function buildUnderlyingDataFilters(query, row, qualify) {
|
|
102
188
|
const sourceFilters = buildApiFilters(query.filters.map((f) => ({
|
|
103
189
|
...f,
|
|
@@ -564,14 +650,21 @@ export function createApiTransport(config, adapter, options) {
|
|
|
564
650
|
// endpoint, so only the default adapter looks base tables up by default.
|
|
565
651
|
// Behind a dev proxy (CLI preview) the proxy answers the host route.
|
|
566
652
|
let defaultResolver = null;
|
|
653
|
+
let defaultSpaceCharts = null;
|
|
567
654
|
if (adapter === undefined) {
|
|
568
655
|
defaultResolver = config.useProxy
|
|
569
656
|
? createHostExploreBaseTableResolver(fetchFn)
|
|
570
657
|
: createApiExploreBaseTableResolver(fetchFn, config.projectUuid);
|
|
658
|
+
defaultSpaceCharts = config.useProxy
|
|
659
|
+
? createHostSpaceChartsResolver(fetchFn)
|
|
660
|
+
: createApiSpaceChartsResolver(fetchFn, config.projectUuid);
|
|
571
661
|
}
|
|
572
662
|
const resolveExploreBaseTable = options === undefined
|
|
573
663
|
? defaultResolver
|
|
574
664
|
: options.resolveExploreBaseTable;
|
|
665
|
+
const listSpaceCharts = options === undefined
|
|
666
|
+
? defaultSpaceCharts
|
|
667
|
+
: (options.listSpaceCharts ?? null);
|
|
575
668
|
const baseTableByExplore = new Map();
|
|
576
669
|
const getFieldQualifier = async (query) => {
|
|
577
670
|
const { exploreName } = query;
|
|
@@ -879,5 +972,8 @@ export function createApiTransport(config, adapter, options) {
|
|
|
879
972
|
async openVizPointMenu(intent) {
|
|
880
973
|
return fetchFn('POST', VIZ_POINT_MENU_PATH, intent);
|
|
881
974
|
},
|
|
975
|
+
// Left absent (not a thrower) when no resolver exists, so
|
|
976
|
+
// `useSpaceCharts().available` honestly reports the capability.
|
|
977
|
+
...(listSpaceCharts ? { listSpaceCharts } : {}),
|
|
882
978
|
};
|
|
883
979
|
}
|
package/dist/features.js
CHANGED
|
@@ -225,6 +225,13 @@ export const SDK_FEATURES = [
|
|
|
225
225
|
description: "Result columns carry the semantic layer's url link patterns, so values with configured links render as links, matching dashboards.",
|
|
226
226
|
wiring: 'In cell renderers (tables, underlying-data dialogs), call resolveColumnUrls({ column, row, columns, format }) and render the value as a link when it returns entries, styled visibly as a link.',
|
|
227
227
|
},
|
|
228
|
+
{
|
|
229
|
+
key: 'space-charts',
|
|
230
|
+
appliesTo: ['data_app'],
|
|
231
|
+
label: 'List charts in a space',
|
|
232
|
+
description: 'List the saved charts in a Lightdash space at runtime, so chart galleries update as charts are saved — limited to spaces the viewer can already access.',
|
|
233
|
+
wiring: "Call useSpaceCharts('<space slug or uuid>') where the app needs the list, render from its charts array (uuid, name, description, chartKind), and pass chart.uuid to savedChart(...) to show one. Check available first — it stays false on hosts without the capability.",
|
|
234
|
+
},
|
|
228
235
|
];
|
|
229
236
|
export const SDK_FEATURE_KEYS = SDK_FEATURES.map((f) => f.key);
|
|
230
237
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const SDK_VERSION = "3.
|
|
1
|
+
export declare const SDK_VERSION = "3.98.1";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/generateSdkVersion.mjs (prebuild) — do not edit.
|
|
2
|
-
export const SDK_VERSION = '3.
|
|
2
|
+
export const SDK_VERSION = '3.98.1';
|
package/dist/hostContext.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Identity the host rides on its `lightdash:sdk:ready`
|
|
3
|
-
* uuid this bundle is deployed as
|
|
4
|
-
*
|
|
2
|
+
* Identity and capabilities the host rides on its `lightdash:sdk:ready`
|
|
3
|
+
* handshake: the app uuid this bundle is deployed as, and which negotiated
|
|
4
|
+
* host features exist (`spaceCharts`). Absent for older hosts and for
|
|
5
|
+
* bundles run outside Lightdash (CLI preview), where host-negotiated
|
|
6
|
+
* features stay off.
|
|
5
7
|
*/
|
|
6
8
|
type HostContext = {
|
|
7
9
|
appUuid: string | null;
|
|
10
|
+
/** Host answers the space-charts virtual route. False for older hosts. */
|
|
11
|
+
spaceCharts: boolean;
|
|
12
|
+
/** A ready handshake has been seen at all. */
|
|
13
|
+
ready: boolean;
|
|
8
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Resolves once a ready handshake has been seen, or after `timeoutMs` —
|
|
17
|
+
* mirrors the fetch adapter's readiness gate so capability checks don't
|
|
18
|
+
* hang on hosts that never handshake.
|
|
19
|
+
*/
|
|
20
|
+
export declare function waitForHostContext(timeoutMs: number): Promise<HostContext>;
|
|
9
21
|
/** Called by `createPostMessageTransport()`; one listener per bundle. */
|
|
10
22
|
export declare function mountHostContext(targetWindow: Window): () => void;
|
|
11
23
|
export declare function useHostAppUuid(): string | null;
|
package/dist/hostContext.js
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Identity the host rides on its `lightdash:sdk:ready`
|
|
3
|
-
* uuid this bundle is deployed as
|
|
4
|
-
*
|
|
2
|
+
* Identity and capabilities the host rides on its `lightdash:sdk:ready`
|
|
3
|
+
* handshake: the app uuid this bundle is deployed as, and which negotiated
|
|
4
|
+
* host features exist (`spaceCharts`). Absent for older hosts and for
|
|
5
|
+
* bundles run outside Lightdash (CLI preview), where host-negotiated
|
|
6
|
+
* features stay off.
|
|
5
7
|
*/
|
|
6
8
|
import { useSyncExternalStore } from 'react';
|
|
7
9
|
const SDK_READY_MESSAGE = 'lightdash:sdk:ready';
|
|
8
|
-
const EMPTY_CONTEXT = {
|
|
10
|
+
const EMPTY_CONTEXT = {
|
|
11
|
+
appUuid: null,
|
|
12
|
+
spaceCharts: false,
|
|
13
|
+
ready: false,
|
|
14
|
+
};
|
|
9
15
|
let value = EMPTY_CONTEXT;
|
|
10
16
|
const listeners = new Set();
|
|
17
|
+
let readyResolvers = [];
|
|
11
18
|
const set = (next) => {
|
|
12
|
-
if (next.appUuid === value.appUuid
|
|
19
|
+
if (next.appUuid === value.appUuid &&
|
|
20
|
+
next.spaceCharts === value.spaceCharts &&
|
|
21
|
+
next.ready === value.ready)
|
|
13
22
|
return;
|
|
14
23
|
value = next;
|
|
15
24
|
listeners.forEach((listener) => listener());
|
|
25
|
+
if (next.ready) {
|
|
26
|
+
readyResolvers.forEach((resolve) => resolve());
|
|
27
|
+
readyResolvers = [];
|
|
28
|
+
}
|
|
16
29
|
};
|
|
17
30
|
const subscribe = (listener) => {
|
|
18
31
|
listeners.add(listener);
|
|
@@ -20,6 +33,22 @@ const subscribe = (listener) => {
|
|
|
20
33
|
listeners.delete(listener);
|
|
21
34
|
};
|
|
22
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Resolves once a ready handshake has been seen, or after `timeoutMs` —
|
|
38
|
+
* mirrors the fetch adapter's readiness gate so capability checks don't
|
|
39
|
+
* hang on hosts that never handshake.
|
|
40
|
+
*/
|
|
41
|
+
export function waitForHostContext(timeoutMs) {
|
|
42
|
+
if (value.ready)
|
|
43
|
+
return Promise.resolve(value);
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
const timer = setTimeout(() => resolve(value), timeoutMs);
|
|
46
|
+
readyResolvers.push(() => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
resolve(value);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
23
52
|
let activeCleanup = null;
|
|
24
53
|
/** Called by `createPostMessageTransport()`; one listener per bundle. */
|
|
25
54
|
export function mountHostContext(targetWindow) {
|
|
@@ -33,6 +62,8 @@ export function mountHostContext(targetWindow) {
|
|
|
33
62
|
return;
|
|
34
63
|
set({
|
|
35
64
|
appUuid: typeof data.appUuid === 'string' ? data.appUuid : null,
|
|
65
|
+
spaceCharts: data.spaceCharts === true,
|
|
66
|
+
ready: true,
|
|
36
67
|
});
|
|
37
68
|
};
|
|
38
69
|
activeCleanup?.();
|
|
@@ -54,4 +85,5 @@ export function peekHostContext() {
|
|
|
54
85
|
}
|
|
55
86
|
export function resetHostContext() {
|
|
56
87
|
value = EMPTY_CONTEXT;
|
|
88
|
+
readyResolvers = [];
|
|
57
89
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { drillDown } from './drillDown';
|
|
|
4
4
|
export { createClient, LightdashClient } from './client';
|
|
5
5
|
export { useLightdash } from './useLightdash';
|
|
6
6
|
export { LightdashProvider, useLightdashClient } from './LightdashProvider';
|
|
7
|
-
export { createApiTransport, LightdashApiError, type FetchAdapter, } from './apiTransport';
|
|
7
|
+
export { createApiTransport, LightdashApiError, SpaceChartsUnsupportedError, type FetchAdapter, } from './apiTransport';
|
|
8
8
|
export { createPostMessageTransport } from './postMessageTransport';
|
|
9
9
|
export { resolveColumnUrls, type ResolvedColumnUrl } from './columnUrls';
|
|
10
10
|
export { SDK_FEATURES, SDK_FEATURE_KEYS, SDK_FIXES, SDK_FIX_KEYS, SDK_FEATURE_TARGETS, SDK_MANIFEST_MESSAGE_TYPE, type SdkFeature, type SdkFeatureTarget, type SdkFix, type SdkManifestMessage, } from './features';
|
|
@@ -28,6 +28,9 @@ export type { AppStateAuthor, AppStateErrorCode, AppStateErrorPayload, AppStateH
|
|
|
28
28
|
export type { SdkAppStateOp, SdkAppStateRequest, SdkAppStateResponse, } from './postMessageTransport';
|
|
29
29
|
export { useAiPrompt } from './aiPrompt';
|
|
30
30
|
export type { AiPrompt, AiPromptOptions, AiPromptSource } from './aiPrompt';
|
|
31
|
+
export { useSpaceCharts } from './spaceCharts';
|
|
32
|
+
export type { SpaceCharts } from './spaceCharts';
|
|
33
|
+
export type { SpaceChartsResult, SpaceChartSummary } from './types';
|
|
31
34
|
export { useInsights } from './insights';
|
|
32
35
|
export type { Insight, InsightInvestigation, InsightSeverity, InsightsPayload, InsightsStatus, QueryInsights, ViewInsights, ViewInsightsOptions, } from './insights';
|
|
33
36
|
export { createGradientColorScale } from './gradientColorScale';
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ export { useLightdash } from './useLightdash';
|
|
|
10
10
|
// Provider
|
|
11
11
|
export { LightdashProvider, useLightdashClient } from './LightdashProvider';
|
|
12
12
|
// Transports
|
|
13
|
-
export { createApiTransport, LightdashApiError, } from './apiTransport';
|
|
13
|
+
export { createApiTransport, LightdashApiError, SpaceChartsUnsupportedError, } from './apiTransport';
|
|
14
14
|
export { createPostMessageTransport } from './postMessageTransport';
|
|
15
15
|
export { resolveColumnUrls } from './columnUrls';
|
|
16
16
|
// Feature manifest (also exported via the "./features" subpath for hosts)
|
|
@@ -32,6 +32,8 @@ export { useUrlState } from './urlState';
|
|
|
32
32
|
export { AppStateError, useAppState, useSharedAppState, matchesDefaultShape, } from './appState';
|
|
33
33
|
// Free-form AI prompts over loaded results (data apps)
|
|
34
34
|
export { useAiPrompt } from './aiPrompt';
|
|
35
|
+
// List the saved charts the viewer can see in a space (data apps)
|
|
36
|
+
export { useSpaceCharts } from './spaceCharts';
|
|
35
37
|
// AI analysis pushed by the host (data apps)
|
|
36
38
|
export { useInsights } from './insights';
|
|
37
39
|
export { createGradientColorScale } from './gradientColorScale';
|
|
@@ -8,10 +8,10 @@
|
|
|
8
8
|
* response back. All query logic (field qualification, polling, result
|
|
9
9
|
* mapping) stays in the SDK's apiTransport.
|
|
10
10
|
*/
|
|
11
|
-
import { createApiTransport, createHostExploreBaseTableResolver, } from './apiTransport';
|
|
11
|
+
import { createApiTransport, createHostExploreBaseTableResolver, createHostSpaceChartsResolver, SpaceChartsUnsupportedError, } from './apiTransport';
|
|
12
12
|
import { AppStateError } from './appState';
|
|
13
13
|
import { mountDeliveryRender } from './deliveryRender';
|
|
14
|
-
import { mountHostContext } from './hostContext';
|
|
14
|
+
import { mountHostContext, waitForHostContext } from './hostContext';
|
|
15
15
|
import { announceSdkManifest } from './manifest';
|
|
16
16
|
import { mountRuntimeErrorObserver } from './runtimeErrors';
|
|
17
17
|
const DEFAULT_TIMEOUT_MS = 120000;
|
|
@@ -280,6 +280,17 @@ export function createPostMessageTransport(config) {
|
|
|
280
280
|
});
|
|
281
281
|
const httpTransport = createApiTransport({ apiKey: '', baseUrl: '', projectUuid: config.projectUuid }, adapter, {
|
|
282
282
|
resolveExploreBaseTable: createHostExploreBaseTableResolver(adapter),
|
|
283
|
+
// Negotiated on the ready handshake: only hosts that announce
|
|
284
|
+
// `spaceCharts: true` answer the virtual route. On an older host
|
|
285
|
+
// the typed rejection makes `useSpaceCharts` report
|
|
286
|
+
// `available: false` instead of surfacing a blocked-route error.
|
|
287
|
+
listSpaceCharts: async (space) => {
|
|
288
|
+
const host = await waitForHostContext(READY_TIMEOUT_MS);
|
|
289
|
+
if (!host.spaceCharts) {
|
|
290
|
+
throw new SpaceChartsUnsupportedError();
|
|
291
|
+
}
|
|
292
|
+
return createHostSpaceChartsResolver(adapter)(space);
|
|
293
|
+
},
|
|
283
294
|
});
|
|
284
295
|
const externalFetch = createPostMessageExternalFetch({
|
|
285
296
|
targetWindow: config.targetWindow,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useSpaceCharts('<space slug or uuid>')` — the saved charts the viewer can
|
|
3
|
+
* see in a space, resolved at runtime with the viewer's own credentials. The
|
|
4
|
+
* list reflects existing space permissions: a space the viewer can't access
|
|
5
|
+
* answers exactly like one that doesn't exist (`space: null`, no charts).
|
|
6
|
+
*
|
|
7
|
+
* Feed an entry's `uuid` into `savedChart(...)` to render it.
|
|
8
|
+
*/
|
|
9
|
+
import { type SpaceChartSummary } from './types';
|
|
10
|
+
export type SpaceCharts = {
|
|
11
|
+
/** False on a transport or host without the capability. On an older
|
|
12
|
+
* host this starts true and settles to false after the first lookup. */
|
|
13
|
+
available: boolean;
|
|
14
|
+
/** Null while loading, on error, or when the space isn't accessible. */
|
|
15
|
+
space: {
|
|
16
|
+
uuid: string;
|
|
17
|
+
name: string;
|
|
18
|
+
slug: string;
|
|
19
|
+
} | null;
|
|
20
|
+
charts: SpaceChartSummary[];
|
|
21
|
+
loading: boolean;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
/** Re-fetch the list, e.g. after the viewer saved a chart elsewhere. */
|
|
24
|
+
refresh: () => void;
|
|
25
|
+
};
|
|
26
|
+
export declare function useSpaceCharts(space: string): SpaceCharts;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `useSpaceCharts('<space slug or uuid>')` — the saved charts the viewer can
|
|
3
|
+
* see in a space, resolved at runtime with the viewer's own credentials. The
|
|
4
|
+
* list reflects existing space permissions: a space the viewer can't access
|
|
5
|
+
* answers exactly like one that doesn't exist (`space: null`, no charts).
|
|
6
|
+
*
|
|
7
|
+
* Feed an entry's `uuid` into `savedChart(...)` to render it.
|
|
8
|
+
*/
|
|
9
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
10
|
+
import { useTransport } from './LightdashProvider';
|
|
11
|
+
const EMPTY_RESULT = { space: null, charts: [] };
|
|
12
|
+
// Instanceof is unreliable across the bundle boundary; match by name.
|
|
13
|
+
const isUnsupportedError = (e) => e instanceof Error &&
|
|
14
|
+
(e.name === 'SpaceChartsUnsupportedError' ||
|
|
15
|
+
// Older mediators reject the unknown route instead of negotiating:
|
|
16
|
+
// the bridge with `Blocked: POST /__sdk/space-charts`, the CLI
|
|
17
|
+
// preview proxy with "... is not a data-app SDK route ...".
|
|
18
|
+
/\/__sdk\/space-charts/.test(e.message));
|
|
19
|
+
export function useSpaceCharts(space) {
|
|
20
|
+
const transport = useTransport();
|
|
21
|
+
const listSpaceCharts = transport.listSpaceCharts;
|
|
22
|
+
// Hosts that predate the capability reject the first lookup with a
|
|
23
|
+
// typed/blocked error; that settles availability to false for good.
|
|
24
|
+
const [hostUnsupported, setHostUnsupported] = useState(false);
|
|
25
|
+
const available = listSpaceCharts !== undefined && !hostUnsupported;
|
|
26
|
+
const [result, setResult] = useState(EMPTY_RESULT);
|
|
27
|
+
const [loading, setLoading] = useState(available);
|
|
28
|
+
const [error, setError] = useState(null);
|
|
29
|
+
const [fetchCount, setFetchCount] = useState(0);
|
|
30
|
+
// Drops responses from superseded fetches (space change, refresh, unmount).
|
|
31
|
+
const fetchSeq = useRef(0);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!listSpaceCharts || hostUnsupported || space.trim().length === 0) {
|
|
34
|
+
setResult(EMPTY_RESULT);
|
|
35
|
+
setLoading(false);
|
|
36
|
+
setError(space.trim().length === 0 && available
|
|
37
|
+
? new Error('useSpaceCharts: pass a space slug or uuid')
|
|
38
|
+
: null);
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
fetchSeq.current += 1;
|
|
42
|
+
const seq = fetchSeq.current;
|
|
43
|
+
setLoading(true);
|
|
44
|
+
setError(null);
|
|
45
|
+
listSpaceCharts(space.trim())
|
|
46
|
+
.then((value) => {
|
|
47
|
+
if (fetchSeq.current !== seq)
|
|
48
|
+
return;
|
|
49
|
+
setResult(value);
|
|
50
|
+
setLoading(false);
|
|
51
|
+
})
|
|
52
|
+
.catch((e) => {
|
|
53
|
+
if (fetchSeq.current !== seq)
|
|
54
|
+
return;
|
|
55
|
+
setResult(EMPTY_RESULT);
|
|
56
|
+
if (isUnsupportedError(e)) {
|
|
57
|
+
// Not an error state: the host simply doesn't have the
|
|
58
|
+
// feature. `available` flips to false and stays there.
|
|
59
|
+
setHostUnsupported(true);
|
|
60
|
+
setError(null);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
setError(e instanceof Error ? e : new Error(String(e)));
|
|
64
|
+
}
|
|
65
|
+
setLoading(false);
|
|
66
|
+
});
|
|
67
|
+
return () => {
|
|
68
|
+
// Invalidate in-flight responses for the unmount/re-run.
|
|
69
|
+
if (fetchSeq.current === seq)
|
|
70
|
+
fetchSeq.current += 1;
|
|
71
|
+
};
|
|
72
|
+
}, [listSpaceCharts, hostUnsupported, space, fetchCount, available]);
|
|
73
|
+
const refresh = useCallback(() => setFetchCount((n) => n + 1), []);
|
|
74
|
+
return {
|
|
75
|
+
available,
|
|
76
|
+
space: result.space,
|
|
77
|
+
charts: result.charts,
|
|
78
|
+
loading,
|
|
79
|
+
error,
|
|
80
|
+
refresh,
|
|
81
|
+
};
|
|
82
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -299,6 +299,36 @@ export type ExploreBaseTableRequest = {
|
|
|
299
299
|
export type ExploreBaseTableResult = {
|
|
300
300
|
baseTable: string | null;
|
|
301
301
|
};
|
|
302
|
+
/**
|
|
303
|
+
* Host-answered virtual route returning the saved charts in a space.
|
|
304
|
+
* Duplicated from `@lightdash/common` (`APP_SDK_SPACE_CHARTS_PATH`) — this
|
|
305
|
+
* package must not depend on common. The bridge and the CLI preview proxy
|
|
306
|
+
* answer it with the viewer's own credentials; the backend has no such route.
|
|
307
|
+
*/
|
|
308
|
+
export declare const SPACE_CHARTS_PATH = "/__sdk/space-charts";
|
|
309
|
+
export type SpaceChartsRequest = {
|
|
310
|
+
space: string;
|
|
311
|
+
};
|
|
312
|
+
/** One saved chart in a space, as listed by `useSpaceCharts`. */
|
|
313
|
+
export type SpaceChartSummary = {
|
|
314
|
+
uuid: string;
|
|
315
|
+
name: string;
|
|
316
|
+
description: string | null;
|
|
317
|
+
/** e.g. `line`, `vertical_bar`, `table` — null on older charts. */
|
|
318
|
+
chartKind: string | null;
|
|
319
|
+
slug: string | null;
|
|
320
|
+
/** ISO timestamp of the chart's last save. */
|
|
321
|
+
updatedAt: string | null;
|
|
322
|
+
};
|
|
323
|
+
export type SpaceChartsResult = {
|
|
324
|
+
/** Null when the space doesn't exist or the viewer can't access it. */
|
|
325
|
+
space: {
|
|
326
|
+
uuid: string;
|
|
327
|
+
name: string;
|
|
328
|
+
slug: string;
|
|
329
|
+
} | null;
|
|
330
|
+
charts: SpaceChartSummary[];
|
|
331
|
+
};
|
|
302
332
|
export type LightdashClientConfig = {
|
|
303
333
|
/** Lightdash instance URL */
|
|
304
334
|
baseUrl: string;
|
|
@@ -474,4 +504,10 @@ export type Transport = {
|
|
|
474
504
|
* `useAiPrompt().available` is false when absent.
|
|
475
505
|
*/
|
|
476
506
|
aiPrompt?: (request: AiPromptRequest) => Promise<AiPromptResult>;
|
|
507
|
+
/**
|
|
508
|
+
* List the saved charts the viewer can see in a space (by slug or uuid).
|
|
509
|
+
* Optional so custom transports predating the capability stay valid —
|
|
510
|
+
* `useSpaceCharts().available` is false when absent.
|
|
511
|
+
*/
|
|
512
|
+
listSpaceCharts?: (space: string) => Promise<SpaceChartsResult>;
|
|
477
513
|
};
|
package/dist/types.js
CHANGED
|
@@ -41,3 +41,10 @@ export const VIZ_POINT_MENU_PATH = '/__sdk/viz/point-menu';
|
|
|
41
41
|
* the backend has no such route.
|
|
42
42
|
*/
|
|
43
43
|
export const EXPLORE_BASE_TABLE_PATH = '/__sdk/explore-base-table';
|
|
44
|
+
/**
|
|
45
|
+
* Host-answered virtual route returning the saved charts in a space.
|
|
46
|
+
* Duplicated from `@lightdash/common` (`APP_SDK_SPACE_CHARTS_PATH`) — this
|
|
47
|
+
* package must not depend on common. The bridge and the CLI preview proxy
|
|
48
|
+
* answer it with the viewer's own credentials; the backend has no such route.
|
|
49
|
+
*/
|
|
50
|
+
export const SPACE_CHARTS_PATH = '/__sdk/space-charts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightdash/query-sdk",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.98.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "SDK for building custom data apps against the Lightdash semantic layer",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@lightdash/common": "3.
|
|
29
|
+
"@lightdash/common": "3.98.1",
|
|
30
30
|
"@types/node": "22.13.1",
|
|
31
31
|
"@types/react": "19.3.0",
|
|
32
32
|
"@types/react-dom": "19.3.0",
|