@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
- * Missing, invalid, or temporarily unavailable publications fail open to the
28
- * generated build-time config by exposing `config: null`.
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 cachedInflight = inflightByFunnelId.get(normalizedFunnelId);
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(normalizedFunnelId);
275
+ inflightByFunnelId.delete(cacheKey);
270
276
  });
271
- inflightByFunnelId.set(normalizedFunnelId, request);
277
+ inflightByFunnelId.set(cacheKey, request);
272
278
  return request;
273
279
  };
274
280
  export const clearFunnelRuntimeConfigInflight = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funnelsgrove/runtime",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/The-Solid-Grove/funnelsgrove.git",