@dloizides/utils 1.0.2 → 1.2.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 CHANGED
@@ -5,6 +5,34 @@ 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.2.0] - 2026-07-18
9
+
10
+ ### Added
11
+
12
+ - `setRedirectHandler(handler)` / `redirectTo(path)` / `resetRedirectHandler()` — a
13
+ framework-agnostic redirect escape hatch for non-React callers (axios 401 interceptors,
14
+ auth providers) that need to navigate without a router hook. Falls back through
15
+ registered handler → `window.location.replace` → a queue drained when the router mounts,
16
+ and forces a hard navigation if the router silently failed to change the URL.
17
+ Promoted from the byte-identical `lib/navigation.ts` twins in erevna-web and
18
+ katalogos-web (de-fork wave W1.1). Also exported as the `navigation` namespace.
19
+
20
+ ### Fixed
21
+
22
+ - The promoted watchdog guarded its `window` access with `try`/`catch` only; it now checks
23
+ `typeof window !== 'undefined'` explicitly, so a non-browser host cannot reach a
24
+ `ReferenceError` path at all. Surfaced by porting the code into a package whose tsconfig
25
+ has no `DOM` lib.
26
+
27
+ ## [1.1.0] - 2026-06-15
28
+
29
+ ### Added
30
+
31
+ - `getErrorMessage(value, fallback?)` — extract a human-readable message from any error shape
32
+ (Error / string / `{message}`). Consolidates the duplicated app-side `errorMessage` helper.
33
+ - `formatDate(date, locale, options?)` — locale-aware date formatting (pure; caller supplies the
34
+ locale, e.g. from i18n). Backs the apps' `FD` helper. (Capability Wave C1, batch 6.)
35
+
8
36
  ## [1.0.0] - 2024-01-27
9
37
 
10
38
  ### Added
package/dist/index.d.mts CHANGED
@@ -1,2 +1,78 @@
1
1
  export { i as guards, a as isEmptyArray, b as isEmptyString, c as isNotEmptyArray, d as isNotEmptyString, e as isNullOrUndefined, f as isValueDefined } from './index-Dge_3DNw.mjs';
2
2
  export { a as assertDefined, i as assertions } from './index-DmagV9KM.mjs';
3
+
4
+ /**
5
+ * Extracts a human-readable error message from various error shapes
6
+ * (Error, string, or an object with a `message` property).
7
+ *
8
+ * @param value - The error value.
9
+ * @param fallback - Returned when no message can be extracted (default: 'Unknown error').
10
+ */
11
+ declare function getErrorMessage(value: unknown, fallback?: string): string;
12
+
13
+ /**
14
+ * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).
15
+ * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.
16
+ *
17
+ * @example formatDate(new Date(), 'en') // "6/15/2024"
18
+ * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // "June 2024"
19
+ * @example formatDate(null, 'en') // ""
20
+ */
21
+ declare function formatDate(date: Date | null | undefined, locale: string, options?: Intl.DateTimeFormatOptions): string;
22
+
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
+ declare const index$2_getErrorMessage: typeof getErrorMessage;
62
+ declare namespace index$2 {
63
+ export { index$2_getErrorMessage as getErrorMessage };
64
+ }
65
+
66
+ declare const index$1_formatDate: typeof formatDate;
67
+ declare namespace index$1 {
68
+ export { index$1_formatDate as formatDate };
69
+ }
70
+
71
+ declare const index_redirectTo: typeof redirectTo;
72
+ declare const index_resetRedirectHandler: typeof resetRedirectHandler;
73
+ declare const index_setRedirectHandler: typeof setRedirectHandler;
74
+ declare namespace index {
75
+ export { index_redirectTo as redirectTo, index_resetRedirectHandler as resetRedirectHandler, index_setRedirectHandler as setRedirectHandler };
76
+ }
77
+
78
+ export { index$2 as error, index$1 as format, formatDate, getErrorMessage, index as navigation, redirectTo, resetRedirectHandler, setRedirectHandler };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,78 @@
1
1
  export { i as guards, a as isEmptyArray, b as isEmptyString, c as isNotEmptyArray, d as isNotEmptyString, e as isNullOrUndefined, f as isValueDefined } from './index-Dge_3DNw.js';
2
2
  export { a as assertDefined, i as assertions } from './index-DmagV9KM.js';
