@dloizides/utils 1.1.0 → 1.3.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.
- package/CHANGELOG.md +35 -0
- package/dist/index.d.mts +100 -5
- package/dist/index.d.ts +100 -5
- package/dist/index.js +161 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +153 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,41 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.3.0] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `isChunkLoadError(error)` / `attemptChunkRecovery(ports?)` / `clearChunkRecoveryFlag(ports?)` /
|
|
13
|
+
`reloadPage(ports?)` + the `ChunkRecoveryPorts` type — stale-chunk detection and a one-shot
|
|
14
|
+
guarded reload. After a deploy the browser can hold a stale `index.html` referencing hashed
|
|
15
|
+
chunks that now 404; a single guarded `location.reload()` recovers silently, and the
|
|
16
|
+
sessionStorage one-shot flag stops it becoming a reload LOOP when the reload does not help.
|
|
17
|
+
Detection is a pure predicate; the storage + reload side effects are injectable ports.
|
|
18
|
+
Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in kefi-web,
|
|
19
|
+
erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd copy — the original file's
|
|
20
|
+
own header predicted the move). Also exported as the `chunkRecovery` namespace.
|
|
21
|
+
Pairs with `<AppErrorBoundary>` from `@dloizides/ui-feedback` ≥1.7.0, which consumes these as
|
|
22
|
+
its `retryable` / `onMount` / `recover` injections.
|
|
23
|
+
|
|
24
|
+
## [1.2.0] - 2026-07-18
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `setRedirectHandler(handler)` / `redirectTo(path)` / `resetRedirectHandler()` — a
|
|
29
|
+
framework-agnostic redirect escape hatch for non-React callers (axios 401 interceptors,
|
|
30
|
+
auth providers) that need to navigate without a router hook. Falls back through
|
|
31
|
+
registered handler → `window.location.replace` → a queue drained when the router mounts,
|
|
32
|
+
and forces a hard navigation if the router silently failed to change the URL.
|
|
33
|
+
Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and
|
|
34
|
+
katalogos-web (de-fork wave W1.1). Also exported as the `navigation` namespace.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- The promoted watchdog guarded its `window` access with `try`/`catch` only; it now checks
|
|
39
|
+
`typeof window !== 'undefined'` explicitly, so a non-browser host cannot reach a
|
|
40
|
+
`ReferenceError` path at all. Surfaced by porting the code into a package whose tsconfig
|
|
41
|
+
has no `DOM` lib.
|
|
42
|
+
|
|
8
43
|
## [1.1.0] - 2026-06-15
|
|
9
44
|
|
|
10
45
|
### Added
|
package/dist/index.d.mts
CHANGED
|
@@ -20,14 +20,109 @@ declare function getErrorMessage(value: unknown, fallback?: string): string;
|
|
|
20
20
|
*/
|
|
21
21
|
declare function formatDate(date: Date | null | undefined, locale: string, options?: Intl.DateTimeFormatOptions): string;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Framework-agnostic redirect escape hatch.
|
|
25
|
+
*
|
|
26
|
+
* WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's
|
|
27
|
+
* token-refresh failure path) needs to navigate, but has no access to a router
|
|
28
|
+
* hook. The app registers its router's push/replace once via `setRedirectHandler`;
|
|
29
|
+
* everything else calls `redirectTo` and does not care which router is mounted.
|
|
30
|
+
*
|
|
31
|
+
* Three layers of fallback, in order:
|
|
32
|
+
* 1. the registered handler (the app's router),
|
|
33
|
+
* 2. `window.location.replace` if no handler is registered yet,
|
|
34
|
+
* 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired
|
|
35
|
+
* before the router mounts is not silently dropped.
|
|
36
|
+
*
|
|
37
|
+
* Layer 1 additionally schedules a watchdog: some routers accept a `push` for a
|
|
38
|
+
* route they cannot resolve and leave the URL untouched, which strands the user on
|
|
39
|
+
* the page they were supposed to leave. If the location has not changed after
|
|
40
|
+
* `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.
|
|
41
|
+
*
|
|
42
|
+
* Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and
|
|
43
|
+
* katalogos-web. Pure DOM + timers — no React, no router, no framework import.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Register the app's router navigate function and drain any redirects that were
|
|
47
|
+
* queued before it mounted. Call once, from the root layout.
|
|
48
|
+
*/
|
|
49
|
+
declare function setRedirectHandler(h: (path: string) => void): void;
|
|
50
|
+
/**
|
|
51
|
+
* Navigate to `path` from anywhere, including outside React.
|
|
52
|
+
* Safe to call before the router mounts — the redirect is queued and replayed.
|
|
53
|
+
*/
|
|
54
|
+
declare function redirectTo(path: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Clear the registered handler and any queued redirects.
|
|
57
|
+
* Exists for tests — module state otherwise leaks between cases.
|
|
58
|
+
*/
|
|
59
|
+
declare function resetRedirectHandler(): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detection + one-shot auto-recovery for stale-chunk load failures.
|
|
63
|
+
*
|
|
64
|
+
* WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold
|
|
65
|
+
* a stale `index.html` that references hashed JS chunks which now 404 — surfacing
|
|
66
|
+
* as a `ChunkLoadError` / "failed to fetch dynamically imported module". A single
|
|
67
|
+
* guarded `location.reload()` re-fetches the fresh entry + chunks and the app
|
|
68
|
+
* recovers with no user-visible error. The sessionStorage one-shot flag prevents
|
|
69
|
+
* a reload LOOP if the reload does not fix it (then the boundary shows a manual
|
|
70
|
+
* Reload action instead).
|
|
71
|
+
*
|
|
72
|
+
* Pure + port-injectable: detection is a pure predicate; the storage + reload
|
|
73
|
+
* side effects are ports (default to `window`) so unit tests run deterministically
|
|
74
|
+
* in jsdom without a real navigation.
|
|
75
|
+
*
|
|
76
|
+
* Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in
|
|
77
|
+
* kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).
|
|
78
|
+
* The original file's own header predicted this move. Pairs with
|
|
79
|
+
* `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its
|
|
80
|
+
* `retryable` / `onMount` / `recover` injections.
|
|
81
|
+
*/
|
|
82
|
+
/** Injectable side-effect ports; default to the real `window`. */
|
|
83
|
+
interface ChunkRecoveryPorts {
|
|
84
|
+
hasFlag: () => boolean;
|
|
85
|
+
setFlag: () => void;
|
|
86
|
+
clearFlag: () => void;
|
|
87
|
+
reload: () => void;
|
|
88
|
+
}
|
|
89
|
+
/** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */
|
|
90
|
+
declare function isChunkLoadError(error: unknown): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Attempt a one-shot reload to recover from a stale-chunk error.
|
|
93
|
+
* Returns `true` when a reload was triggered, `false` when the one-shot guard was
|
|
94
|
+
* already spent (the caller then shows a manual Reload action, never a loop).
|
|
95
|
+
*/
|
|
96
|
+
declare function attemptChunkRecovery(ports?: ChunkRecoveryPorts): boolean;
|
|
97
|
+
/** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */
|
|
98
|
+
declare function clearChunkRecoveryFlag(ports?: ChunkRecoveryPorts): void;
|
|
99
|
+
/** Manually reload the page (the Reload action). */
|
|
100
|
+
declare function reloadPage(ports?: ChunkRecoveryPorts): void;
|
|
101
|
+
|
|
102
|
+
declare const index$3_getErrorMessage: typeof getErrorMessage;
|
|
103
|
+
declare namespace index$3 {
|
|
104
|
+
export { index$3_getErrorMessage as getErrorMessage };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
declare const index$2_formatDate: typeof formatDate;
|
|
108
|
+
declare namespace index$2 {
|
|
109
|
+
export { index$2_formatDate as formatDate };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
declare const index$1_redirectTo: typeof redirectTo;
|
|
113
|
+
declare const index$1_resetRedirectHandler: typeof resetRedirectHandler;
|
|
114
|
+
declare const index$1_setRedirectHandler: typeof setRedirectHandler;
|
|
24
115
|
declare namespace index$1 {
|
|
25
|
-
export { index$
|
|
116
|
+
export { index$1_redirectTo as redirectTo, index$1_resetRedirectHandler as resetRedirectHandler, index$1_setRedirectHandler as setRedirectHandler };
|
|
26
117
|
}
|
|
27
118
|
|
|
28
|
-
|
|
119
|
+
type index_ChunkRecoveryPorts = ChunkRecoveryPorts;
|
|
120
|
+
declare const index_attemptChunkRecovery: typeof attemptChunkRecovery;
|
|
121
|
+
declare const index_clearChunkRecoveryFlag: typeof clearChunkRecoveryFlag;
|
|
122
|
+
declare const index_isChunkLoadError: typeof isChunkLoadError;
|
|
123
|
+
declare const index_reloadPage: typeof reloadPage;
|
|
29
124
|
declare namespace index {
|
|
30
|
-
export {
|
|
125
|
+
export { type index_ChunkRecoveryPorts as ChunkRecoveryPorts, index_attemptChunkRecovery as attemptChunkRecovery, index_clearChunkRecoveryFlag as clearChunkRecoveryFlag, index_isChunkLoadError as isChunkLoadError, index_reloadPage as reloadPage };
|
|
31
126
|
}
|
|
32
127
|
|
|
33
|
-
export { index$
|
|
128
|
+
export { type ChunkRecoveryPorts, attemptChunkRecovery, index as chunkRecovery, clearChunkRecoveryFlag, index$3 as error, index$2 as format, formatDate, getErrorMessage, isChunkLoadError, index$1 as navigation, redirectTo, reloadPage, resetRedirectHandler, setRedirectHandler };
|
package/dist/index.d.ts
CHANGED
|
@@ -20,14 +20,109 @@ declare function getErrorMessage(value: unknown, fallback?: string): string;
|
|
|
20
20
|
*/
|
|
21
21
|
declare function formatDate(date: Date | null | undefined, locale: string, options?: Intl.DateTimeFormatOptions): string;
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Framework-agnostic redirect escape hatch.
|
|
25
|
+
*
|
|
26
|
+
* WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's
|
|
27
|
+
* token-refresh failure path) needs to navigate, but has no access to a router
|
|
28
|
+
* hook. The app registers its router's push/replace once via `setRedirectHandler`;
|
|
29
|
+
* everything else calls `redirectTo` and does not care which router is mounted.
|
|
30
|
+
*
|
|
31
|
+
* Three layers of fallback, in order:
|
|
32
|
+
* 1. the registered handler (the app's router),
|
|
33
|
+
* 2. `window.location.replace` if no handler is registered yet,
|
|
34
|
+
* 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired
|
|
35
|
+
* before the router mounts is not silently dropped.
|
|
36
|
+
*
|
|
37
|
+
* Layer 1 additionally schedules a watchdog: some routers accept a `push` for a
|
|
38
|
+
* route they cannot resolve and leave the URL untouched, which strands the user on
|
|
39
|
+
* the page they were supposed to leave. If the location has not changed after
|
|
40
|
+
* `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.
|
|
41
|
+
*
|
|
42
|
+
* Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and
|
|
43
|
+
* katalogos-web. Pure DOM + timers — no React, no router, no framework import.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Register the app's router navigate function and drain any redirects that were
|
|
47
|
+
* queued before it mounted. Call once, from the root layout.
|
|
48
|
+
*/
|
|
49
|
+
declare function setRedirectHandler(h: (path: string) => void): void;
|
|
50
|
+
/**
|
|
51
|
+
* Navigate to `path` from anywhere, including outside React.
|
|
52
|
+
* Safe to call before the router mounts — the redirect is queued and replayed.
|
|
53
|
+
*/
|
|
54
|
+
declare function redirectTo(path: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Clear the registered handler and any queued redirects.
|
|
57
|
+
* Exists for tests — module state otherwise leaks between cases.
|
|
58
|
+
*/
|
|
59
|
+
declare function resetRedirectHandler(): void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detection + one-shot auto-recovery for stale-chunk load failures.
|
|
63
|
+
*
|
|
64
|
+
* WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold
|
|
65
|
+
* a stale `index.html` that references hashed JS chunks which now 404 — surfacing
|
|
66
|
+
* as a `ChunkLoadError` / "failed to fetch dynamically imported module". A single
|
|
67
|
+
* guarded `location.reload()` re-fetches the fresh entry + chunks and the app
|
|
68
|
+
* recovers with no user-visible error. The sessionStorage one-shot flag prevents
|
|
69
|
+
* a reload LOOP if the reload does not fix it (then the boundary shows a manual
|
|
70
|
+
* Reload action instead).
|
|
71
|
+
*
|
|
72
|
+
* Pure + port-injectable: detection is a pure predicate; the storage + reload
|
|
73
|
+
* side effects are ports (default to `window`) so unit tests run deterministically
|
|
74
|
+
* in jsdom without a real navigation.
|
|
75
|
+
*
|
|
76
|
+
* Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in
|
|
77
|
+
* kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).
|
|
78
|
+
* The original file's own header predicted this move. Pairs with
|
|
79
|
+
* `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its
|
|
80
|
+
* `retryable` / `onMount` / `recover` injections.
|
|
81
|
+
*/
|
|
82
|
+
/** Injectable side-effect ports; default to the real `window`. */
|
|
83
|
+
interface ChunkRecoveryPorts {
|
|
84
|
+
hasFlag: () => boolean;
|
|
85
|
+
setFlag: () => void;
|
|
86
|
+
clearFlag: () => void;
|
|
87
|
+
reload: () => void;
|
|
88
|
+
}
|
|
89
|
+
/** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */
|
|
90
|
+
declare function isChunkLoadError(error: unknown): boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Attempt a one-shot reload to recover from a stale-chunk error.
|
|
93
|
+
* Returns `true` when a reload was triggered, `false` when the one-shot guard was
|
|
94
|
+
* already spent (the caller then shows a manual Reload action, never a loop).
|
|
95
|
+
*/
|
|
96
|
+
declare function attemptChunkRecovery(ports?: ChunkRecoveryPorts): boolean;
|
|
97
|
+
/** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */
|
|
98
|
+
declare function clearChunkRecoveryFlag(ports?: ChunkRecoveryPorts): void;
|
|
99
|
+
/** Manually reload the page (the Reload action). */
|
|
100
|
+
declare function reloadPage(ports?: ChunkRecoveryPorts): void;
|
|
101
|
+
|
|
102
|
+
declare const index$3_getErrorMessage: typeof getErrorMessage;
|
|
103
|
+
declare namespace index$3 {
|
|
104
|
+
export { index$3_getErrorMessage as getErrorMessage };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
declare const index$2_formatDate: typeof formatDate;
|
|
108
|
+
declare namespace index$2 {
|
|
109
|
+
export { index$2_formatDate as formatDate };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
declare const index$1_redirectTo: typeof redirectTo;
|
|
113
|
+
declare const index$1_resetRedirectHandler: typeof resetRedirectHandler;
|
|
114
|
+
declare const index$1_setRedirectHandler: typeof setRedirectHandler;
|
|
24
115
|
declare namespace index$1 {
|
|
25
|
-
export { index$
|
|
116
|
+
export { index$1_redirectTo as redirectTo, index$1_resetRedirectHandler as resetRedirectHandler, index$1_setRedirectHandler as setRedirectHandler };
|
|
26
117
|
}
|
|
27
118
|
|
|
28
|
-
|
|
119
|
+
type index_ChunkRecoveryPorts = ChunkRecoveryPorts;
|
|
120
|
+
declare const index_attemptChunkRecovery: typeof attemptChunkRecovery;
|
|
121
|
+
declare const index_clearChunkRecoveryFlag: typeof clearChunkRecoveryFlag;
|
|
122
|
+
declare const index_isChunkLoadError: typeof isChunkLoadError;
|
|
123
|
+
declare const index_reloadPage: typeof reloadPage;
|
|
29
124
|
declare namespace index {
|
|
30
|
-
export {
|
|
125
|
+
export { type index_ChunkRecoveryPorts as ChunkRecoveryPorts, index_attemptChunkRecovery as attemptChunkRecovery, index_clearChunkRecoveryFlag as clearChunkRecoveryFlag, index_isChunkLoadError as isChunkLoadError, index_reloadPage as reloadPage };
|
|
31
126
|
}
|
|
32
127
|
|
|
33
|
-
export { index$
|
|
128
|
+
export { type ChunkRecoveryPorts, attemptChunkRecovery, index as chunkRecovery, clearChunkRecoveryFlag, index$3 as error, index$2 as format, formatDate, getErrorMessage, isChunkLoadError, index$1 as navigation, redirectTo, reloadPage, resetRedirectHandler, setRedirectHandler };
|
package/dist/index.js
CHANGED
|
@@ -76,6 +76,141 @@ function formatDate(date, locale, options) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
// src/navigation/redirect.ts
|
|
80
|
+
var NAVIGATION_CHECK_DELAY_MS = 150;
|
|
81
|
+
var redirectHandler = null;
|
|
82
|
+
var queuedRedirects = [];
|
|
83
|
+
function normalizeRouterPath(path) {
|
|
84
|
+
const normalized = path.replace(/\/\([^/]+\)/g, "");
|
|
85
|
+
return normalized === "" ? "/" : normalized;
|
|
86
|
+
}
|
|
87
|
+
function getCurrentLocation() {
|
|
88
|
+
if (typeof window === "undefined") {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
92
|
+
}
|
|
93
|
+
function scheduleFallbackNavigation(before, normalized) {
|
|
94
|
+
setTimeout(() => {
|
|
95
|
+
try {
|
|
96
|
+
const after = getCurrentLocation();
|
|
97
|
+
if (after === before && typeof window !== "undefined") {
|
|
98
|
+
window.location.replace(normalized);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
}, NAVIGATION_CHECK_DELAY_MS);
|
|
103
|
+
}
|
|
104
|
+
function tryWindowNavigation(normalized) {
|
|
105
|
+
if (typeof window === "undefined") {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
window.location.replace(normalized);
|
|
110
|
+
return true;
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function setRedirectHandler(h) {
|
|
116
|
+
redirectHandler = h;
|
|
117
|
+
while (queuedRedirects.length > 0) {
|
|
118
|
+
const p = queuedRedirects.shift();
|
|
119
|
+
try {
|
|
120
|
+
if (typeof p === "string" && p.length > 0) {
|
|
121
|
+
h(normalizeRouterPath(p));
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function redirectTo(path) {
|
|
128
|
+
const normalized = normalizeRouterPath(path);
|
|
129
|
+
if (redirectHandler) {
|
|
130
|
+
const before = getCurrentLocation();
|
|
131
|
+
try {
|
|
132
|
+
redirectHandler(normalized);
|
|
133
|
+
if (typeof before === "string") {
|
|
134
|
+
scheduleFallbackNavigation(before, normalized);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (tryWindowNavigation(normalized)) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
queuedRedirects.push(normalized);
|
|
144
|
+
}
|
|
145
|
+
function resetRedirectHandler() {
|
|
146
|
+
redirectHandler = null;
|
|
147
|
+
queuedRedirects.length = 0;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// src/chunkRecovery/chunkRecovery.ts
|
|
151
|
+
var CHUNK_RELOAD_FLAG = "ui.chunkReload.attempted";
|
|
152
|
+
var CHUNK_ERROR_PATTERNS = [
|
|
153
|
+
/ChunkLoadError/i,
|
|
154
|
+
/Loading chunk [\w-]+ failed/i,
|
|
155
|
+
/Loading CSS chunk/i,
|
|
156
|
+
/Failed to fetch dynamically imported module/i,
|
|
157
|
+
/error loading dynamically imported module/i,
|
|
158
|
+
/Importing a module script failed/i,
|
|
159
|
+
/is not a valid JavaScript MIME type/i
|
|
160
|
+
];
|
|
161
|
+
function isChunkLoadError(error) {
|
|
162
|
+
if (!(error instanceof Error)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
if (error.name === "ChunkLoadError") {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
const haystack = `${error.name} ${error.message}`;
|
|
169
|
+
return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
170
|
+
}
|
|
171
|
+
function resolveStorage() {
|
|
172
|
+
if (typeof window === "undefined") {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
return window.sessionStorage;
|
|
177
|
+
} catch {
|
|
178
|
+
return void 0;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function defaultPorts() {
|
|
182
|
+
const storage = resolveStorage();
|
|
183
|
+
const hasWindow = typeof window !== "undefined";
|
|
184
|
+
return {
|
|
185
|
+
hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),
|
|
186
|
+
setFlag: () => {
|
|
187
|
+
storage?.setItem(CHUNK_RELOAD_FLAG, "1");
|
|
188
|
+
},
|
|
189
|
+
clearFlag: () => {
|
|
190
|
+
storage?.removeItem(CHUNK_RELOAD_FLAG);
|
|
191
|
+
},
|
|
192
|
+
reload: () => {
|
|
193
|
+
if (hasWindow) {
|
|
194
|
+
window.location.reload();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function attemptChunkRecovery(ports = defaultPorts()) {
|
|
200
|
+
if (ports.hasFlag()) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
ports.setFlag();
|
|
204
|
+
ports.reload();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
function clearChunkRecoveryFlag(ports = defaultPorts()) {
|
|
208
|
+
ports.clearFlag();
|
|
209
|
+
}
|
|
210
|
+
function reloadPage(ports = defaultPorts()) {
|
|
211
|
+
ports.reload();
|
|
212
|
+
}
|
|
213
|
+
|
|
79
214
|
// src/guards/index.ts
|
|
80
215
|
var guards_exports = {};
|
|
81
216
|
__export(guards_exports, {
|
|
@@ -105,18 +240,44 @@ __export(format_exports, {
|
|
|
105
240
|
formatDate: () => formatDate
|
|
106
241
|
});
|
|
107
242
|
|
|
243
|
+
// src/navigation/index.ts
|
|
244
|
+
var navigation_exports = {};
|
|
245
|
+
__export(navigation_exports, {
|
|
246
|
+
redirectTo: () => redirectTo,
|
|
247
|
+
resetRedirectHandler: () => resetRedirectHandler,
|
|
248
|
+
setRedirectHandler: () => setRedirectHandler
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// src/chunkRecovery/index.ts
|
|
252
|
+
var chunkRecovery_exports = {};
|
|
253
|
+
__export(chunkRecovery_exports, {
|
|
254
|
+
attemptChunkRecovery: () => attemptChunkRecovery,
|
|
255
|
+
clearChunkRecoveryFlag: () => clearChunkRecoveryFlag,
|
|
256
|
+
isChunkLoadError: () => isChunkLoadError,
|
|
257
|
+
reloadPage: () => reloadPage
|
|
258
|
+
});
|
|
259
|
+
|
|
108
260
|
exports.assertDefined = assertDefined;
|
|
109
261
|
exports.assertions = assertions_exports;
|
|
262
|
+
exports.attemptChunkRecovery = attemptChunkRecovery;
|
|
263
|
+
exports.chunkRecovery = chunkRecovery_exports;
|
|
264
|
+
exports.clearChunkRecoveryFlag = clearChunkRecoveryFlag;
|
|
110
265
|
exports.error = error_exports;
|
|
111
266
|
exports.format = format_exports;
|
|
112
267
|
exports.formatDate = formatDate;
|
|
113
268
|
exports.getErrorMessage = getErrorMessage;
|
|
114
269
|
exports.guards = guards_exports;
|
|
270
|
+
exports.isChunkLoadError = isChunkLoadError;
|
|
115
271
|
exports.isEmptyArray = isEmptyArray;
|
|
116
272
|
exports.isEmptyString = isEmptyString;
|
|
117
273
|
exports.isNotEmptyArray = isNotEmptyArray;
|
|
118
274
|
exports.isNotEmptyString = isNotEmptyString;
|
|
119
275
|
exports.isNullOrUndefined = isNullOrUndefined;
|
|
120
276
|
exports.isValueDefined = isValueDefined;
|
|
277
|
+
exports.navigation = navigation_exports;
|
|
278
|
+
exports.redirectTo = redirectTo;
|
|
279
|
+
exports.reloadPage = reloadPage;
|
|
280
|
+
exports.resetRedirectHandler = resetRedirectHandler;
|
|
281
|
+
exports.setRedirectHandler = setRedirectHandler;
|
|
121
282
|
//# sourceMappingURL=index.js.map
|
|
122
283
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/navigation/redirect.ts","../src/chunkRecovery/chunkRecovery.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/index.ts","../src/chunkRecovery/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","/**\n * Framework-agnostic redirect escape hatch.\n *\n * WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's\n * token-refresh failure path) needs to navigate, but has no access to a router\n * hook. The app registers its router's push/replace once via `setRedirectHandler`;\n * everything else calls `redirectTo` and does not care which router is mounted.\n *\n * Three layers of fallback, in order:\n * 1. the registered handler (the app's router),\n * 2. `window.location.replace` if no handler is registered yet,\n * 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired\n * before the router mounts is not silently dropped.\n *\n * Layer 1 additionally schedules a watchdog: some routers accept a `push` for a\n * route they cannot resolve and leave the URL untouched, which strands the user on\n * the page they were supposed to leave. If the location has not changed after\n * `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.\n *\n * Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and\n * katalogos-web. Pure DOM + timers — no React, no router, no framework import.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020`. Adding DOM globally would let every\n * other util in the package reach for browser globals that are absent at runtime\n * on the server. Declaring the ~4 properties used here keeps that door shut.\n */\ninterface BrowserWindow {\n location: {\n pathname: string;\n search: string;\n hash: string;\n replace: (url: string) => void;\n };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** Delay in ms before checking whether the registered handler actually navigated. */\nconst NAVIGATION_CHECK_DELAY_MS = 150;\n\nlet redirectHandler: ((path: string) => void) | null = null;\nconst queuedRedirects: string[] = [];\n\n/**\n * Strips Expo-Router group segments — `/(protected)/settings` → `/settings`.\n * Group folders are a routing-config device and never appear in a real URL, so a\n * raw group path handed to `window.location` would 404.\n */\nfunction normalizeRouterPath(path: string): string {\n const normalized = path.replace(/\\/\\([^/]+\\)/g, '');\n return normalized === '' ? '/' : normalized;\n}\n\n/** Current location as path+search+hash, or null outside a browser (SSR/native). */\nfunction getCurrentLocation(): string | null {\n if (typeof window === 'undefined') {return null;}\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n/** Force a hard navigation if the registered handler left the location unchanged. */\nfunction scheduleFallbackNavigation(before: string, normalized: string): void {\n setTimeout(() => {\n try {\n const after = getCurrentLocation();\n if (after === before && typeof window !== 'undefined') {window.location.replace(normalized);}\n } catch {\n /* ignore — window may have gone away (unmount/teardown) */\n }\n }, NAVIGATION_CHECK_DELAY_MS);\n}\n\n/** Attempt a hard browser navigation. Returns false outside a browser or on throw. */\nfunction tryWindowNavigation(normalized: string): boolean {\n if (typeof window === 'undefined') {return false;}\n try {\n window.location.replace(normalized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Register the app's router navigate function and drain any redirects that were\n * queued before it mounted. Call once, from the root layout.\n */\nexport function setRedirectHandler(h: (path: string) => void): void {\n redirectHandler = h;\n while (queuedRedirects.length > 0) {\n const p = queuedRedirects.shift();\n try {\n if (typeof p === 'string' && p.length > 0) {h(normalizeRouterPath(p));}\n } catch {\n /* swallow; the handler may still not be ready */\n }\n }\n}\n\n/**\n * Navigate to `path` from anywhere, including outside React.\n * Safe to call before the router mounts — the redirect is queued and replayed.\n */\nexport function redirectTo(path: string): void {\n const normalized = normalizeRouterPath(path);\n if (redirectHandler) {\n const before = getCurrentLocation();\n try {\n redirectHandler(normalized);\n if (typeof before === 'string') {scheduleFallbackNavigation(before, normalized);}\n return;\n } catch {\n /* fall through to a hard navigation */\n }\n }\n if (tryWindowNavigation(normalized)) {return;}\n queuedRedirects.push(normalized);\n}\n\n/**\n * Clear the registered handler and any queued redirects.\n * Exists for tests — module state otherwise leaks between cases.\n */\nexport function resetRedirectHandler(): void {\n redirectHandler = null;\n queuedRedirects.length = 0;\n}\n","/**\n * Detection + one-shot auto-recovery for stale-chunk load failures.\n *\n * WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold\n * a stale `index.html` that references hashed JS chunks which now 404 — surfacing\n * as a `ChunkLoadError` / \"failed to fetch dynamically imported module\". A single\n * guarded `location.reload()` re-fetches the fresh entry + chunks and the app\n * recovers with no user-visible error. The sessionStorage one-shot flag prevents\n * a reload LOOP if the reload does not fix it (then the boundary shows a manual\n * Reload action instead).\n *\n * Pure + port-injectable: detection is a pure predicate; the storage + reload\n * side effects are ports (default to `window`) so unit tests run deterministically\n * in jsdom without a real navigation.\n *\n * Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in\n * kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).\n * The original file's own header predicted this move. Pairs with\n * `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its\n * `retryable` / `onMount` / `recover` injections.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020` (see `navigation/redirect.ts`, which\n * makes the same call). Declaring the few properties used here keeps every other\n * util in the package from reaching for browser globals absent on the server.\n */\ninterface ChunkRecoveryStorage {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n}\n\ninterface BrowserWindow {\n sessionStorage: ChunkRecoveryStorage;\n location: { reload: () => void };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** sessionStorage key for the one-shot auto-reload guard. */\nconst CHUNK_RELOAD_FLAG = 'ui.chunkReload.attempted';\n\n/** Messages/names emitted by webpack/metro/vite/Safari for a stale-chunk failure. */\nconst CHUNK_ERROR_PATTERNS: readonly RegExp[] = [\n /ChunkLoadError/i,\n /Loading chunk [\\w-]+ failed/i,\n /Loading CSS chunk/i,\n /Failed to fetch dynamically imported module/i,\n /error loading dynamically imported module/i,\n /Importing a module script failed/i,\n /is not a valid JavaScript MIME type/i,\n];\n\n/** Injectable side-effect ports; default to the real `window`. */\nexport interface ChunkRecoveryPorts {\n hasFlag: () => boolean;\n setFlag: () => void;\n clearFlag: () => void;\n reload: () => void;\n}\n\n/** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n if (error.name === 'ChunkLoadError') {\n return true;\n }\n const haystack = `${error.name} ${error.message}`;\n return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));\n}\n\nfunction resolveStorage(): ChunkRecoveryStorage | undefined {\n if (typeof window === 'undefined') {\n return undefined;\n }\n try {\n return window.sessionStorage;\n } catch {\n return undefined;\n }\n}\n\nfunction defaultPorts(): ChunkRecoveryPorts {\n const storage = resolveStorage();\n const hasWindow = typeof window !== 'undefined';\n return {\n hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),\n setFlag: (): void => {\n storage?.setItem(CHUNK_RELOAD_FLAG, '1');\n },\n clearFlag: (): void => {\n storage?.removeItem(CHUNK_RELOAD_FLAG);\n },\n reload: (): void => {\n if (hasWindow) {\n window.location.reload();\n }\n },\n };\n}\n\n/**\n * Attempt a one-shot reload to recover from a stale-chunk error.\n * Returns `true` when a reload was triggered, `false` when the one-shot guard was\n * already spent (the caller then shows a manual Reload action, never a loop).\n */\nexport function attemptChunkRecovery(ports: ChunkRecoveryPorts = defaultPorts()): boolean {\n if (ports.hasFlag()) {\n return false;\n }\n ports.setFlag();\n ports.reload();\n return true;\n}\n\n/** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */\nexport function clearChunkRecoveryFlag(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.clearFlag();\n}\n\n/** Manually reload the page (the Reload action). */\nexport function reloadPage(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.reload();\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n","export { setRedirectHandler, redirectTo, resetRedirectHandler } from './redirect';\n","export {\n isChunkLoadError,\n attemptChunkRecovery,\n clearChunkRecoveryFlag,\n reloadPage,\n} from './chunkRecovery';\nexport type { ChunkRecoveryPorts } from './chunkRecovery';\n"]}
|
package/dist/index.mjs
CHANGED
|
@@ -74,6 +74,141 @@ function formatDate(date, locale, options) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// src/navigation/redirect.ts
|
|
78
|
+
var NAVIGATION_CHECK_DELAY_MS = 150;
|
|
79
|
+
var redirectHandler = null;
|
|
80
|
+
var queuedRedirects = [];
|
|
81
|
+
function normalizeRouterPath(path) {
|
|
82
|
+
const normalized = path.replace(/\/\([^/]+\)/g, "");
|
|
83
|
+
return normalized === "" ? "/" : normalized;
|
|
84
|
+
}
|
|
85
|
+
function getCurrentLocation() {
|
|
86
|
+
if (typeof window === "undefined") {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
90
|
+
}
|
|
91
|
+
function scheduleFallbackNavigation(before, normalized) {
|
|
92
|
+
setTimeout(() => {
|
|
93
|
+
try {
|
|
94
|
+
const after = getCurrentLocation();
|
|
95
|
+
if (after === before && typeof window !== "undefined") {
|
|
96
|
+
window.location.replace(normalized);
|
|
97
|
+
}
|
|
98
|
+
} catch {
|
|
99
|
+
}
|
|
100
|
+
}, NAVIGATION_CHECK_DELAY_MS);
|
|
101
|
+
}
|
|
102
|
+
function tryWindowNavigation(normalized) {
|
|
103
|
+
if (typeof window === "undefined") {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
window.location.replace(normalized);
|
|
108
|
+
return true;
|
|
109
|
+
} catch {
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function setRedirectHandler(h) {
|
|
114
|
+
redirectHandler = h;
|
|
115
|
+
while (queuedRedirects.length > 0) {
|
|
116
|
+
const p = queuedRedirects.shift();
|
|
117
|
+
try {
|
|
118
|
+
if (typeof p === "string" && p.length > 0) {
|
|
119
|
+
h(normalizeRouterPath(p));
|
|
120
|
+
}
|
|
121
|
+
} catch {
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function redirectTo(path) {
|
|
126
|
+
const normalized = normalizeRouterPath(path);
|
|
127
|
+
if (redirectHandler) {
|
|
128
|
+
const before = getCurrentLocation();
|
|
129
|
+
try {
|
|
130
|
+
redirectHandler(normalized);
|
|
131
|
+
if (typeof before === "string") {
|
|
132
|
+
scheduleFallbackNavigation(before, normalized);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
} catch {
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (tryWindowNavigation(normalized)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
queuedRedirects.push(normalized);
|
|
142
|
+
}
|
|
143
|
+
function resetRedirectHandler() {
|
|
144
|
+
redirectHandler = null;
|
|
145
|
+
queuedRedirects.length = 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/chunkRecovery/chunkRecovery.ts
|
|
149
|
+
var CHUNK_RELOAD_FLAG = "ui.chunkReload.attempted";
|
|
150
|
+
var CHUNK_ERROR_PATTERNS = [
|
|
151
|
+
/ChunkLoadError/i,
|
|
152
|
+
/Loading chunk [\w-]+ failed/i,
|
|
153
|
+
/Loading CSS chunk/i,
|
|
154
|
+
/Failed to fetch dynamically imported module/i,
|
|
155
|
+
/error loading dynamically imported module/i,
|
|
156
|
+
/Importing a module script failed/i,
|
|
157
|
+
/is not a valid JavaScript MIME type/i
|
|
158
|
+
];
|
|
159
|
+
function isChunkLoadError(error) {
|
|
160
|
+
if (!(error instanceof Error)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
if (error.name === "ChunkLoadError") {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const haystack = `${error.name} ${error.message}`;
|
|
167
|
+
return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));
|
|
168
|
+
}
|
|
169
|
+
function resolveStorage() {
|
|
170
|
+
if (typeof window === "undefined") {
|
|
171
|
+
return void 0;
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
return window.sessionStorage;
|
|
175
|
+
} catch {
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function defaultPorts() {
|
|
180
|
+
const storage = resolveStorage();
|
|
181
|
+
const hasWindow = typeof window !== "undefined";
|
|
182
|
+
return {
|
|
183
|
+
hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),
|
|
184
|
+
setFlag: () => {
|
|
185
|
+
storage?.setItem(CHUNK_RELOAD_FLAG, "1");
|
|
186
|
+
},
|
|
187
|
+
clearFlag: () => {
|
|
188
|
+
storage?.removeItem(CHUNK_RELOAD_FLAG);
|
|
189
|
+
},
|
|
190
|
+
reload: () => {
|
|
191
|
+
if (hasWindow) {
|
|
192
|
+
window.location.reload();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function attemptChunkRecovery(ports = defaultPorts()) {
|
|
198
|
+
if (ports.hasFlag()) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
ports.setFlag();
|
|
202
|
+
ports.reload();
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
function clearChunkRecoveryFlag(ports = defaultPorts()) {
|
|
206
|
+
ports.clearFlag();
|
|
207
|
+
}
|
|
208
|
+
function reloadPage(ports = defaultPorts()) {
|
|
209
|
+
ports.reload();
|
|
210
|
+
}
|
|
211
|
+
|
|
77
212
|
// src/guards/index.ts
|
|
78
213
|
var guards_exports = {};
|
|
79
214
|
__export(guards_exports, {
|
|
@@ -103,6 +238,23 @@ __export(format_exports, {
|
|
|
103
238
|
formatDate: () => formatDate
|
|
104
239
|
});
|
|
105
240
|
|
|
106
|
-
|
|
241
|
+
// src/navigation/index.ts
|
|
242
|
+
var navigation_exports = {};
|
|
243
|
+
__export(navigation_exports, {
|
|
244
|
+
redirectTo: () => redirectTo,
|
|
245
|
+
resetRedirectHandler: () => resetRedirectHandler,
|
|
246
|
+
setRedirectHandler: () => setRedirectHandler
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// src/chunkRecovery/index.ts
|
|
250
|
+
var chunkRecovery_exports = {};
|
|
251
|
+
__export(chunkRecovery_exports, {
|
|
252
|
+
attemptChunkRecovery: () => attemptChunkRecovery,
|
|
253
|
+
clearChunkRecoveryFlag: () => clearChunkRecoveryFlag,
|
|
254
|
+
isChunkLoadError: () => isChunkLoadError,
|
|
255
|
+
reloadPage: () => reloadPage
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
export { assertDefined, assertions_exports as assertions, attemptChunkRecovery, chunkRecovery_exports as chunkRecovery, clearChunkRecoveryFlag, error_exports as error, format_exports as format, formatDate, getErrorMessage, guards_exports as guards, isChunkLoadError, isEmptyArray, isEmptyString, isNotEmptyArray, isNotEmptyString, isNullOrUndefined, isValueDefined, navigation_exports as navigation, redirectTo, reloadPage, resetRedirectHandler, setRedirectHandler };
|
|
107
259
|
//# sourceMappingURL=index.mjs.map
|
|
108
260
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/guards/isValueDefined.ts","../src/guards/isNotEmptyArray.ts","../src/guards/isNotEmptyString.ts","../src/guards/isNullOrUndefined.ts","../src/guards/isEmptyArray.ts","../src/guards/isEmptyString.ts","../src/assertions/assertDefined.ts","../src/error/getErrorMessage.ts","../src/format/formatDate.ts","../src/navigation/redirect.ts","../src/chunkRecovery/chunkRecovery.ts","../src/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/index.ts","../src/chunkRecovery/index.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["/**\r\n * Type guard that checks if a value is neither null nor undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is defined (not null and not undefined)\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isValueDefined(value)) {\r\n * // value is narrowed to string\r\n * console.log(value.length);\r\n * }\r\n * ```\r\n */\r\nexport function isValueDefined<T>(value: T | null | undefined): value is T {\r\n return value !== undefined && value !== null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is a non-empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with at least one element\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] | undefined = getItems();\r\n * if (isNotEmptyArray(items)) {\r\n * // items is narrowed to string[] with at least one element\r\n * console.log(items[0]); // Safe to access\r\n * }\r\n * ```\r\n */\r\nexport function isNotEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: number } {\r\n return Array.isArray(value) && value.length > 0;\r\n}\r\n","/**\r\n * Checks if a value is a non-empty string (after trimming whitespace).\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is a string with non-whitespace content\r\n *\r\n * @example\r\n * ```typescript\r\n * isNotEmptyString('hello'); // true\r\n * isNotEmptyString(' '); // false\r\n * isNotEmptyString(''); // false\r\n * isNotEmptyString(undefined); // false\r\n * ```\r\n */\r\nexport function isNotEmptyString(value: string | null | undefined): value is string {\r\n return typeof value === 'string' && value.trim().length > 0;\r\n}\r\n","/**\r\n * Type guard that checks if a value is null or undefined.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const value: string | null | undefined = getValue();\r\n * if (isNullOrUndefined(value)) {\r\n * // value is narrowed to null | undefined\r\n * console.log('Value is not set');\r\n * }\r\n * ```\r\n */\r\nexport function isNullOrUndefined(value: unknown): value is null | undefined {\r\n return value === undefined || value === null;\r\n}\r\n","/**\r\n * Type guard that checks if a value is an empty array.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an array with zero elements\r\n *\r\n * @example\r\n * ```typescript\r\n * const items: string[] = getItems();\r\n * if (isEmptyArray(items)) {\r\n * console.log('No items found');\r\n * }\r\n * ```\r\n */\r\nexport function isEmptyArray<T>(\r\n value: readonly T[] | null | undefined\r\n): value is readonly T[] & { readonly length: 0 } {\r\n return Array.isArray(value) && value.length === 0;\r\n}\r\n","/**\r\n * Checks if a value is an empty string or contains only whitespace.\r\n *\r\n * @param value - The value to check\r\n * @returns True if the value is an empty string or whitespace-only\r\n *\r\n * @example\r\n * ```typescript\r\n * isEmptyString(''); // true\r\n * isEmptyString(' '); // true\r\n * isEmptyString('hello'); // false\r\n * isEmptyString(undefined); // false (not a string)\r\n * isEmptyString(null); // false (not a string)\r\n * ```\r\n */\r\nexport function isEmptyString(value: string | null | undefined): boolean {\r\n return typeof value === 'string' && value.trim().length === 0;\r\n}\r\n","/**\r\n * Asserts that a value is defined (not null or undefined).\r\n * Throws an error if the value is null or undefined.\r\n *\r\n * @param value - The value to assert\r\n * @param message - Optional error message\r\n * @throws Error if value is null or undefined\r\n *\r\n * @example\r\n * ```typescript\r\n * const user = getUser(); // User | null\r\n * assertDefined(user, 'User must exist');\r\n * // user is now narrowed to User\r\n * console.log(user.name);\r\n * ```\r\n */\r\nexport function assertDefined<T>(\r\n value: T | null | undefined,\r\n message?: string\r\n): asserts value is T {\r\n if (value === undefined || value === null) {\r\n throw new Error(message ?? 'Value must be defined');\r\n }\r\n}\r\n","import { isValueDefined } from '../guards/isValueDefined';\n\ninterface ErrorWithMessage {\n message?: unknown;\n}\n\nfunction isErrorWithMessage(value: unknown): value is ErrorWithMessage {\n return typeof value === 'object' && isValueDefined(value) && 'message' in value;\n}\n\n/**\n * Extracts a human-readable error message from various error shapes\n * (Error, string, or an object with a `message` property).\n *\n * @param value - The error value.\n * @param fallback - Returned when no message can be extracted (default: 'Unknown error').\n */\nexport function getErrorMessage(value: unknown, fallback = 'Unknown error'): string {\n if (value instanceof Error) {return value.message;}\n if (typeof value === 'string') {return value;}\n if (isErrorWithMessage(value)) {\n const message = value.message;\n if (typeof message === 'string' && message.length > 0) {return message;}\n }\n return fallback;\n}\n","/**\n * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).\n * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.\n *\n * @example formatDate(new Date(), 'en') // \"6/15/2024\"\n * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // \"June 2024\"\n * @example formatDate(null, 'en') // \"\"\n */\nexport function formatDate(\n date: Date | null | undefined,\n locale: string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n if (date === null || date === undefined) {return '';}\n\n const resolvedLocale = locale !== '' ? locale : 'en';\n\n try {\n return new Intl.DateTimeFormat(resolvedLocale, options).format(date);\n } catch {\n // Fallback to ISO date if formatting fails (invalid locale/options).\n return date.toISOString().split('T')[0] ?? '';\n }\n}\n","/**\n * Framework-agnostic redirect escape hatch.\n *\n * WHY THIS EXISTS: non-React code (an axios 401 interceptor, an auth provider's\n * token-refresh failure path) needs to navigate, but has no access to a router\n * hook. The app registers its router's push/replace once via `setRedirectHandler`;\n * everything else calls `redirectTo` and does not care which router is mounted.\n *\n * Three layers of fallback, in order:\n * 1. the registered handler (the app's router),\n * 2. `window.location.replace` if no handler is registered yet,\n * 3. a queue, drained on the next `setRedirectHandler` call — so a redirect fired\n * before the router mounts is not silently dropped.\n *\n * Layer 1 additionally schedules a watchdog: some routers accept a `push` for a\n * route they cannot resolve and leave the URL untouched, which strands the user on\n * the page they were supposed to leave. If the location has not changed after\n * `NAVIGATION_CHECK_DELAY_MS`, a hard `window.location.replace` is forced.\n *\n * Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and\n * katalogos-web. Pure DOM + timers — no React, no router, no framework import.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020`. Adding DOM globally would let every\n * other util in the package reach for browser globals that are absent at runtime\n * on the server. Declaring the ~4 properties used here keeps that door shut.\n */\ninterface BrowserWindow {\n location: {\n pathname: string;\n search: string;\n hash: string;\n replace: (url: string) => void;\n };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** Delay in ms before checking whether the registered handler actually navigated. */\nconst NAVIGATION_CHECK_DELAY_MS = 150;\n\nlet redirectHandler: ((path: string) => void) | null = null;\nconst queuedRedirects: string[] = [];\n\n/**\n * Strips Expo-Router group segments — `/(protected)/settings` → `/settings`.\n * Group folders are a routing-config device and never appear in a real URL, so a\n * raw group path handed to `window.location` would 404.\n */\nfunction normalizeRouterPath(path: string): string {\n const normalized = path.replace(/\\/\\([^/]+\\)/g, '');\n return normalized === '' ? '/' : normalized;\n}\n\n/** Current location as path+search+hash, or null outside a browser (SSR/native). */\nfunction getCurrentLocation(): string | null {\n if (typeof window === 'undefined') {return null;}\n return `${window.location.pathname}${window.location.search}${window.location.hash}`;\n}\n\n/** Force a hard navigation if the registered handler left the location unchanged. */\nfunction scheduleFallbackNavigation(before: string, normalized: string): void {\n setTimeout(() => {\n try {\n const after = getCurrentLocation();\n if (after === before && typeof window !== 'undefined') {window.location.replace(normalized);}\n } catch {\n /* ignore — window may have gone away (unmount/teardown) */\n }\n }, NAVIGATION_CHECK_DELAY_MS);\n}\n\n/** Attempt a hard browser navigation. Returns false outside a browser or on throw. */\nfunction tryWindowNavigation(normalized: string): boolean {\n if (typeof window === 'undefined') {return false;}\n try {\n window.location.replace(normalized);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Register the app's router navigate function and drain any redirects that were\n * queued before it mounted. Call once, from the root layout.\n */\nexport function setRedirectHandler(h: (path: string) => void): void {\n redirectHandler = h;\n while (queuedRedirects.length > 0) {\n const p = queuedRedirects.shift();\n try {\n if (typeof p === 'string' && p.length > 0) {h(normalizeRouterPath(p));}\n } catch {\n /* swallow; the handler may still not be ready */\n }\n }\n}\n\n/**\n * Navigate to `path` from anywhere, including outside React.\n * Safe to call before the router mounts — the redirect is queued and replayed.\n */\nexport function redirectTo(path: string): void {\n const normalized = normalizeRouterPath(path);\n if (redirectHandler) {\n const before = getCurrentLocation();\n try {\n redirectHandler(normalized);\n if (typeof before === 'string') {scheduleFallbackNavigation(before, normalized);}\n return;\n } catch {\n /* fall through to a hard navigation */\n }\n }\n if (tryWindowNavigation(normalized)) {return;}\n queuedRedirects.push(normalized);\n}\n\n/**\n * Clear the registered handler and any queued redirects.\n * Exists for tests — module state otherwise leaks between cases.\n */\nexport function resetRedirectHandler(): void {\n redirectHandler = null;\n queuedRedirects.length = 0;\n}\n","/**\n * Detection + one-shot auto-recovery for stale-chunk load failures.\n *\n * WHY (UX Move 3, would have masked P0-01): after a deploy, the browser may hold\n * a stale `index.html` that references hashed JS chunks which now 404 — surfacing\n * as a `ChunkLoadError` / \"failed to fetch dynamically imported module\". A single\n * guarded `location.reload()` re-fetches the fresh entry + chunks and the app\n * recovers with no user-visible error. The sessionStorage one-shot flag prevents\n * a reload LOOP if the reload does not fix it (then the boundary shows a manual\n * Reload action instead).\n *\n * Pure + port-injectable: detection is a pure predicate; the storage + reload\n * side effects are ports (default to `window`) so unit tests run deterministically\n * in jsdom without a real navigation.\n *\n * Promoted from the byte-identical `src/utils/chunkLoadRecovery.ts` triplets in\n * kefi-web, erevna-web and katalogos-web (extract-on-2nd-use; this was the 3rd).\n * The original file's own header predicted this move. Pairs with\n * `<AppErrorBoundary>` from `@dloizides/ui-feedback`, which consumes these as its\n * `retryable` / `onMount` / `recover` injections.\n */\n\n/**\n * The slice of `window` this module touches, declared structurally.\n *\n * WHY NOT `lib: [\"DOM\"]`: `@dloizides/utils` ships to Node consumers too, and its\n * tsconfig deliberately lists only `ES2020` (see `navigation/redirect.ts`, which\n * makes the same call). Declaring the few properties used here keeps every other\n * util in the package from reaching for browser globals absent on the server.\n */\ninterface ChunkRecoveryStorage {\n getItem: (key: string) => string | null;\n setItem: (key: string, value: string) => void;\n removeItem: (key: string) => void;\n}\n\ninterface BrowserWindow {\n sessionStorage: ChunkRecoveryStorage;\n location: { reload: () => void };\n}\n\ndeclare const window: BrowserWindow | undefined;\n\n/** sessionStorage key for the one-shot auto-reload guard. */\nconst CHUNK_RELOAD_FLAG = 'ui.chunkReload.attempted';\n\n/** Messages/names emitted by webpack/metro/vite/Safari for a stale-chunk failure. */\nconst CHUNK_ERROR_PATTERNS: readonly RegExp[] = [\n /ChunkLoadError/i,\n /Loading chunk [\\w-]+ failed/i,\n /Loading CSS chunk/i,\n /Failed to fetch dynamically imported module/i,\n /error loading dynamically imported module/i,\n /Importing a module script failed/i,\n /is not a valid JavaScript MIME type/i,\n];\n\n/** Injectable side-effect ports; default to the real `window`. */\nexport interface ChunkRecoveryPorts {\n hasFlag: () => boolean;\n setFlag: () => void;\n clearFlag: () => void;\n reload: () => void;\n}\n\n/** True when `error` looks like a stale-chunk / failed-dynamic-import failure. */\nexport function isChunkLoadError(error: unknown): boolean {\n if (!(error instanceof Error)) {\n return false;\n }\n if (error.name === 'ChunkLoadError') {\n return true;\n }\n const haystack = `${error.name} ${error.message}`;\n return CHUNK_ERROR_PATTERNS.some((pattern) => pattern.test(haystack));\n}\n\nfunction resolveStorage(): ChunkRecoveryStorage | undefined {\n if (typeof window === 'undefined') {\n return undefined;\n }\n try {\n return window.sessionStorage;\n } catch {\n return undefined;\n }\n}\n\nfunction defaultPorts(): ChunkRecoveryPorts {\n const storage = resolveStorage();\n const hasWindow = typeof window !== 'undefined';\n return {\n hasFlag: () => Boolean(storage?.getItem(CHUNK_RELOAD_FLAG)),\n setFlag: (): void => {\n storage?.setItem(CHUNK_RELOAD_FLAG, '1');\n },\n clearFlag: (): void => {\n storage?.removeItem(CHUNK_RELOAD_FLAG);\n },\n reload: (): void => {\n if (hasWindow) {\n window.location.reload();\n }\n },\n };\n}\n\n/**\n * Attempt a one-shot reload to recover from a stale-chunk error.\n * Returns `true` when a reload was triggered, `false` when the one-shot guard was\n * already spent (the caller then shows a manual Reload action, never a loop).\n */\nexport function attemptChunkRecovery(ports: ChunkRecoveryPorts = defaultPorts()): boolean {\n if (ports.hasFlag()) {\n return false;\n }\n ports.setFlag();\n ports.reload();\n return true;\n}\n\n/** Clear the one-shot guard after a clean load so a FUTURE deploy can auto-recover. */\nexport function clearChunkRecoveryFlag(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.clearFlag();\n}\n\n/** Manually reload the page (the Reload action). */\nexport function reloadPage(ports: ChunkRecoveryPorts = defaultPorts()): void {\n ports.reload();\n}\n","export { isValueDefined } from './isValueDefined';\r\nexport { isNotEmptyArray } from './isNotEmptyArray';\r\nexport { isNotEmptyString } from './isNotEmptyString';\r\nexport { isNullOrUndefined } from './isNullOrUndefined';\r\nexport { isEmptyArray } from './isEmptyArray';\r\nexport { isEmptyString } from './isEmptyString';\r\n","export { assertDefined } from './assertDefined';\r\n","export { getErrorMessage } from './getErrorMessage';\n","export { formatDate } from './formatDate';\n","export { setRedirectHandler, redirectTo, resetRedirectHandler } from './redirect';\n","export {\n isChunkLoadError,\n attemptChunkRecovery,\n clearChunkRecoveryFlag,\n reloadPage,\n} from './chunkRecovery';\nexport type { ChunkRecoveryPorts } from './chunkRecovery';\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dloizides/utils",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Type-safe utility functions for TypeScript projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -79,7 +79,6 @@
|
|
|
79
79
|
"deps:licenses": "npx license-checker --onlyAllow \"MIT;Apache-2.0;ISC;BSD-2-Clause;BSD-3-Clause;0BSD;Unlicense;CC0-1.0\"",
|
|
80
80
|
"deps:health": "npm run deps:outdated && npm run deps:unused",
|
|
81
81
|
"prepublishOnly": "npm run clean && npm run build && npm run test",
|
|
82
|
-
"version": "npm run build",
|
|
83
82
|
"postversion": "git push && git push --tags"
|
|
84
83
|
},
|
|
85
84
|
"overrides": {
|