@zudojs/types 0.1.0 → 1.1.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/LICENSE +21 -0
- package/README.md +62 -7
- package/dist/runtime/index.d.ts +11 -1
- package/dist/runtime/index.js +10 -1
- package/dist/runtime/runtime.core.d.ts +49 -11
- package/dist/runtime/runtime.core.js +35 -52
- package/dist/runtime/runtime.int.d.ts +31 -0
- package/dist/runtime/runtime.int.js +54 -0
- package/dist/runtime/runtime.seeded.d.ts +31 -0
- package/dist/runtime/runtime.seeded.js +70 -0
- package/dist/typeConverters/typeConverters.core.d.ts +38 -3
- package/dist/typeConverters/typeConverters.core.js +111 -20
- package/dist/typeGuards/index.d.ts +1 -1
- package/dist/typeGuards/index.js +1 -1
- package/dist/typeGuards/typeGuards.core.d.ts +53 -4
- package/dist/typeGuards/typeGuards.core.js +112 -13
- package/dist/typeUtilities/index.d.ts +1 -1
- package/dist/typeUtilities/typeUtilities.core.d.ts +22 -3
- package/package.json +23 -12
- package/dist/.tsbuildinfo +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/runtime/index.d.ts.map +0 -1
- package/dist/runtime/index.js.map +0 -1
- package/dist/runtime/runtime.core.d.ts.map +0 -1
- package/dist/runtime/runtime.core.js.map +0 -1
- package/dist/typeConverters/index.d.ts.map +0 -1
- package/dist/typeConverters/index.js.map +0 -1
- package/dist/typeConverters/typeConverters.core.d.ts.map +0 -1
- package/dist/typeConverters/typeConverters.core.js.map +0 -1
- package/dist/typeGuards/index.d.ts.map +0 -1
- package/dist/typeGuards/index.js.map +0 -1
- package/dist/typeGuards/typeGuards.core.d.ts.map +0 -1
- package/dist/typeGuards/typeGuards.core.js.map +0 -1
- package/dist/typeUtilities/index.d.ts.map +0 -1
- package/dist/typeUtilities/index.js.map +0 -1
- package/dist/typeUtilities/typeUtilities.core.d.ts.map +0 -1
- package/dist/typeUtilities/typeUtilities.core.js.map +0 -1
|
@@ -3,12 +3,35 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module typeConverters/typeConverters
|
|
5
5
|
*/
|
|
6
|
+
/** Property names that mutate a prototype instead of adding a key. */
|
|
7
|
+
const UNSAFE_KEYS = new Set([
|
|
8
|
+
"__proto__",
|
|
9
|
+
"constructor",
|
|
10
|
+
"prototype",
|
|
11
|
+
]);
|
|
6
12
|
/**
|
|
7
13
|
* Safely parse JSON with a fallback value.
|
|
14
|
+
*
|
|
15
|
+
* "Safe" here means only that malformed JSON yields the fallback rather than
|
|
16
|
+
* throwing. The result is cast to `T` without validation — parse a trust
|
|
17
|
+
* boundary with `@zudojs/validation` or `@zudojs/schema` instead of relying on
|
|
18
|
+
* this cast.
|
|
19
|
+
*
|
|
20
|
+
* `__proto__`, `constructor` and `prototype` keys are dropped at every depth.
|
|
21
|
+
* `JSON.parse` itself never routes them through a prototype, so this is a
|
|
22
|
+
* deliberate deny-list, not a parser fix: a downstream deep merge that walks
|
|
23
|
+
* `constructor.prototype` or `__proto__` would otherwise reach
|
|
24
|
+
* `Object.prototype`. A payload whose legitimate field is named
|
|
25
|
+
* `constructor` or `prototype` loses that field; parse it with plain
|
|
26
|
+
* `JSON.parse` and validate it instead.
|
|
8
27
|
*/
|
|
9
28
|
export function safeJsonParse(json, fallback) {
|
|
10
29
|
try {
|
|
11
|
-
return JSON.parse(json)
|
|
30
|
+
return JSON.parse(json, function reviver(key, value) {
|
|
31
|
+
if (UNSAFE_KEYS.has(key))
|
|
32
|
+
return undefined;
|
|
33
|
+
return value;
|
|
34
|
+
});
|
|
12
35
|
}
|
|
13
36
|
catch {
|
|
14
37
|
return fallback;
|
|
@@ -16,48 +39,81 @@ export function safeJsonParse(json, fallback) {
|
|
|
16
39
|
}
|
|
17
40
|
/**
|
|
18
41
|
* Convert a value to a string safely.
|
|
42
|
+
*
|
|
43
|
+
* Always returns a string. `JSON.stringify` returns the *value* `undefined`
|
|
44
|
+
* — not a string, and without throwing — for functions, symbols and
|
|
45
|
+
* `undefined`, so its result is checked rather than returned directly.
|
|
19
46
|
*/
|
|
20
47
|
export function toString(value, fallback = "") {
|
|
21
48
|
if (value === null || value === undefined)
|
|
22
49
|
return fallback;
|
|
23
50
|
if (typeof value === "string")
|
|
24
51
|
return value;
|
|
25
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
52
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
26
53
|
return String(value);
|
|
54
|
+
}
|
|
55
|
+
if (typeof value === "bigint")
|
|
56
|
+
return `${value}`;
|
|
57
|
+
if (typeof value === "symbol")
|
|
58
|
+
return value.toString();
|
|
59
|
+
if (typeof value === "function")
|
|
60
|
+
return fallback;
|
|
27
61
|
try {
|
|
28
|
-
|
|
62
|
+
const serialized = JSON.stringify(value);
|
|
63
|
+
return typeof serialized === "string" ? serialized : fallback;
|
|
29
64
|
}
|
|
30
65
|
catch {
|
|
31
66
|
return fallback;
|
|
32
67
|
}
|
|
33
68
|
}
|
|
34
69
|
/**
|
|
35
|
-
* Convert a value to a number safely.
|
|
70
|
+
* Convert a value to a finite number safely.
|
|
71
|
+
*
|
|
72
|
+
* Blank strings, hexadecimal literals and infinities all fall back rather than
|
|
73
|
+
* converting: a missing query parameter arriving as `""` becoming a real zero
|
|
74
|
+
* silently turns into a page size, a price or a limit.
|
|
36
75
|
*/
|
|
37
76
|
export function toNumber(value, fallback = NaN) {
|
|
38
|
-
if (typeof value === "number")
|
|
39
|
-
return value;
|
|
77
|
+
if (typeof value === "number") {
|
|
78
|
+
return Number.isFinite(value) ? value : fallback;
|
|
79
|
+
}
|
|
40
80
|
if (typeof value === "string") {
|
|
41
|
-
const
|
|
42
|
-
|
|
81
|
+
const trimmed = value.trim();
|
|
82
|
+
if (trimmed.length === 0)
|
|
83
|
+
return fallback;
|
|
84
|
+
if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/u.test(trimmed)) {
|
|
85
|
+
return fallback;
|
|
86
|
+
}
|
|
87
|
+
const parsed = Number(trimmed);
|
|
88
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
43
89
|
}
|
|
44
90
|
return fallback;
|
|
45
91
|
}
|
|
46
92
|
/**
|
|
47
93
|
* Convert a value to a boolean safely.
|
|
94
|
+
*
|
|
95
|
+
* `NaN` falls back rather than converting to true. It is what `toNumber`
|
|
96
|
+
* produces on failure, so chaining the two would otherwise turn a parse
|
|
97
|
+
* failure into the permissive answer for a flag.
|
|
48
98
|
*/
|
|
49
99
|
export function toBoolean(value, fallback = false) {
|
|
50
100
|
if (typeof value === "boolean")
|
|
51
101
|
return value;
|
|
52
102
|
if (typeof value === "string") {
|
|
53
103
|
const lower = value.toLowerCase().trim();
|
|
54
|
-
if (lower === "true" || lower === "1" || lower === "yes")
|
|
104
|
+
if (lower === "true" || lower === "1" || lower === "yes" || lower === "on")
|
|
55
105
|
return true;
|
|
56
|
-
if (lower === "false" ||
|
|
106
|
+
if (lower === "false" ||
|
|
107
|
+
lower === "0" ||
|
|
108
|
+
lower === "no" ||
|
|
109
|
+
lower === "off" ||
|
|
110
|
+
lower === "")
|
|
57
111
|
return false;
|
|
112
|
+
return fallback;
|
|
113
|
+
}
|
|
114
|
+
if (typeof value === "number") {
|
|
115
|
+
return Number.isNaN(value) ? fallback : value !== 0;
|
|
58
116
|
}
|
|
59
|
-
if (typeof value === "number")
|
|
60
|
-
return value !== 0;
|
|
61
117
|
return fallback;
|
|
62
118
|
}
|
|
63
119
|
/**
|
|
@@ -70,11 +126,22 @@ export function toArray(value) {
|
|
|
70
126
|
}
|
|
71
127
|
/**
|
|
72
128
|
* Convert a Map to a plain object.
|
|
129
|
+
*
|
|
130
|
+
* Built on a null-prototype object with `defineProperty`. Assigning into an
|
|
131
|
+
* object literal routes a `__proto__` key through the prototype setter, so a
|
|
132
|
+
* Map built from request data — headers, form fields, query parameters — could
|
|
133
|
+
* replace the result's prototype with attacker-supplied values that
|
|
134
|
+
* `Object.keys` does not reveal.
|
|
73
135
|
*/
|
|
74
136
|
export function mapToObject(map) {
|
|
75
|
-
const obj =
|
|
137
|
+
const obj = Object.create(null);
|
|
76
138
|
for (const [key, value] of map) {
|
|
77
|
-
obj
|
|
139
|
+
Object.defineProperty(obj, key, {
|
|
140
|
+
value,
|
|
141
|
+
enumerable: true,
|
|
142
|
+
writable: true,
|
|
143
|
+
configurable: true,
|
|
144
|
+
});
|
|
78
145
|
}
|
|
79
146
|
return obj;
|
|
80
147
|
}
|
|
@@ -88,24 +155,48 @@ export function objectToMap(obj) {
|
|
|
88
155
|
* Convert snake_case to camelCase.
|
|
89
156
|
*/
|
|
90
157
|
export function snakeToCamel(str) {
|
|
91
|
-
return str.replace(/_([a-
|
|
158
|
+
return str.replace(/(?<!_)_+([a-z0-9])/gu, (_, char) => char.toUpperCase());
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Split a camelCase or PascalCase identifier into its words.
|
|
162
|
+
*
|
|
163
|
+
* Runs of capitals are kept together, so `parseHTTPResponse` yields
|
|
164
|
+
* `["parse", "HTTP", "Response"]` rather than one word per letter. Letter
|
|
165
|
+
* classes are Unicode-aware (`caféAuLait` keeps its `é`), existing `_`, `-`
|
|
166
|
+
* and whitespace separators are word boundaries, and any other character is
|
|
167
|
+
* kept in place rather than silently dropped.
|
|
168
|
+
*/
|
|
169
|
+
function splitCamelWords(str) {
|
|
170
|
+
return str
|
|
171
|
+
.replace(/([\p{Ll}\p{N}])(\p{Lu})/gu, "$1\u0000$2")
|
|
172
|
+
.replace(/(\p{Lu})(\p{Lu}\p{Ll})/gu, "$1\u0000$2")
|
|
173
|
+
.split(/[\u0000\s_-]+/u)
|
|
174
|
+
.filter((word) => word.length > 0);
|
|
92
175
|
}
|
|
93
176
|
/**
|
|
94
|
-
* Convert camelCase to snake_case.
|
|
177
|
+
* Convert camelCase or PascalCase to snake_case.
|
|
178
|
+
*
|
|
179
|
+
* Leading capitals do not produce a leading separator, and acronyms survive
|
|
180
|
+
* as single words — an identifier like `_hello_world` is not a valid column
|
|
181
|
+
* name, and `parse_h_t_t_p_response` is not a useful one.
|
|
95
182
|
*/
|
|
96
183
|
export function camelToSnake(str) {
|
|
97
|
-
return str
|
|
184
|
+
return splitCamelWords(str)
|
|
185
|
+
.map((word) => word.toLowerCase())
|
|
186
|
+
.join("_");
|
|
98
187
|
}
|
|
99
188
|
/**
|
|
100
189
|
* Convert kebab-case to camelCase.
|
|
101
190
|
*/
|
|
102
191
|
export function kebabToCamel(str) {
|
|
103
|
-
return str.replace(
|
|
192
|
+
return str.replace(/(?<!-)-+([a-z0-9])/gu, (_, char) => char.toUpperCase());
|
|
104
193
|
}
|
|
105
194
|
/**
|
|
106
|
-
* Convert camelCase to kebab-case.
|
|
195
|
+
* Convert camelCase or PascalCase to kebab-case.
|
|
107
196
|
*/
|
|
108
197
|
export function camelToKebab(str) {
|
|
109
|
-
return str
|
|
198
|
+
return splitCamelWords(str)
|
|
199
|
+
.map((word) => word.toLowerCase())
|
|
200
|
+
.join("-");
|
|
110
201
|
}
|
|
111
202
|
//# sourceMappingURL=typeConverters.core.js.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module typeGuards
|
|
5
5
|
*/
|
|
6
|
-
export { isPlainObject, isNonNullObject, isNonEmptyString, isPositiveNumber, isInteger, isDate, isUrl, isEmail, isUuid, isIsoDateString, isArrayOfType, isDefined, isFunction, isPromise, } from "./typeGuards.core.js";
|
|
6
|
+
export { MAX_EMAIL_LENGTH, isPlainObject, isNonNullObject, isNonEmptyString, isPositiveNumber, isFiniteNumber, isInteger, isDate, isUrl, isEmail, isUuid, isUuidV4, isIsoDateString, isIsoDateTimeString, isArrayOfType, isDefined, isFunction, isPromise, isThenable, } from "./typeGuards.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/typeGuards/index.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module typeGuards
|
|
5
5
|
*/
|
|
6
|
-
export { isPlainObject, isNonNullObject, isNonEmptyString, isPositiveNumber, isInteger, isDate, isUrl, isEmail, isUuid, isIsoDateString, isArrayOfType, isDefined, isFunction, isPromise, } from "./typeGuards.core.js";
|
|
6
|
+
export { MAX_EMAIL_LENGTH, isPlainObject, isNonNullObject, isNonEmptyString, isPositiveNumber, isFiniteNumber, isInteger, isDate, isUrl, isEmail, isUuid, isUuidV4, isIsoDateString, isIsoDateTimeString, isArrayOfType, isDefined, isFunction, isPromise, isThenable, } from "./typeGuards.core.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -16,7 +16,14 @@ export declare function isNonNullObject(value: unknown): value is Record<string,
|
|
|
16
16
|
*/
|
|
17
17
|
export declare function isNonEmptyString(value: unknown): value is string;
|
|
18
18
|
/**
|
|
19
|
-
* Check if a value is a
|
|
19
|
+
* Check if a value is a finite number.
|
|
20
|
+
*/
|
|
21
|
+
export declare function isFiniteNumber(value: unknown): value is number;
|
|
22
|
+
/**
|
|
23
|
+
* Check if a value is a positive, finite number.
|
|
24
|
+
*
|
|
25
|
+
* `Infinity` is excluded: a positive-number guard is normally protecting a
|
|
26
|
+
* size, a count or a price, none of which have a meaningful infinite value.
|
|
20
27
|
*/
|
|
21
28
|
export declare function isPositiveNumber(value: unknown): value is number;
|
|
22
29
|
/**
|
|
@@ -31,18 +38,52 @@ export declare function isDate(value: unknown): value is Date;
|
|
|
31
38
|
* Check if a value is a valid URL string.
|
|
32
39
|
*/
|
|
33
40
|
export declare function isUrl(value: unknown): value is string;
|
|
41
|
+
/**
|
|
42
|
+
* Longest input `isEmail` will examine.
|
|
43
|
+
*
|
|
44
|
+
* RFC 5321 caps a forward path at 254 characters; anything longer cannot be a
|
|
45
|
+
* deliverable address, and refusing it up front keeps an attacker from
|
|
46
|
+
* handing the domain pattern a megabyte to backtrack over.
|
|
47
|
+
*/
|
|
48
|
+
export declare const MAX_EMAIL_LENGTH = 254;
|
|
34
49
|
/**
|
|
35
50
|
* Check if a value is a valid email string.
|
|
51
|
+
*
|
|
52
|
+
* This is the monorepo's reference email check. `ValidationPattern.EMAIL`
|
|
53
|
+
* in `@zudojs/constants` (and therefore `createEmailAddress` and the schema
|
|
54
|
+
* `email` format) encodes the same acceptance set, including the 254
|
|
55
|
+
* character bound. `@zudojs/validation`'s `email` constraint still carries
|
|
56
|
+
* its own copy of the pattern without the length bound; until it delegates
|
|
57
|
+
* here, an address over 254 characters passes that constraint only.
|
|
36
58
|
*/
|
|
37
59
|
export declare function isEmail(value: unknown): value is string;
|
|
38
60
|
/**
|
|
39
|
-
* Check if a value is a
|
|
61
|
+
* Check if a value is a UUID string of any defined version.
|
|
62
|
+
*
|
|
63
|
+
* Accepts versions 1 through 8 — UUIDv7 included — plus the nil and max
|
|
64
|
+
* UUIDs. Use {@link isUuidV4} when the version genuinely matters.
|
|
40
65
|
*/
|
|
41
66
|
export declare function isUuid(value: unknown): value is string;
|
|
42
67
|
/**
|
|
43
|
-
* Check if a value is a
|
|
68
|
+
* Check if a value is specifically a UUID v4 string.
|
|
69
|
+
*/
|
|
70
|
+
export declare function isUuidV4(value: unknown): value is string;
|
|
71
|
+
/**
|
|
72
|
+
* Check if a value is a valid ISO 8601 date string, with or without a time.
|
|
73
|
+
*
|
|
74
|
+
* Validates the calendar date as well as the shape, and accepts numeric UTC
|
|
75
|
+
* offsets. Checking digit counts alone accepted `2024-13-45T99:99:99Z` and
|
|
76
|
+
* rejected `2024-01-01T00:00:00+02:00` — wrong in both directions.
|
|
77
|
+
*
|
|
78
|
+
* Use {@link isIsoDateTimeString} where a time component is required.
|
|
44
79
|
*/
|
|
45
80
|
export declare function isIsoDateString(value: unknown): value is string;
|
|
81
|
+
/**
|
|
82
|
+
* Check if a value is a valid ISO 8601 date-time string.
|
|
83
|
+
*
|
|
84
|
+
* Like {@link isIsoDateString}, but a time component is mandatory.
|
|
85
|
+
*/
|
|
86
|
+
export declare function isIsoDateTimeString(value: unknown): value is string;
|
|
46
87
|
/**
|
|
47
88
|
* Check if a value is an array of a specific element type.
|
|
48
89
|
*/
|
|
@@ -56,7 +97,15 @@ export declare function isDefined<T>(value: T | null | undefined): value is T;
|
|
|
56
97
|
*/
|
|
57
98
|
export declare function isFunction(value: unknown): value is (...args: unknown[]) => unknown;
|
|
58
99
|
/**
|
|
59
|
-
* Check if a value is a Promise.
|
|
100
|
+
* Check if a value is a native Promise.
|
|
60
101
|
*/
|
|
61
102
|
export declare function isPromise(value: unknown): value is Promise<unknown>;
|
|
103
|
+
/**
|
|
104
|
+
* Check if a value is awaitable.
|
|
105
|
+
*
|
|
106
|
+
* Narrows to `PromiseLike`, not `Promise`: a plain thenable is safe to
|
|
107
|
+
* `await` but has no `.catch()` or `.finally()`, so claiming it is a Promise
|
|
108
|
+
* makes those calls throw at the point the guard was supposed to make safe.
|
|
109
|
+
*/
|
|
110
|
+
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
62
111
|
//# sourceMappingURL=typeGuards.core.d.ts.map
|
|
@@ -25,10 +25,19 @@ export function isNonEmptyString(value) {
|
|
|
25
25
|
return typeof value === "string" && value.length > 0;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Check if a value is a
|
|
28
|
+
* Check if a value is a finite number.
|
|
29
|
+
*/
|
|
30
|
+
export function isFiniteNumber(value) {
|
|
31
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if a value is a positive, finite number.
|
|
35
|
+
*
|
|
36
|
+
* `Infinity` is excluded: a positive-number guard is normally protecting a
|
|
37
|
+
* size, a count or a price, none of which have a meaningful infinite value.
|
|
29
38
|
*/
|
|
30
39
|
export function isPositiveNumber(value) {
|
|
31
|
-
return
|
|
40
|
+
return isFiniteNumber(value) && value > 0;
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* Check if a value is a valid integer.
|
|
@@ -56,29 +65,108 @@ export function isUrl(value) {
|
|
|
56
65
|
return false;
|
|
57
66
|
}
|
|
58
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Longest input `isEmail` will examine.
|
|
70
|
+
*
|
|
71
|
+
* RFC 5321 caps a forward path at 254 characters; anything longer cannot be a
|
|
72
|
+
* deliverable address, and refusing it up front keeps an attacker from
|
|
73
|
+
* handing the domain pattern a megabyte to backtrack over.
|
|
74
|
+
*/
|
|
75
|
+
export const MAX_EMAIL_LENGTH = 254;
|
|
59
76
|
/**
|
|
60
77
|
* Check if a value is a valid email string.
|
|
78
|
+
*
|
|
79
|
+
* This is the monorepo's reference email check. `ValidationPattern.EMAIL`
|
|
80
|
+
* in `@zudojs/constants` (and therefore `createEmailAddress` and the schema
|
|
81
|
+
* `email` format) encodes the same acceptance set, including the 254
|
|
82
|
+
* character bound. `@zudojs/validation`'s `email` constraint still carries
|
|
83
|
+
* its own copy of the pattern without the length bound; until it delegates
|
|
84
|
+
* here, an address over 254 characters passes that constraint only.
|
|
61
85
|
*/
|
|
62
86
|
export function isEmail(value) {
|
|
63
87
|
if (typeof value !== "string")
|
|
64
88
|
return false;
|
|
65
|
-
|
|
89
|
+
// Bound the input before the pattern runs against it. The domain half
|
|
90
|
+
// contains `(?:[a-z0-9-]*[a-z0-9])?` nested inside `(?:\. … )+`, which is
|
|
91
|
+
// the ambiguous-quantifier shape that backtracks super-linearly on a long
|
|
92
|
+
// non-matching label. RFC 5321 caps a path at 254 characters, so nothing
|
|
93
|
+
// legitimate is lost by refusing to even look at more.
|
|
94
|
+
if (value.length > MAX_EMAIL_LENGTH)
|
|
95
|
+
return false;
|
|
96
|
+
if (value.includes(".."))
|
|
97
|
+
return false;
|
|
98
|
+
return /^[^\s@,;<>"[\]\\]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/iu.test(value);
|
|
66
99
|
}
|
|
67
100
|
/**
|
|
68
|
-
* Check if a value is a
|
|
101
|
+
* Check if a value is a UUID string of any defined version.
|
|
102
|
+
*
|
|
103
|
+
* Accepts versions 1 through 8 — UUIDv7 included — plus the nil and max
|
|
104
|
+
* UUIDs. Use {@link isUuidV4} when the version genuinely matters.
|
|
69
105
|
*/
|
|
70
106
|
export function isUuid(value) {
|
|
71
107
|
if (typeof value !== "string")
|
|
72
108
|
return false;
|
|
73
|
-
|
|
109
|
+
if (value === "00000000-0000-0000-0000-000000000000")
|
|
110
|
+
return true;
|
|
111
|
+
if (value.toLowerCase() === "ffffffff-ffff-ffff-ffff-ffffffffffff") {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value);
|
|
74
115
|
}
|
|
75
116
|
/**
|
|
76
|
-
* Check if a value is a
|
|
117
|
+
* Check if a value is specifically a UUID v4 string.
|
|
118
|
+
*/
|
|
119
|
+
export function isUuidV4(value) {
|
|
120
|
+
if (typeof value !== "string")
|
|
121
|
+
return false;
|
|
122
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value);
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Check if a value is a valid ISO 8601 date string, with or without a time.
|
|
126
|
+
*
|
|
127
|
+
* Validates the calendar date as well as the shape, and accepts numeric UTC
|
|
128
|
+
* offsets. Checking digit counts alone accepted `2024-13-45T99:99:99Z` and
|
|
129
|
+
* rejected `2024-01-01T00:00:00+02:00` — wrong in both directions.
|
|
130
|
+
*
|
|
131
|
+
* Use {@link isIsoDateTimeString} where a time component is required.
|
|
77
132
|
*/
|
|
78
133
|
export function isIsoDateString(value) {
|
|
79
134
|
if (typeof value !== "string")
|
|
80
135
|
return false;
|
|
81
|
-
|
|
136
|
+
if (!/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:\d{2})?)?$/u.test(value)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
if (Number.isNaN(new Date(value).getTime()))
|
|
140
|
+
return false;
|
|
141
|
+
// Real guards rather than `!` assertions: the regex above already fixes the
|
|
142
|
+
// shape, but an assertion would turn any future loosening of it into silent
|
|
143
|
+
// NaN propagation through Date.UTC instead of a `false`.
|
|
144
|
+
const parts = value.slice(0, 10).split("-").map(Number);
|
|
145
|
+
const [year, month, day] = parts;
|
|
146
|
+
if (year === undefined || month === undefined || day === undefined) {
|
|
147
|
+
return false;
|
|
148
|
+
}
|
|
149
|
+
if (!Number.isInteger(year) ||
|
|
150
|
+
!Number.isInteger(month) ||
|
|
151
|
+
!Number.isInteger(day)) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const asUtc = new Date(Date.UTC(year, month - 1, day));
|
|
155
|
+
return (asUtc.getUTCFullYear() === year &&
|
|
156
|
+
asUtc.getUTCMonth() === month - 1 &&
|
|
157
|
+
asUtc.getUTCDate() === day);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Check if a value is a valid ISO 8601 date-time string.
|
|
161
|
+
*
|
|
162
|
+
* Like {@link isIsoDateString}, but a time component is mandatory.
|
|
163
|
+
*/
|
|
164
|
+
export function isIsoDateTimeString(value) {
|
|
165
|
+
if (typeof value !== "string")
|
|
166
|
+
return false;
|
|
167
|
+
if (!/[T ]\d{2}:\d{2}/u.test(value))
|
|
168
|
+
return false;
|
|
169
|
+
return isIsoDateString(value);
|
|
82
170
|
}
|
|
83
171
|
/**
|
|
84
172
|
* Check if a value is an array of a specific element type.
|
|
@@ -101,13 +189,24 @@ export function isFunction(value) {
|
|
|
101
189
|
return typeof value === "function";
|
|
102
190
|
}
|
|
103
191
|
/**
|
|
104
|
-
* Check if a value is a Promise.
|
|
192
|
+
* Check if a value is a native Promise.
|
|
105
193
|
*/
|
|
106
194
|
export function isPromise(value) {
|
|
107
|
-
return
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
195
|
+
return value instanceof Promise;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Check if a value is awaitable.
|
|
199
|
+
*
|
|
200
|
+
* Narrows to `PromiseLike`, not `Promise`: a plain thenable is safe to
|
|
201
|
+
* `await` but has no `.catch()` or `.finally()`, so claiming it is a Promise
|
|
202
|
+
* makes those calls throw at the point the guard was supposed to make safe.
|
|
203
|
+
*/
|
|
204
|
+
export function isThenable(value) {
|
|
205
|
+
if (value instanceof Promise)
|
|
206
|
+
return true;
|
|
207
|
+
return (typeof value === "object" &&
|
|
208
|
+
value !== null &&
|
|
209
|
+
"then" in value &&
|
|
210
|
+
typeof value.then === "function");
|
|
112
211
|
}
|
|
113
212
|
//# sourceMappingURL=typeGuards.core.js.map
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module typeUtilities
|
|
5
5
|
*/
|
|
6
|
-
export type { DeepReadonly, DeepPartial, DeepRequired, Prettify, StringKeysOf, NumberKeysOf, PartialExcept, RequiredExcept,
|
|
6
|
+
export type { DeepReadonly, DeepPartial, DeepRequired, Prettify, StringKeysOf, NumberKeysOf, PartialExcept, RequiredExcept, PartialKeys, OptionalKeyNames, RequireKeys, AsyncReturnType, Nullable, Undefinable, Maybe, MaybePromise, NestedKeyOf, NestedValueOf, OmitByValue, PickByValue, } from "./typeUtilities.core.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -44,17 +44,36 @@ export type PartialExcept<T, K extends keyof T> = Partial<Omit<T, K>> & Pick<T,
|
|
|
44
44
|
*/
|
|
45
45
|
export type RequiredExcept<T, K extends keyof T> = Required<Omit<T, K>> & Pick<T, K>;
|
|
46
46
|
/**
|
|
47
|
-
* Create a type that makes specified keys optional.
|
|
47
|
+
* Create a type that makes the specified keys optional.
|
|
48
|
+
*
|
|
49
|
+
* Named `PartialKeys` to pair with {@link RequireKeys}. It was `OptionalKeys`,
|
|
50
|
+
* which in every other utility library means "the union of `T`'s optional key
|
|
51
|
+
* names" — the opposite kind of thing, and a two-parameter type that reads as
|
|
52
|
+
* a one-parameter one. Use {@link OptionalKeyNames} for that meaning.
|
|
48
53
|
*/
|
|
49
|
-
export type
|
|
54
|
+
export type PartialKeys<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
55
|
+
/**
|
|
56
|
+
* The union of `T`'s optional key names.
|
|
57
|
+
*
|
|
58
|
+
* This is what `OptionalKeys<T>` reads as, and there was no type that did it.
|
|
59
|
+
*/
|
|
60
|
+
export type OptionalKeyNames<T> = {
|
|
61
|
+
[K in keyof T]-?: object extends Pick<T, K> ? K : never;
|
|
62
|
+
}[keyof T];
|
|
50
63
|
/**
|
|
51
64
|
* Create a type that makes specified keys required.
|
|
52
65
|
*/
|
|
53
66
|
export type RequireKeys<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>;
|
|
54
67
|
/**
|
|
55
68
|
* Extract the return type of an async function.
|
|
69
|
+
*
|
|
70
|
+
* The constraint uses `never[]` rather than `unknown[]`. Parameters are
|
|
71
|
+
* contravariant, so `(id: string) => Promise<User>` is **not** assignable to
|
|
72
|
+
* `(...args: unknown[]) => Promise<unknown>` — the previous constraint
|
|
73
|
+
* rejected almost every real async function, and nothing caught it because no
|
|
74
|
+
* test instantiated the type.
|
|
56
75
|
*/
|
|
57
|
-
export type AsyncReturnType<T extends (...args:
|
|
76
|
+
export type AsyncReturnType<T extends (...args: never[]) => Promise<unknown>> = T extends (...args: never[]) => Promise<infer R> ? R : never;
|
|
58
77
|
/**
|
|
59
78
|
* Make a type nullable (allows null).
|
|
60
79
|
*/
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zudojs/types",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Shared type guards, utility types, and type converters for the Zudojs framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"author": {
|
|
7
|
+
"name": "Oluwayemi Oyinlola",
|
|
8
|
+
"url": "https://github.com/oyinlola-tech"
|
|
9
|
+
},
|
|
6
10
|
"type": "module",
|
|
7
11
|
"main": "./dist/index.js",
|
|
8
12
|
"module": "./dist/index.js",
|
|
@@ -15,22 +19,18 @@
|
|
|
15
19
|
}
|
|
16
20
|
},
|
|
17
21
|
"files": [
|
|
18
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"!dist/**/*.map",
|
|
24
|
+
"!dist/**/*.tsbuildinfo",
|
|
25
|
+
"!dist/.tsbuildinfo"
|
|
19
26
|
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc -p tsconfig.json",
|
|
22
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
23
|
-
"clean": "rm -rf dist",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"test:watch": "vitest"
|
|
26
|
-
},
|
|
27
27
|
"dependencies": {},
|
|
28
28
|
"engines": {
|
|
29
29
|
"node": ">=24.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^26.4.1",
|
|
33
|
-
"typescript": "
|
|
33
|
+
"typescript": "7.0.2",
|
|
34
34
|
"vitest": "^4.1.11"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
@@ -43,8 +43,19 @@
|
|
|
43
43
|
"utilities"
|
|
44
44
|
],
|
|
45
45
|
"homepage": "https://github.com/oyinlola-tech/zudo#readme",
|
|
46
|
+
"bugs": {
|
|
47
|
+
"url": "https://github.com/oyinlola-tech/zudo/issues"
|
|
48
|
+
},
|
|
46
49
|
"repository": {
|
|
47
50
|
"type": "git",
|
|
48
|
-
"url": "https://github.com/oyinlola-tech/zudo"
|
|
51
|
+
"url": "https://github.com/oyinlola-tech/zudo",
|
|
52
|
+
"directory": "packages/types"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "tsc -p tsconfig.json",
|
|
56
|
+
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
|
|
57
|
+
"clean": "rm -rf dist",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"test:watch": "vitest"
|
|
49
60
|
}
|
|
50
|
-
}
|
|
61
|
+
}
|