3
+
4
+ /**
5
+ * Extracts a human-readable error message from various error shapes
6
+ * (Error, string, or an object with a `message` property).
7
+ *
8
+ * @param value - The error value.
9
+ * @param fallback - Returned when no message can be extracted (default: 'Unknown error').
10
+ */
11
+ declare function getErrorMessage(value: unknown, fallback?: string): string;
12
+
13
+ /**
14
+ * Locale-aware date formatting (pure — the caller supplies the locale, e.g. from i18n).
15
+ * Returns '' for null/undefined; falls back to the ISO date if Intl formatting throws.
16
+ *
17
+ * @example formatDate(new Date(), 'en') // "6/15/2024"
18
+ * @example formatDate(new Date(), 'en', { year: 'numeric', month: 'long' }) // "June 2024"
19
+ * @example formatDate(null, 'en') // ""
20
+ */
21
+ declare function formatDate(date: Date | null | undefined, locale: string, options?: Intl.DateTimeFormatOptions): string;
22
+
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
+ declare const index$2_getErrorMessage: typeof getErrorMessage;
62
+ declare namespace index$2 {
63
+ export { index$2_getErrorMessage as getErrorMessage };
64
+ }
65
+
66
+ declare const index$1_formatDate: typeof formatDate;
67
+ declare namespace index$1 {
68
+ export { index$1_formatDate as formatDate };
69
+ }
70
+
71
+ declare const index_redirectTo: typeof redirectTo;
72
+ declare const index_resetRedirectHandler: typeof resetRedirectHandler;
73
+ declare const index_setRedirectHandler: typeof setRedirectHandler;
74
+ declare namespace index {
75
+ export { index_redirectTo as redirectTo, index_resetRedirectHandler as resetRedirectHandler, index_setRedirectHandler as setRedirectHandler };
76
+ }
77
+
78
+ export { index$2 as error, index$1 as format, formatDate, getErrorMessage, index as navigation, redirectTo, resetRedirectHandler, setRedirectHandler };
package/dist/index.js CHANGED
@@ -43,6 +43,110 @@ function assertDefined(value, message) {
43
43
  }
44
44
  }
45
45
 
46
+ // src/error/getErrorMessage.ts
47
+ function isErrorWithMessage(value) {
48
+ return typeof value === "object" && isValueDefined(value) && "message" in value;
49
+ }
50
+ function getErrorMessage(value, fallback = "Unknown error") {
51
+ if (value instanceof Error) {
52
+ return value.message;
53
+ }
54
+ if (typeof value === "string") {
55
+ return value;
56
+ }
57
+ if (isErrorWithMessage(value)) {
58
+ const message = value.message;
59
+ if (typeof message === "string" && message.length > 0) {
60
+ return message;
61
+ }
62
+ }
63
+ return fallback;
64
+ }
65
+
66
+ // src/format/formatDate.ts
67
+ function formatDate(date, locale, options) {
68
+ if (date === null || date === void 0) {
69
+ return "";
70
+ }
71
+ const resolvedLocale = locale !== "" ? locale : "en";
72
+ try {
73
+ return new Intl.DateTimeFormat(resolvedLocale, options).format(date);
74
+ } catch {
75
+ return date.toISOString().split("T")[0] ?? "";
76
+ }
77
+ }
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
+
46
150
  // src/guards/index.ts
47
151
  var guards_exports = {};
