gemi 0.164.0 → 0.165.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.
Files changed (44) hide show
  1. package/dist/{chunk-1kt3p3dg.js → chunk-3q6adj7c.js} +10 -10
  2. package/dist/{chunk-1kt3p3dg.js.map → chunk-3q6adj7c.js.map} +3 -3
  3. package/dist/{chunk-dcnz24yt.js → chunk-7d0n00an.js} +1 -1
  4. package/dist/{chunk-34svz95a.js → chunk-bs0setym.js} +2 -2
  5. package/dist/{chunk-34svz95a.js.map → chunk-bs0setym.js.map} +1 -1
  6. package/dist/chunk-sk22zf9e.js +24 -0
  7. package/dist/{chunk-472qd3v9.js.map → chunk-sk22zf9e.js.map} +4 -4
  8. package/dist/{chunk-mvj8q5a7.js → chunk-w4227psm.js} +1 -1
  9. package/dist/chunks/{RealtimeClient-b6NvkUAQ.js → RealtimeClient-BX_HwEBK.js} +2 -2
  10. package/dist/chunks/{RealtimeClient-b6NvkUAQ.js.map → RealtimeClient-BX_HwEBK.js.map} +1 -1
  11. package/dist/chunks/{ThemeProvider-CkuoNG4Q.js → ThemeProvider-D4mhhONE.js} +78 -22
  12. package/dist/chunks/ThemeProvider-D4mhhONE.js.map +1 -0
  13. package/dist/chunks/{fetchFileRequest-DVWwtyWu.js → fetchFileRequest-C3wMigR3.js} +2 -2
  14. package/dist/chunks/{fetchFileRequest-DVWwtyWu.js.map → fetchFileRequest-C3wMigR3.js.map} +1 -1
  15. package/dist/chunks/{live-CpXfJmRN.js → live-CJOuf1Gu.js} +3 -3
  16. package/dist/chunks/{live-CpXfJmRN.js.map → live-CJOuf1Gu.js.map} +1 -1
  17. package/dist/chunks/{mutationExtras-BNFui6Pt.js → mutationExtras-DpB8MtNs.js} +2 -2
  18. package/dist/chunks/{mutationExtras-BNFui6Pt.js.map → mutationExtras-DpB8MtNs.js.map} +1 -1
  19. package/dist/client/ClientRouter.d.ts.map +1 -1
  20. package/dist/client/ClientRouterContext.d.ts.map +1 -1
  21. package/dist/client/ServerDataProvider.d.ts +6 -0
  22. package/dist/client/ServerDataProvider.d.ts.map +1 -1
  23. package/dist/client/chunkLoadRecovery.d.ts +56 -8
  24. package/dist/client/chunkLoadRecovery.d.ts.map +1 -1
  25. package/dist/client/helpers/loadRoutePayload.d.ts +8 -0
  26. package/dist/client/helpers/loadRoutePayload.d.ts.map +1 -1
  27. package/dist/client/index.d.ts +1 -1
  28. package/dist/client/index.d.ts.map +1 -1
  29. package/dist/client/index.js +23 -9
  30. package/dist/client/index.js.map +1 -1
  31. package/dist/facades/index.js +1 -1
  32. package/dist/http/index.js +1 -1
  33. package/dist/i18n/index.js +1 -1
  34. package/dist/kernel/index.js +1 -1
  35. package/dist/server/httpProd.d.ts.map +1 -1
  36. package/dist/server/index.js +1 -1
  37. package/dist/services/index.js +1 -1
  38. package/dist/services/router/ViewRouteDispatcher.d.ts.map +1 -1
  39. package/dist/testing/index.js +2 -2
  40. package/package.json +1 -1
  41. package/dist/chunk-472qd3v9.js +0 -24
  42. package/dist/chunks/ThemeProvider-CkuoNG4Q.js.map +0 -1
  43. /package/dist/{chunk-dcnz24yt.js.map → chunk-7d0n00an.js.map} +0 -0
  44. /package/dist/{chunk-mvj8q5a7.js.map → chunk-w4227psm.js.map} +0 -0
@@ -796,7 +796,7 @@ function loadedRealtimeClient() {
796
796
  }
797
797
  var loading = null;
798
798
  /** The lazily loaded client. Replaceable in tests, to simulate a failed load. */
