cloudflare-next-intl 0.8.47 → 0.8.49

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/README.md CHANGED
@@ -557,8 +557,9 @@ export default function GlobalError({
557
557
  }
558
558
  ```
559
559
 
560
- - `useStaleDeployRecovery(error: Error, onRecover?: () => Promise<unknown>, delayMs = 5000): boolean`: Detects a stale-deploy error via `isStaleDeployError` and, **once per build id**, waits `delayMs`, runs `onRecover` (if provided) and `clearClientCache` in parallel, then reloads the page. The build id is read from `localStorage['buildId']` (set by `IntlHelperScript`'s `BUILD_ID` check) and a `sessionStorage` marker records which build id already spent its reload, so a repeat failure on the same build falls through to `false` (render your normal error UI) instead of reloading forever. A redeploy changes the build id and re-arms exactly one more attempt.
561
- - `shouldRecoverFromStaleDeploy(error: Error, buildId: string, marker: string | null): boolean`: The pure predicate behind the hook, exported for testing.
560
+ - `useStaleDeployRecovery(error: Error, onRecover?: () => Promise<unknown>, delayMs = 5000): boolean`: Detects a stale-deploy error via `isStaleDeployError` and, **once per build id**, waits `delayMs`, runs `onRecover` (if provided) and `clearClientCache` in parallel, then reloads the page. The build id is read from `localStorage['buildId']` (set by `IntlHelperScript`'s `BUILD_ID` check) and a `sessionStorage` marker records which build id already spent its reload, so a repeat failure on the same build falls through to `false` (render your normal error UI) instead of reloading forever. A redeploy changes the build id and re-arms exactly one more attempt. **Exception:** if the build id was written within the last 60 seconds (`localStorage['buildIdSetAt']`, also set by `IntlHelperScript`), the hook recovers anyway — a stale-deploy error moments after adopting a new build is the deploy still settling, not a real failure, so the one-reload cap is bypassed for that window.
561
+ - `shouldRecoverFromStaleDeploy(error: Error, buildId: string, marker: string | null, recentBuild = false): boolean`: The pure predicate behind the hook, exported for testing.
562
+ - `isRecentBuild(setAt: number | null, now: number, windowMs = 60000): boolean`: Whether `setAt` (typically `buildIdSetAt`) falls within `windowMs` of `now`.
562
563
 
563
564
  ### Database (`db`)
564
565
 
@@ -32,6 +32,7 @@ export const defaultIgnoredConsoleErrors = [
32
32
  'The `punycode` module is deprecated. Please use a userland alternative instead.',
33
33
  'failed to pipe response',
34
34
  "FirebaseServerApp authIdToken is invalid: the token has expired.",
35
+ "FirebaseServerApp appCheckToken is invalid: the token has expired.",
35
36
  "failed Error: Database is closing/hidden",
36
37
  'Failed to fetch RSC payload',
37
38
  'The above error occurred in a React component',
@@ -1,4 +1,4 @@
1
1
  export declare const defaultStaleDeployPatterns: readonly string[];
2
2
  export declare function setStaleDeployPatterns(patterns: readonly string[]): void;
3
3
  export declare function getStaleDeployPatterns(): readonly string[];
4
- export default function isStaleDeployError(error: Error, patterns?: readonly string[]): boolean;
4
+ export default function isStaleDeployError(error: unknown, patterns?: readonly string[]): boolean;
@@ -18,8 +18,18 @@ export function getStaleDeployPatterns() {
18
18
  return activePatterns;
19
19
  }
20
20
  export default function isStaleDeployError(error, patterns) {
21
+ // A stale build can leave the caught value itself missing — e.g. an
22
+ // aborted RSC stream reaching a client component as `undefined` rather
23
+ // than a real Error (seen as "Global Error undefined ... The above error
24
+ // occurred in a React component" in the console, with no message to
25
+ // pattern-match on). Treat exactly `undefined` as stale-deploy; a normal
26
+ // thrown error is never `undefined`.
27
+ if (error === undefined)
28
+ return true;
21
29
  if (!error)
22
30
  return false;
31
+ if (!(error instanceof Error))
32
+ return false;
23
33
  if (error.name === 'ChunkLoadError')
24
34
  return true;
25
35
  const message = (error.message || '').toLowerCase();
@@ -1,4 +1,13 @@
1
- export declare function shouldRecoverFromStaleDeploy(error: Error, buildId: string, marker: string | null): boolean;
1
+ /**
2
+ * True when this build id was written within the last `windowMs` — i.e. the
3
+ * client just picked up a new deploy (via `IntlHelperScript`'s BUILD_ID
4
+ * check). A stale-deploy error in that window is the deploy itself still
5
+ * settling (new chunks, in-flight RSC requests against the old build), not a
6
+ * failure a reload can't fix — so it recovers even on a build id the reload
7
+ * marker already covers.
8
+ */
9
+ export declare function isRecentBuild(setAt: number | null, now: number, windowMs?: number): boolean;
10
+ export declare function shouldRecoverFromStaleDeploy(error: unknown, buildId: string, marker: string | null, recentBuild?: boolean): boolean;
2
11
  /**
3
12
  * Detects a stale-deploy error and, once per build id, silently clears client
4
13
  * caches and reloads after `delayMs`. Returns whether a reload is pending so
@@ -7,4 +16,4 @@ export declare function shouldRecoverFromStaleDeploy(error: Error, buildId: stri
7
16
  * a server action) and its rejection is ignored — cache clearing is
8
17
  * best-effort.
9
18
  */
10
- export default function useStaleDeployRecovery(error: Error, onRecover?: () => Promise<unknown>, delayMs?: number): boolean;
19
+ export default function useStaleDeployRecovery(error: unknown, onRecover?: () => Promise<unknown>, delayMs?: number): boolean;
@@ -4,6 +4,8 @@ import isStaleDeployError from './is_stale_deploy_error';
4
4
  import clearClientCache from './clear_client_cache';
5
5
  const RECOVERY_RELOAD_KEY = 'stale-deploy-recovery-reloaded';
6
6
  const BUILD_ID_KEY = 'buildId';
7
+ const BUILD_ID_SET_AT_KEY = 'buildIdSetAt';
8
+ const RECENT_BUILD_WINDOW_MS = 60000;
7
9
  function currentBuildId() {
8
10
  try {
9
11
  return localStorage.getItem(BUILD_ID_KEY) ?? 'unknown';
@@ -12,18 +14,39 @@ function currentBuildId() {
12
14
  return 'unknown';
13
15
  }
14
16
  }
15
- // One silent reload per deployment. The marker carries the build id the reload
16
- // was spent on, so a redeploy re-arms exactly one more attempt while a repeat
17
- // failure on the same build falls through to the caller's error UI instead of
18
- // spinning forever.
19
- export function shouldRecoverFromStaleDeploy(error, buildId, marker) {
20
- return isStaleDeployError(error) && marker !== buildId;
17
+ function buildIdSetAt() {
18
+ try {
19
+ const raw = localStorage.getItem(BUILD_ID_SET_AT_KEY);
20
+ return raw ? Number(raw) : null;
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /**
27
+ * True when this build id was written within the last `windowMs` — i.e. the
28
+ * client just picked up a new deploy (via `IntlHelperScript`'s BUILD_ID
29
+ * check). A stale-deploy error in that window is the deploy itself still
30
+ * settling (new chunks, in-flight RSC requests against the old build), not a
31
+ * failure a reload can't fix — so it recovers even on a build id the reload
32
+ * marker already covers.
33
+ */
34
+ export function isRecentBuild(setAt, now, windowMs = RECENT_BUILD_WINDOW_MS) {
35
+ return setAt !== null && now - setAt < windowMs;
36
+ }
37
+ // One silent reload per deployment, UNLESS the build id was written moments
38
+ // ago — see `isRecentBuild`. The marker carries the build id the reload was
39
+ // spent on, so a redeploy re-arms exactly one more attempt while a repeat
40
+ // failure well after the deploy settled falls through to the caller's error
41
+ // UI instead of spinning forever.
42
+ export function shouldRecoverFromStaleDeploy(error, buildId, marker, recentBuild = false) {
43
+ return isStaleDeployError(error) && (marker !== buildId || recentBuild);
21
44
  }
22
45
  function canRecover(error) {
23
46
  if (typeof window === 'undefined')
24
47
  return false;
25
48
  try {
26
- return shouldRecoverFromStaleDeploy(error, currentBuildId(), sessionStorage.getItem(RECOVERY_RELOAD_KEY));
49
+ return shouldRecoverFromStaleDeploy(error, currentBuildId(), sessionStorage.getItem(RECOVERY_RELOAD_KEY), isRecentBuild(buildIdSetAt(), Date.now()));
27
50
  }
28
51
  catch {
29
52
  return false;
@@ -44,23 +44,9 @@ export default function HelperScript() {
44
44
 
45
45
  if (prevBuild !== BUILD_ID) {
46
46
  localStorage.setItem('buildId', BUILD_ID);
47
+ localStorage.setItem('buildIdSetAt', String(Date.now()));
47
48
  if(prevBuild){
48
- // Reloading while the document/RSC stream is
49
- // still downloading is the same as pressing
50
- // Stop: Firefox aborts the in-flight read and
51
- // surfaces "The connection to the page was
52
- // unexpectedly closed", which reaches React as
53
- // a caught error and renders the app's error
54
- // screen. Wait for the load to settle first.
55
- if (document.readyState === 'complete') {
56
- window.location.reload();
57
- } else {
58
- window.addEventListener(
59
- 'load',
60
- function () { window.location.reload(); },
61
- { once: true },
62
- );
63
- }
49
+ window.location.reload(true);
64
50
  }
65
51
  }
66
52
  }
package/llms.txt CHANGED
@@ -28,10 +28,10 @@ other subpath can be used.
28
28
  - `./dbEslint` — flat-config ESLint fragment banning direct `@supabase/supabase-js`, `pg`, `postgres`, and deep `dist/` imports in application code.
29
29
  - `./dbHelpers` — generic Drizzle SQL helper functions (`excluded`, `onConflictSet`, `ago`, `currentDate`, `windowCount`, `unnestLateral`, `ascNullsLast`, `alwaysTrue`, `lateral`, `aliasColumn`, `minOf`, `maxOf`, `roundReal`, `multiply`, `scalarFromCte`) for use with `./db`.
30
30
  - `./vite` — `buildIdAsset(fileName?)`: Vite plugin to emit the client `BUILD_ID` asset (from `__VINEXT_SHARED_BUILD_ID` or `__VINEXT_BUILD_ID`) during build in Vinext/Vite environments.
31
- - `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`, `useStaleDeployRecovery`, `shouldRecoverFromStaleDeploy`.
31
+ - `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`, `useStaleDeployRecovery`, `shouldRecoverFromStaleDeploy`, `isRecentBuild`.
32
32
  - `./isStaleDeployError` — `isStaleDeployError(error, patterns?)`, `setStaleDeployPatterns(patterns)`, `getStaleDeployPatterns()`: detector returning `true` for version skew / chunk load / hydration errors (ChunkLoadError, failed to fetch, loading CSS chunk, connection closed, RSC payload failure, minified error #412) with fast pre-lowercased pattern cache and intl-config integration (`errorHandling.staleDeployPatterns`).
33
33
  - `./clearClientCache` — `clearClientCache()`: async helper wiping `window.caches`, unregistering service workers, and clearing `sessionStorage` for recovering from stale deployments.
34
- - `useStaleDeployRecovery(error, onRecover?, delayMs?)` (client hook, in `./errorHandling`) — once per build id (`localStorage['buildId']`, `sessionStorage` marker), waits `delayMs` (default 5000ms), runs optional `onRecover()` + `clearClientCache()` in parallel, then `window.location.reload()`. Returns whether a reload is pending, so caller renders a loading state instead of error UI. `shouldRecoverFromStaleDeploy(error, buildId, marker)` is the pure predicate.
34
+ - `useStaleDeployRecovery(error, onRecover?, delayMs?)` (client hook, in `./errorHandling`) — once per build id (`localStorage['buildId']`, `sessionStorage` marker), waits `delayMs` (default 5000ms), runs optional `onRecover()` + `clearClientCache()` in parallel, then `window.location.reload()`. Returns whether a reload is pending, so caller renders a loading state instead of error UI. Recovers even past the one-reload cap when `localStorage['buildIdSetAt']` is <60s old (new deploy still settling). `shouldRecoverFromStaleDeploy(error, buildId, marker, recentBuild?)` and `isRecentBuild(setAt, now, windowMs?)` are the pure predicates.
35
35
  - `./createServerErrorAction` — `createServerErrorAction(action, config)`: wrapper for server actions with standardized error reporting.
36
36
 
37
37
  ## `firebaseAuth*` subpaths (require `firebaseAuth` set on your `RoutingConfig`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudflare-next-intl",
3
- "version": "0.8.47",
3
+ "version": "0.8.49",
4
4
  "description": "Optimized Next Intl Package Special for App Router and Cloudflare",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",