48
152
  __export(guards_exports, {
@@ -60,8 +164,32 @@ __export(assertions_exports, {
60
164
  assertDefined: () => assertDefined
61
165
  });
62
166
 
167
+ // src/error/index.ts
168
+ var error_exports = {};
169
+ __export(error_exports, {
170
+ getErrorMessage: () => getErrorMessage
171
+ });
172
+
173
+ // src/format/index.ts
174
+ var format_exports = {};
175
+ __export(format_exports, {
176
+ formatDate: () => formatDate
177
+ });
178
+
179
+ // src/navigation/index.ts
180
+ var navigation_exports = {};
181
+ __export(navigation_exports, {
182
+ redirectTo: () => redirectTo,
183
+ resetRedirectHandler: () => resetRedirectHandler,
184
+ setRedirectHandler: () => setRedirectHandler
185
+ });
186
+
63
187
  exports.assertDefined = assertDefined;
64
188
  exports.assertions = assertions_exports;
189
+ exports.error = error_exports;
190
+ exports.format = format_exports;
191
+ exports.formatDate = formatDate;
192
+ exports.getErrorMessage = getErrorMessage;
65
193
  exports.guards = guards_exports;
66
194
  exports.isEmptyArray = isEmptyArray;
67
195
  exports.isEmptyString = isEmptyString;
@@ -69,5 +197,9 @@ exports.isNotEmptyArray = isNotEmptyArray;
69
197
  exports.isNotEmptyString = isNotEmptyString;
70
198
  exports.isNullOrUndefined = isNullOrUndefined;
71
199
  exports.isValueDefined = isValueDefined;
200
+ exports.navigation = navigation_exports;
201
+ exports.redirectTo = redirectTo;
202
+ exports.resetRedirectHandler = resetRedirectHandler;
203
+ exports.setRedirectHandler = setRedirectHandler;
72
204
  //# sourceMappingURL=index.js.map
73
205
  //# 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/guards/index.ts","../src/assertions/index.ts"],"names":[],"mappings":";;;;;;;;;AAeO,SAAS,eAAkB,KAAA,EAAyC;AACzE,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,KAAU,IAAA;AAC1C;;;ACFO,SAAS,gBACd,KAAA,EACqD;AACrD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,MAAM,MAAA,GAAS,CAAA;AAChD;;;ACLO,SAAS,iBAAiB,KAAA,EAAmD;AAClF,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,IAAA,GAAO,MAAA,GAAS,CAAA;AAC5D;;;ACDO,SAAS,kBAAkB,KAAA,EAA2C;AAC3E,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,KAAU,IAAA;AAC1C;;;ACHO,SAAS,aACd,KAAA,EACgD;AAChD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,MAAM,MAAA,KAAW,CAAA;AAClD;;;ACHO,SAAS,cAAc,KAAA,EAA2C;AACvE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,IAAA,GAAO,MAAA,KAAW,CAAA;AAC9D;;;ACDO,SAAS,aAAA,CACd,OACA,OAAA,EACoB;AACpB,EAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,IAAA,MAAM,IAAI,KAAA,CAAM,OAAA,IAAW,uBAAuB,CAAA;AAAA,EACpD;AACF;;;ACvBA,IAAA,cAAA,GAAA;AAAA,QAAA,CAAA,cAAA,EAAA;AAAA,EAAA,YAAA,EAAA,MAAA,YAAA;AAAA,EAAA,aAAA,EAAA,MAAA,aAAA;AAAA,EAAA,eAAA,EAAA,MAAA,eAAA;AAAA,EAAA,gBAAA,EAAA,MAAA,gBAAA;AAAA,EAAA,iBAAA,EAAA,MAAA,iBAAA;AAAA,EAAA,cAAA,EAAA,MAAA;AAAA,CAAA,CAAA;;;ACAA,IAAA,kBAAA,GAAA;AAAA,QAAA,CAAA,kBAAA,EAAA;AAAA,EAAA,aAAA,EAAA,MAAA;AAAA,CAAA,CAAA","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","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"]}
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/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/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","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"]}
package/dist/index.mjs CHANGED
@@ -41,6 +41,110 @@ function assertDefined(value, message) {
41
41
  }
42
42
  }
43
43
 
44
+ // src/error/getErrorMessage.ts
45
+ function isErrorWithMessage(value) {
46
+ return typeof value === "object" && isValueDefined(value) && "message" in value;
47
+ }
48
+ function getErrorMessage(value, fallback = "Unknown error") {
49
+ if (value instanceof Error) {
50
+ return value.message;
51
+ }
52
+ if (typeof value === "string") {
53
+ return value;
54
+ }
55
+ if (isErrorWithMessage(value)) {
56
+ const message = value.message;
57
+ if (typeof message === "string" && message.length > 0) {
58
+ return message;
59
+ }
60
+ }
61
+ return fallback;
62
+ }
63
+
64
+ // src/format/formatDate.ts
65
+ function formatDate(date, locale, options) {
66
+ if (date === null || date === void 0) {
67
+ return "";
68
+ }
69
+ const resolvedLocale = locale !== "" ? locale : "en";
70
+ try {
71
+ return new Intl.DateTimeFormat(resolvedLocale, options).format(date);
72
+ } catch {
73
+ return date.toISOString().split("T")[0] ?? "";
74
+ }
75
+ }
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
+
44
148
  // src/guards/index.ts
45
149
  var guards_exports = {};
46
150
  __export(guards_exports, {
@@ -58,6 +162,26 @@ __export(assertions_exports, {
58
162
  assertDefined: () => assertDefined
59
163
  });
60
164
 
61
- export { assertDefined, assertions_exports as assertions, guards_exports as guards, isEmptyArray, isEmptyString, isNotEmptyArray, isNotEmptyString, isNullOrUndefined, isValueDefined };
165
+ // src/error/index.ts
166
+ var error_exports = {};
167
+ __export(error_exports, {
168
+ getErrorMessage: () => getErrorMessage
169
+ });
170
+
171
+ // src/format/index.ts
172
+ var format_exports = {};
173
+ __export(format_exports, {
174
+ formatDate: () => formatDate
175
+ });
176
+
177
+ // src/navigation/index.ts
178
+ var navigation_exports = {};
179
+ __export(navigation_exports, {
180
+ redirectTo: () => redirectTo,
181
+ resetRedirectHandler: () => resetRedirectHandler,
182
+ setRedirectHandler: () => setRedirectHandler
183
+ });
184
+
185
+ export { assertDefined, assertions_exports as assertions, error_exports as error, format_exports as format, formatDate, getErrorMessage, guards_exports as guards, isEmptyArray, isEmptyString, isNotEmptyArray, isNotEmptyString, isNullOrUndefined, isValueDefined, navigation_exports as navigation, redirectTo, resetRedirectHandler, setRedirectHandler };
62
186
  //# sourceMappingURL=index.mjs.map
63
187
  //# sourceMappingURL=index.mjs.map
@@ -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/guards/index.ts","../src/assertions/index.ts"],"names":[],"mappings":";;;;;;;AAeO,SAAS,eAAkB,KAAA,EAAyC;AACzE,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,KAAU,IAAA;AAC1C;;;ACFO,SAAS,gBACd,KAAA,EACqD;AACrD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,MAAM,MAAA,GAAS,CAAA;AAChD;;;ACLO,SAAS,iBAAiB,KAAA,EAAmD;AAClF,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,IAAA,GAAO,MAAA,GAAS,CAAA;AAC5D;;;ACDO,SAAS,kBAAkB,KAAA,EAA2C;AAC3E,EAAA,OAAO,KAAA,KAAU,UAAa,KAAA,KAAU,IAAA;AAC1C;;;ACHO,SAAS,aACd,KAAA,EACgD;AAChD,EAAA,OAAO,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,MAAM,MAAA,KAAW,CAAA;AAClD;;;ACHO,SAAS,cAAc,KAAA,EAA2C;AACvE,EAAA,OAAO,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,CAAM,IAAA,GAAO,MAAA,KAAW,CAAA;AAC9D;;;ACDO,SAAS,aAAA,CACd,OACA,OAAA,EACoB;AACpB,EAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,KAAU,IAAA,EAAM;AACzC,IAAA,MAAM,IAAI,KAAA,CAAM,OAAA,IAAW,uBAAuB,CAAA;AAAA,EACpD;AACF;;;ACvBA,IAAA,cAAA,GAAA;AAAA,QAAA,CAAA,cAAA,EAAA;AAAA,EAAA,YAAA,EAAA,MAAA,YAAA;AAAA,EAAA,aAAA,EAAA,MAAA,aAAA;AAAA,EAAA,eAAA,EAAA,MAAA,eAAA;AAAA,EAAA,gBAAA,EAAA,MAAA,gBAAA;AAAA,EAAA,iBAAA,EAAA,MAAA,iBAAA;AAAA,EAAA,cAAA,EAAA,MAAA;AAAA,CAAA,CAAA;;;ACAA,IAAA,kBAAA,GAAA;AAAA,QAAA,CAAA,kBAAA,EAAA;AAAA,EAAA,aAAA,EAAA,MAAA;AAAA,CAAA,CAAA","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","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"]}
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/guards/index.ts","../src/assertions/index.ts","../src/error/index.ts","../src/format/index.ts","../src/navigation/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","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"]}
package/package.json CHANGED
@@ -1,114 +1,118 @@
1
- {
2
- "name": "@dloizides/utils",
3
- "version": "1.0.2",
4
- "description": "Type-safe utility functions for TypeScript projects",
5
- "keywords": [
6
- "typescript",
7
- "utilities",
8
- "type-guards",
9
- "validation"
10
- ],
11
- "author": "dloizides",
12
- "license": "MIT",
13
- "repository": {
14
- "type": "git",
15
- "url": "https://github.com/openmindednewby/utils.git"
16
- },
17
- "homepage": "https://github.com/openmindednewby/utils#readme",
18
- "bugs": {
19
- "url": "https://github.com/openmindednewby/utils/issues"
20
- },
21
- "main": "./dist/index.js",
22
- "module": "./dist/index.mjs",
23
- "types": "./dist/index.d.ts",
24
- "exports": {
25
- ".": {
26
- "require": {
27
- "types": "./dist/index.d.ts",
28
- "default": "./dist/index.js"
29
- },
30
- "import": {
31
- "types": "./dist/index.d.mts",
32
- "default": "./dist/index.mjs"
33
- }
34
- },
35
- "./guards": {
36
- "require": {
37
- "types": "./dist/guards/index.d.ts",
38
- "default": "./dist/guards/index.js"
39
- },
40
- "import": {
41
- "types": "./dist/guards/index.d.mts",
42
- "default": "./dist/guards/index.mjs"
43
- }
44
- },
45
- "./assertions": {
46
- "require": {
47
- "types": "./dist/assertions/index.d.ts",
48
- "default": "./dist/assertions/index.js"
49
- },
50
- "import": {
51
- "types": "./dist/assertions/index.d.mts",
52
- "default": "./dist/assertions/index.mjs"
53
- }
54
- }
55
- },
56
- "files": [
57
- "dist",
58
- "README.md",
59
- "CHANGELOG.md"
60
- ],
61
- "sideEffects": false,
62
- "engines": {
63
- "node": ">=18.0.0"
64
- },
65
- "scripts": {
66
- "build": "tsup",
67
- "build:watch": "tsup --watch",
68
- "build:analyze": "tsup && size-limit",
69
- "test": "jest",
70
- "test:watch": "jest --watch",
71
- "test:coverage": "jest --coverage",
72
- "lint": "eslint src --ext .ts",
73
- "lint:fix": "eslint src --ext .ts --fix",
74
- "typecheck": "tsc --noEmit",
75
- "clean": "rimraf dist",
76
- "security:audit": "npm audit --audit-level=high",
77
- "deps:outdated": "npm outdated || exit 0",
78
- "deps:unused": "npx depcheck --ignores \"@types/jest,@types/node,rimraf,size-limit,@size-limit/preset-small-lib\"",
79
- "deps:licenses": "npx license-checker --onlyAllow \"MIT;Apache-2.0;ISC;BSD-2-Clause;BSD-3-Clause;0BSD;Unlicense;CC0-1.0\"",
80
- "deps:health": "npm run deps:outdated && npm run deps:unused",
81
- "prepublishOnly": "npm run clean && npm run build && npm run test",
82
- "version": "npm run build",
83
- "postversion": "git push && git push --tags"
84
- },
85
- "devDependencies": {
86
- "@size-limit/preset-small-lib": "^12.0.0",
87
- "@types/jest": "^29.5.0",
88
- "@types/node": "^20.19.32",
89
- "@typescript-eslint/eslint-plugin": "^7.0.0",
90
- "@typescript-eslint/parser": "^7.0.0",
91
- "eslint": "^8.57.0",
92
- "eslint-plugin-sonarjs": "^3.0.6",
93
- "jest": "^29.7.0",
94
- "rimraf": "^5.0.0",
95
- "size-limit": "^12.0.0",
96
- "ts-jest": "^29.1.0",
97
- "tsup": "^8.0.0",
98
- "typescript": "^5.4.0"
99
- },
100
- "size-limit": [
101
- {
102
- "path": "dist/index.js",
103
- "limit": "3 KB"
104
- },
105
- {
106
- "path": "dist/guards/index.js",
107
- "limit": "2 KB"
108
- },
109
- {
110
- "path": "dist/assertions/index.js",
111
- "limit": "1 KB"
112
- }
113
- ]
114
- }
1
+ {
2
+ "name": "@dloizides/utils",
3
+ "version": "1.2.0",
4
+ "description": "Type-safe utility functions for TypeScript projects",
5
+ "keywords": [
6
+ "typescript",
7
+ "utilities",
8
+ "type-guards",
9
+ "validation"
10
+ ],
11
+ "author": "dloizides",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/openmindednewby/utils.git"
16
+ },
17
+ "homepage": "https://github.com/openmindednewby/utils#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/openmindednewby/utils/issues"
20
+ },
21
+ "main": "./dist/index.js",
22
+ "module": "./dist/index.mjs",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "require": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "import": {
31
+ "types": "./dist/index.d.mts",
32
+ "default": "./dist/index.mjs"
33
+ }
34
+ },
35
+ "./guards": {
36
+ "require": {
37
+ "types": "./dist/guards/index.d.ts",
38
+ "default": "./dist/guards/index.js"
39
+ },
40
+ "import": {
41
+ "types": "./dist/guards/index.d.mts",
42
+ "default": "./dist/guards/index.mjs"
43
+ }
44
+ },
45
+ "./assertions": {
46
+ "require": {
47
+ "types": "./dist/assertions/index.d.ts",
48
+ "default": "./dist/assertions/index.js"
49
+ },
50
+ "import": {
51
+ "types": "./dist/assertions/index.d.mts",
52
+ "default": "./dist/assertions/index.mjs"
53
+ }
54
+ }
55
+ },
56
+ "files": [
57
+ "dist",
58
+ "README.md",
59
+ "CHANGELOG.md"
60
+ ],
61
+ "sideEffects": false,
62
+ "engines": {
63
+ "node": ">=18.0.0"
64
+ },
65
+ "scripts": {
66
+ "build": "rimraf dist && tsup",
67
+ "build:watch": "tsup --watch",
68
+ "build:analyze": "tsup && size-limit",
69
+ "test": "jest",
70
+ "test:watch": "jest --watch",
71
+ "test:coverage": "jest --coverage",
72
+ "lint": "eslint src --ext .ts",
73
+ "lint:fix": "eslint src --ext .ts --fix",
74
+ "typecheck": "tsc --noEmit",
75
+ "clean": "rimraf dist",
76
+ "security:audit": "npm audit --audit-level=high",
77
+ "deps:outdated": "npm outdated || exit 0",
78
+ "deps:unused": "npx depcheck --ignores \"@types/jest,@types/node,rimraf,size-limit,@size-limit/preset-small-lib\"",
79
+ "deps:licenses": "npx license-checker --onlyAllow \"MIT;Apache-2.0;ISC;BSD-2-Clause;BSD-3-Clause;0BSD;Unlicense;CC0-1.0\"",
80
+ "deps:health": "npm run deps:outdated && npm run deps:unused",
81
+ "prepublishOnly": "npm run clean && npm run build && npm run test",
82
+ "postversion": "git push && git push --tags"
83
+ },
84
+ "overrides": {
85
+ "eslint-plugin-sonarjs": {
86
+ "minimatch": "10.2.4"
87
+ }
88
+ },
89
+ "devDependencies": {
90
+ "@size-limit/preset-small-lib": "^12.0.0",
91
+ "@types/jest": "^29.5.0",
92
+ "@types/node": "^20.19.32",
93
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
94
+ "@typescript-eslint/parser": "^7.0.0",
95
+ "eslint": "^8.57.0",
96
+ "eslint-plugin-sonarjs": "^3.0.6",
97
+ "jest": "^29.7.0",
98
+ "rimraf": "^5.0.0",
99
+ "size-limit": "^12.0.0",
100
+ "ts-jest": "^29.1.0",
101
+ "tsup": "^8.0.0",
102
+ "typescript": "^5.4.0"
103
+ },
104
+ "size-limit": [
105
+ {
106
+ "path": "dist/index.js",
107
+ "limit": "3 KB"
108
+ },
109
+ {
110
+ "path": "dist/guards/index.js",
111
+ "limit": "2 KB"
112
+ },
113
+ {
114
+ "path": "dist/assertions/index.js",
115
+ "limit": "1 KB"
116
+ }
117
+ ]
118
+ }