799
- var lazyModules = { client: () => import("./RealtimeClient-b6NvkUAQ.js") };
799
+ var lazyModules = { client: () => import("./RealtimeClient-BX_HwEBK.js") };
800
800
  /**
801
801
  * `./live`, loaded by the first live query. `<Page>` in `gemi/testing` sets
802
802
  * `current` up front, so a live query on a provided socket subscribes during
@@ -804,7 +804,7 @@ var lazyModules = { client: () => import("./RealtimeClient-b6NvkUAQ.js") };
804
804
  */
805
805
  var liveModule = {
806
806
  current: null,
807
- load: () => import("./live-CpXfJmRN.js").then((n) => n.n)
807
+ load: () => import("./live-CJOuf1Gu.js").then((n) => n.n)
808
808
  };
809
809
  /**
810
810
  * The tab's client, loading its module and creating it on first use. `null`
@@ -2974,16 +2974,28 @@ function flattenComponentTree(componentTree) {
2974
2974
  * (#548). The page cannot continue with the module it wanted, and a full load
2975
2975
  * of the URL it was heading to gets it the current release's document.
2976
2976
  *
2977
- * One reload, guarded: a reload that lands on the same failure (a chunk that is
2978
- * genuinely missing, a proxy that keeps refusing it) must not become a loop. A
2979
- * `sessionStorage` marker with a timestamp survives the reload it guards, so a
2980
- * second failure inside the cooldown is left to the route's error boundary.
2977
+ * One reload per page, guarded: a reload that lands on the same failure (a
2978
+ * chunk that is genuinely missing, a proxy that keeps refusing it) must not
2979
+ * become a loop. A `sessionStorage` marker records when each URL was reloaded
2980
+ * onto and survives the reload it guards, so a second failure for the same URL
2981
+ * inside the cooldown is left to the route's error boundary. Per URL rather
2982
+ * than per tab (#1056): while two releases serve at once every navigation can
2983
+ * miss, and a tab-wide cooldown sent the second one to the error boundary.
2984
+ *
2985
+ * The same guard covers a navigation whose data came from another build
2986
+ * (`recoverFromBuildMismatch`).
2981
2987
  *
2982
2988
  * No DOM or React import at module scope: the server's reload stub
2983
2989
  * (`server/staticAssetMiss.ts`) embeds the same guard as a script.
2984
2990
  */
2985
2991
  /** The `sessionStorage` key the guard lives under, shared with the reload stub. */
2986
2992
  var CHUNK_RELOAD_MARKER = "gemi:chunk-reload";
2993
+ /**
2994
+ * The response header carrying the client build id on a `.json` navigation
2995
+ * (#1056). The document carries its own as `buildId`; they differ when the
2996
+ * request reached a server running another release.
2997
+ */
2998
+ var BUILD_HEADER = "x-gemi-build";
2987
2999
  var enabled = true;
2988
3000
  var options = {};
2989
3001
  var reloading = false;
@@ -3036,20 +3048,55 @@ function recoverFromChunkLoadError(error, { source = "app", url } = {}) {
3036
3048
  if (typeof window === "undefined" || !enabled || !isChunkLoadError(error)) return false;
3037
3049
  if (reloading) return true;
3038
3050
  const target = url ?? window.location.href;
3039
- const blocked = reloadBlock(options.cooldownMs ?? 3e4);
3051
+ const blocked = reloadBlock(target);
3040
3052
  if (options.onChunkLoadError?.({
3041
3053
  error,
3042
3054
  url: target,
3043
3055
  source,
3044
3056
  blocked
3045
3057
  }) === false) return false;
3058
+ return loadInFull(target, blocked);
3059
+ }
3060
+ /**
3061
+ * Loads `url` in full because the navigation to it got its data from another
3062
+ * build (#1056): the views this page runs were not written for that data, and
3063
+ * rendering it anyway is how a deploy turned into "Something went wrong". The
3064
+ * full load gets a document and views of one build. Same guard as a chunk
3065
+ * failure, once per URL per cooldown; a navigation refused here renders the
3066
+ * data as before. Returns whether the page load was started.
3067
+ */
3068
+ function recoverFromBuildMismatch({ url, clientBuild, serverBuild }) {
3069
+ if (typeof window === "undefined" || !enabled) return false;
3070
+ if (reloading) return true;
3071
+ const blocked = reloadBlock(url);
3072
+ if (options.onBuildMismatch?.({
3073
+ url,
3074
+ clientBuild,
3075
+ serverBuild,
3076
+ blocked
3077
+ }) === false) return false;
3078
+ return loadInFull(url, blocked);
3079
+ }
3080
+ /**
3081
+ * The build that answered a `.json` navigation, when it is not `clientBuild`;
3082
+ * otherwise `null`. Unknown on either side (dev, a test double, a server from
3083
+ * before the header) is not a mismatch.
3084
+ */
3085
+ function otherBuild(response, clientBuild) {
3086
+ const served = response.headers?.get?.(BUILD_HEADER);
3087
+ return clientBuild && served && served !== clientBuild ? served : null;
3088
+ }
3089
+ function loadInFull(target, blocked) {
3046
3090
  if (blocked !== null || !markReload(target)) return false;
3047
3091
  reloading = true;
3048
3092
  if (target === window.location.href) window.location.reload();
3049
3093
  else window.location.assign(target);
3050
3094
  return true;
3051
3095
  }
