@funnelsgrove/runtime 0.5.0 → 0.6.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.
|
@@ -10,6 +10,7 @@ type FunnelRuntimeConfigBoundaryProps = {
|
|
|
10
10
|
funnelVersionId?: string;
|
|
11
11
|
children: ReactNode;
|
|
12
12
|
loadingFallback?: ReactNode;
|
|
13
|
+
errorFallback?: ReactNode;
|
|
13
14
|
disabled?: boolean;
|
|
14
15
|
};
|
|
15
16
|
export declare function FunnelRuntimeConfigBoundary({ disabled, ...props }: FunnelRuntimeConfigBoundaryProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -24,10 +24,10 @@ const toError = (value) => (value instanceof Error ? value : new Error('Unable t
|
|
|
24
24
|
* snapshot is deeply frozen and stays fixed for the lifetime of this boundary,
|
|
25
25
|
* so one visitor can never switch experiments or prices mid-session.
|
|
26
26
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
27
|
+
* A published funnel never falls back to generated pricing or experiment
|
|
28
|
+
* config: the version-bound artifact is its single source of truth.
|
|
29
29
|
*/
|
|
30
|
-
function ActiveFunnelRuntimeConfigBoundary({ funnelId, funnelVersionId, children, loadingFallback = null, }) {
|
|
30
|
+
function ActiveFunnelRuntimeConfigBoundary({ funnelId, funnelVersionId, children, loadingFallback = null, errorFallback = null, }) {
|
|
31
31
|
const [state, setState] = useState({
|
|
32
32
|
status: 'loading',
|
|
33
33
|
config: null,
|
|
@@ -35,7 +35,7 @@ function ActiveFunnelRuntimeConfigBoundary({ funnelId, funnelVersionId, children
|
|
|
35
35
|
});
|
|
36
36
|
useEffect(() => {
|
|
37
37
|
let active = true;
|
|
38
|
-
loadFunnelRuntimeConfig(funnelId)
|
|
38
|
+
loadFunnelRuntimeConfig(funnelId, { versionId: funnelVersionId })
|
|
39
39
|
.then((published) => resolvePublishedFunnelRuntimeConfig(published, {
|
|
40
40
|
expectedFunnelVersionId: funnelVersionId,
|
|
41
41
|
}))
|
|
@@ -57,6 +57,9 @@ function ActiveFunnelRuntimeConfigBoundary({ funnelId, funnelVersionId, children
|
|
|
57
57
|
if (state.status === 'loading') {
|
|
58
58
|
return loadingFallback;
|
|
59
59
|
}
|
|
60
|
+
if (state.status === 'fallback') {
|
|
61
|
+
return errorFallback;
|
|
62
|
+
}
|
|
60
63
|
return (_jsx(FunnelRuntimeConfigContext.Provider, { value: contextValue, children: children }));
|
|
61
64
|
}
|
|
62
65
|
export function FunnelRuntimeConfigBoundary(_a) {
|
|
@@ -31,6 +31,7 @@ export declare const resolvePublishedFunnelRuntimeConfig: (published: PublishedF
|
|
|
31
31
|
export declare const loadFunnelRuntimeConfig: (funnelId: string, options?: {
|
|
32
32
|
fetcher?: RuntimeConfigFetcher;
|
|
33
33
|
basePath?: string;
|
|
34
|
+
versionId?: string;
|
|
34
35
|
}) => Promise<PublishedFunnelRuntimeConfig>;
|
|
35
36
|
export declare const clearFunnelRuntimeConfigInflight: () => void;
|
|
36
37
|
export {};
|
|
@@ -244,17 +244,23 @@ const parsePublishedRuntimeConfig = (value) => {
|
|
|
244
244
|
return value;
|
|
245
245
|
};
|
|
246
246
|
export const loadFunnelRuntimeConfig = (funnelId, options = {}) => {
|
|
247
|
+
var _a;
|
|
247
248
|
const normalizedFunnelId = funnelId.trim();
|
|
248
249
|
if (!normalizedFunnelId) {
|
|
249
250
|
return Promise.reject(new Error('funnelId is required'));
|
|
250
251
|
}
|
|
251
|
-
const
|
|
252
|
+
const versionId = ((_a = options.versionId) === null || _a === void 0 ? void 0 : _a.trim()) || '';
|
|
253
|
+
if (!versionId) {
|
|
254
|
+
return Promise.reject(new Error('versionId is required'));
|
|
255
|
+
}
|
|
256
|
+
const cacheKey = `${normalizedFunnelId}:${versionId}`;
|
|
257
|
+
const cachedInflight = inflightByFunnelId.get(cacheKey);
|
|
252
258
|
if (cachedInflight) {
|
|
253
259
|
return cachedInflight;
|
|
254
260
|
}
|
|
255
261
|
const fetcher = options.fetcher || globalThis.fetch;
|
|
256
262
|
const basePath = (options.basePath || '/api/funnel-config').replace(/\/$/, '');
|
|
257
|
-
const request = fetcher(`${basePath}/${encodeURIComponent(normalizedFunnelId)}`, {
|
|
263
|
+
const request = fetcher(`${basePath}/${encodeURIComponent(normalizedFunnelId)}?versionId=${encodeURIComponent(versionId)}`, {
|
|
258
264
|
headers: { accept: 'application/json' },
|
|
259
265
|
}).then(async (response) => {
|
|
260
266
|
if (!response.ok) {
|
|
@@ -266,9 +272,9 @@ export const loadFunnelRuntimeConfig = (funnelId, options = {}) => {
|
|
|
266
272
|
}
|
|
267
273
|
return published;
|
|
268
274
|
}).finally(() => {
|
|
269
|
-
inflightByFunnelId.delete(
|
|
275
|
+
inflightByFunnelId.delete(cacheKey);
|
|
270
276
|
});
|
|
271
|
-
inflightByFunnelId.set(
|
|
277
|
+
inflightByFunnelId.set(cacheKey, request);
|
|
272
278
|
return request;
|
|
273
279
|
};
|
|
274
280
|
export const clearFunnelRuntimeConfigInflight = () => {
|