cloudflare-next-intl 0.8.44 → 0.8.46
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 +28 -0
- package/dist/src/error_handling/default_ignored_console_errors.js +2 -0
- package/dist/src/error_handling/index.d.ts +1 -0
- package/dist/src/error_handling/index.js +1 -0
- package/dist/src/error_handling/is_stale_deploy_error.js +2 -0
- package/dist/src/error_handling/use_stale_deploy_recovery.d.ts +10 -0
- package/dist/src/error_handling/use_stale_deploy_recovery.js +60 -0
- package/llms.txt +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -532,6 +532,34 @@ export default function GlobalError({
|
|
|
532
532
|
- `getStaleDeployPatterns(): readonly string[]`: Returns the currently active pattern list.
|
|
533
533
|
- `clearClientCache(): Promise<void>`: Best-effort cleanup that deletes all CacheStorage caches (`window.caches`), unregisters active Service Workers, and clears `sessionStorage`.
|
|
534
534
|
|
|
535
|
+
For a ready-made recovery flow (recommended over wiring `isStaleDeployError` + `clearClientCache` by hand), use the `useStaleDeployRecovery` hook:
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
'use client';
|
|
539
|
+
|
|
540
|
+
import { useStaleDeployRecovery } from "cloudflare-next-intl/errorHandling";
|
|
541
|
+
|
|
542
|
+
export default function GlobalError({
|
|
543
|
+
error,
|
|
544
|
+
reset,
|
|
545
|
+
}: {
|
|
546
|
+
error: Error & { digest?: string };
|
|
547
|
+
reset: () => void;
|
|
548
|
+
}) {
|
|
549
|
+
// Optionally pass a server action to clear server-side cookies/cache
|
|
550
|
+
// before the reload; runs alongside clearClientCache() and any
|
|
551
|
+
// rejection is ignored (best-effort).
|
|
552
|
+
const isRecovering = useStaleDeployRecovery(error /*, clearServerCookies */);
|
|
553
|
+
|
|
554
|
+
if (isRecovering) return <LoadingIndicator />;
|
|
555
|
+
|
|
556
|
+
// ... render your normal error UI
|
|
557
|
+
}
|
|
558
|
+
```
|
|
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.
|
|
562
|
+
|
|
535
563
|
### Database (`db`)
|
|
536
564
|
|
|
537
565
|
Thin Postgres/Drizzle data-access layer over a Postgres connection string
|
|
@@ -34,6 +34,8 @@ export const defaultIgnoredConsoleErrors = [
|
|
|
34
34
|
"FirebaseServerApp authIdToken is invalid: the token has expired.",
|
|
35
35
|
"failed Error: Database is closing/hidden",
|
|
36
36
|
'Failed to fetch RSC payload',
|
|
37
|
+
'The above error occurred in a React component',
|
|
38
|
+
'The connection to the page was unexpectedly closed',
|
|
37
39
|
...(process.env.NODE_ENV === 'development'
|
|
38
40
|
? ['A DurableObjectNamespace in the config referenced the class "DOQueueHandler", but no such Durable Object class is exported from the worker. Please make sure the class name matches,']
|
|
39
41
|
: []),
|
|
@@ -9,4 +9,5 @@ export { default as formatErrorMessage } from './format_error_message';
|
|
|
9
9
|
export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
|
|
10
10
|
export { default as isStaleDeployError, defaultStaleDeployPatterns, setStaleDeployPatterns, getStaleDeployPatterns, } from './is_stale_deploy_error';
|
|
11
11
|
export { default as clearClientCache } from './clear_client_cache';
|
|
12
|
+
export { default as useStaleDeployRecovery, shouldRecoverFromStaleDeploy } from './use_stale_deploy_recovery';
|
|
12
13
|
export type { ErrorHandlingParams, ErrorHandlingRoutingConfig } from '../types/types';
|
|
@@ -7,3 +7,4 @@ export { default as formatErrorMessage } from './format_error_message';
|
|
|
7
7
|
export { defaultIgnoredConsoleErrors } from './default_ignored_console_errors';
|
|
8
8
|
export { default as isStaleDeployError, defaultStaleDeployPatterns, setStaleDeployPatterns, getStaleDeployPatterns, } from './is_stale_deploy_error';
|
|
9
9
|
export { default as clearClientCache } from './clear_client_cache';
|
|
10
|
+
export { default as useStaleDeployRecovery, shouldRecoverFromStaleDeploy } from './use_stale_deploy_recovery';
|
|
@@ -5,6 +5,8 @@ export const defaultStaleDeployPatterns = [
|
|
|
5
5
|
'connection closed',
|
|
6
6
|
'rsc payload',
|
|
7
7
|
'minified react error #412',
|
|
8
|
+
'the above error occurred in a react component',
|
|
9
|
+
'the connection to the page was unexpectedly closed',
|
|
8
10
|
];
|
|
9
11
|
let activePatterns = defaultStaleDeployPatterns;
|
|
10
12
|
let activeLowercasedPatterns = defaultStaleDeployPatterns.map((p) => p.toLowerCase());
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare function shouldRecoverFromStaleDeploy(error: Error, buildId: string, marker: string | null): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Detects a stale-deploy error and, once per build id, silently clears client
|
|
4
|
+
* caches and reloads after `delayMs`. Returns whether a reload is pending so
|
|
5
|
+
* the caller can render a loading state instead of the error UI while it
|
|
6
|
+
* waits. `onRecover` runs before the reload (e.g. to clear server cookies via
|
|
7
|
+
* a server action) and its rejection is ignored — cache clearing is
|
|
8
|
+
* best-effort.
|
|
9
|
+
*/
|
|
10
|
+
export default function useStaleDeployRecovery(error: Error, onRecover?: () => Promise<unknown>, delayMs?: number): boolean;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import isStaleDeployError from './is_stale_deploy_error';
|
|
4
|
+
import clearClientCache from './clear_client_cache';
|
|
5
|
+
const RECOVERY_RELOAD_KEY = 'stale-deploy-recovery-reloaded';
|
|
6
|
+
const BUILD_ID_KEY = 'buildId';
|
|
7
|
+
function currentBuildId() {
|
|
8
|
+
try {
|
|
9
|
+
return localStorage.getItem(BUILD_ID_KEY) ?? 'unknown';
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return 'unknown';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
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;
|
|
21
|
+
}
|
|
22
|
+
function canRecover(error) {
|
|
23
|
+
if (typeof window === 'undefined')
|
|
24
|
+
return false;
|
|
25
|
+
try {
|
|
26
|
+
return shouldRecoverFromStaleDeploy(error, currentBuildId(), sessionStorage.getItem(RECOVERY_RELOAD_KEY));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Detects a stale-deploy error and, once per build id, silently clears client
|
|
34
|
+
* caches and reloads after `delayMs`. Returns whether a reload is pending so
|
|
35
|
+
* the caller can render a loading state instead of the error UI while it
|
|
36
|
+
* waits. `onRecover` runs before the reload (e.g. to clear server cookies via
|
|
37
|
+
* a server action) and its rejection is ignored — cache clearing is
|
|
38
|
+
* best-effort.
|
|
39
|
+
*/
|
|
40
|
+
export default function useStaleDeployRecovery(error, onRecover, delayMs = 5000) {
|
|
41
|
+
const [recovering] = useState(() => canRecover(error));
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!recovering)
|
|
44
|
+
return;
|
|
45
|
+
const buildId = currentBuildId();
|
|
46
|
+
const timeout = setTimeout(() => {
|
|
47
|
+
Promise.all([onRecover?.().catch(() => undefined), clearClientCache().catch(() => undefined)])
|
|
48
|
+
.finally(() => {
|
|
49
|
+
try {
|
|
50
|
+
sessionStorage.setItem(RECOVERY_RELOAD_KEY, buildId);
|
|
51
|
+
}
|
|
52
|
+
catch { /* storage unavailable */ }
|
|
53
|
+
window.location.reload();
|
|
54
|
+
});
|
|
55
|
+
}, delayMs);
|
|
56
|
+
return () => clearTimeout(timeout);
|
|
57
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
58
|
+
}, [recovering]);
|
|
59
|
+
return recovering;
|
|
60
|
+
}
|
package/llms.txt
CHANGED
|
@@ -28,9 +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`.
|
|
31
|
+
- `./errorHandling` — error reporting & stale deploy recovery barrel: `reportError`, `withErrorHandling`, `installConsoleErrorOverride`, `installGlobalErrorOverride`, `stringifyUnknown`, `formatErrorMessage`, `defaultIgnoredConsoleErrors`, `createServerErrorAction`, `isStaleDeployError`, `defaultStaleDeployPatterns`, `setStaleDeployPatterns`, `getStaleDeployPatterns`, `clearClientCache`, `useStaleDeployRecovery`, `shouldRecoverFromStaleDeploy`.
|
|
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
35
|
- `./createServerErrorAction` — `createServerErrorAction(action, config)`: wrapper for server actions with standardized error reporting.
|
|
35
36
|
|
|
36
37
|
## `firebaseAuth*` subpaths (require `firebaseAuth` set on your `RoutingConfig`)
|