3052
- function reloadBlock(cooldownMs) {
3096
+ function cooldownMs() {
3097
+ return options.cooldownMs ?? 3e4;
3098
+ }
3099
+ function reloadBlock(url) {
3053
3100
  if (typeof navigator !== "undefined" && navigator.onLine === false) return "offline";
3054
3101
  let raw;
3055
3102
  try {
@@ -3057,27 +3104,36 @@ function reloadBlock(cooldownMs) {
3057
3104
  } catch {
3058
3105
  return "storage";
3059
3106
  }
3060
- const at = parseMarker(raw);
3061
- return at !== null && withinCooldown(Date.now() - at, cooldownMs) ? "cooldown" : null;
3107
+ const at = parseMarker(raw)[url];
3108
+ return at !== void 0 && withinCooldown(Date.now() - at, cooldownMs()) ? "cooldown" : null;
3062
3109
  }
3063
3110
  function withinCooldown(elapsed, cooldownMs) {
3064
3111
  return elapsed >= 0 && elapsed < cooldownMs;
3065
3112
  }
3113
+ /**
3114
+ * The marker: when each URL was last reloaded onto, `{ [url]: at }`. Anything
3115
+ * else in it (a marker written by an earlier gemi as `{ url, at }`) is ignored.
3116
+ */
3066
3117
  function parseMarker(raw) {
3067
- if (!raw) return null;
3118
+ let parsed;
3068
3119
  try {
3069
- const at = JSON.parse(raw)?.at;
3070
- return typeof at === "number" ? at : null;
3120
+ parsed = raw ? JSON.parse(raw) : null;
3071
3121
  } catch {
3072
- return null;
3122
+ return {};
3123
+ }
3124
+ const marker = {};
3125
+ if (parsed && typeof parsed === "object") {
3126
+ for (const [url, at] of Object.entries(parsed)) if (typeof at === "number") marker[url] = at;
3073
3127
  }
3128
+ return marker;
3074
3129
  }
3075
3130
  function markReload(url) {
3076
3131
  try {
3077
- window.sessionStorage.setItem(CHUNK_RELOAD_MARKER, JSON.stringify({
3078
- url,
3079
- at: Date.now()
3080
- }));
3132
+ const now = Date.now();
3133
+ const marker = {};
3134
+ for (const [other, at] of Object.entries(parseMarker(window.sessionStorage.getItem(CHUNK_RELOAD_MARKER)))) if (withinCooldown(now - at, cooldownMs())) marker[other] = at;
3135
+ marker[url] = now;
3136
+ window.sessionStorage.setItem(CHUNK_RELOAD_MARKER, JSON.stringify(marker));
3081
3137
  return true;
3082
3138
  } catch {
3083
3139
  return false;
@@ -3221,7 +3277,7 @@ var ClientRouterProvider = (props) => {
3221
3277
  return new Subject(false);
3222
3278
  });
3223
3279
  const i18nContext = useContext(I18nContext);
3224
- const { staticRoutes, i18n } = useContext(ServerDataContext);
3280
+ const { staticRoutes, i18n, buildId } = useContext(ServerDataContext);
3225
3281
  const localeRouted = i18n?.localeRouting !== "off";
3226
3282
  const supportedLocales = localeRouted ? i18nContext.supportedLocales ?? NO_LOCALES : NO_LOCALES;
3227
3283
  const locale = localeRouted ? i18nContext.locale : null;
@@ -3397,7 +3453,7 @@ var ClientRouterProvider = (props) => {
3397
3453
  for (const view of routeManifest[routePath] ?? []) loadViewModule(view, "prefetch").catch(() => {});
3398
3454
  await prefetchCache.prime(url, async () => {
3399
3455
  const response = await fetch(url, { headers: { Purpose: "prefetch" } });
3400
- if (!response.ok) return null;
3456
+ if (!response.ok || otherBuild(response, buildId)) return null;
3401
3457
  return await readSettledRoutePayload(response);
3402
3458
  });
3403
3459
  };
@@ -3706,6 +3762,6 @@ function useTheme() {
3706
3762
  return useContext(ThemeContext);
3707
3763
  }
3708
3764
  //#endregion
3709
- export { DEFAULT_STALE_TIME as $, ServerDataContext as A, followLive as B, readRoutePayload as C, useNavigate as D, useSearchParams as E, ProgressManager as F, loadedModule as G, realtime as H, Action as I, QueryConfigContext as J, toVariantKey as K, createMemoryHistory as L, useLocation as M, domainUrl as N, I18nContext as O, isAbsoluteUrl as P, pickDefined as Q, RealtimeContext as R, setActiveLocale as S, useRoute as T, reconnectRealtime as U, liveModule as V, loadLazy as W, QueryManagerProvider as X, QueryManagerContext as Y, USER_QUERY_CONFIG_KEYS as Z, isChunkLoadError as _, useRouteTransition as a, QueryError as at, loadDictionaryForRender as b, ClientRouterContext as c, ComponentsProvider as d, readResponseBody as et, initialViewModulesReady as f, configureChunkLoadRecovery as g, CHUNK_RELOAD_MARKER as h, RouteTransitionProvider as i, shouldRetryQuery as it, ServerDataProvider as j, I18nProvider as k, ClientRouterProvider as l, subscribeViewModules as m, useTheme as n, parseRetryAfter as nt, useChannelSubscription as o, Subject as ot, loadViewModule as p, applyParams as q, m as r, retryDelayFor as rt, useCoalesced as s, ThemeProvider as t, isRetryableQueryError as tt, ComponentsContext as u, recoverFromChunkLoadError as v, routeDataUrl as w, registerDictionary as x, loadDictionary as y, configureRealtime as z };
3765
+ export { USER_QUERY_CONFIG_KEYS as $, I18nContext as A, RealtimeContext as B, registerDictionary as C, useRoute as D, routeDataUrl as E, domainUrl as F, reconnectRealtime as G, followLive as H, isAbsoluteUrl as I, toVariantKey as J, loadLazy as K, ProgressManager as L, ServerDataContext as M, ServerDataProvider as N, useSearchParams as O, useLocation as P, QueryManagerProvider as Q, Action as R, loadDictionaryForRender as S, readRoutePayload as T, liveModule as U, configureRealtime as V, realtime as W, QueryConfigContext as X, applyParams as Y, QueryManagerContext as Z, isChunkLoadError as _, useRouteTransition as a, retryDelayFor as at, recoverFromChunkLoadError as b, ClientRouterContext as c, Subject as ct, ComponentsProvider as d, pickDefined as et, initialViewModulesReady as f, configureChunkLoadRecovery as g, CHUNK_RELOAD_MARKER as h, RouteTransitionProvider as i, parseRetryAfter as it, I18nProvider as j, useNavigate as k, ClientRouterProvider as l, subscribeViewModules as m, useTheme as n, readResponseBody as nt, useChannelSubscription as o, shouldRetryQuery as ot, loadViewModule as p, loadedModule as q, m as r, isRetryableQueryError as rt, useCoalesced as s, QueryError as st, ThemeProvider as t, DEFAULT_STALE_TIME as tt, ComponentsContext as u, otherBuild as v, setActiveLocale as w, loadDictionary as x, recoverFromBuildMismatch as y, createMemoryHistory as z };
3710
3766
 
3711
- //# sourceMappingURL=ThemeProvider-CkuoNG4Q.js.map
3767
+ //# sourceMappingURL=ThemeProvider-D4mhhONE.js.map