@ix-xs/node-comfort 1.2.0 → 2.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.
Files changed (78) hide show
  1. package/CHANGELOG.md +340 -0
  2. package/README.md +137 -548
  3. package/index.js +80 -35
  4. package/package.json +161 -33
  5. package/src/Arr.js +1050 -418
  6. package/src/Async.js +817 -0
  7. package/src/Cache.js +433 -0
  8. package/src/Checker.js +1202 -539
  9. package/src/Cli.js +1293 -0
  10. package/src/Color.js +556 -0
  11. package/src/Crypto.js +1082 -0
  12. package/src/Emitter.js +356 -0
  13. package/src/Env.js +689 -0
  14. package/src/FS.js +1626 -894
  15. package/src/Func.js +823 -356
  16. package/src/Http.js +700 -0
  17. package/src/Id.js +568 -200
  18. package/src/Logger.js +963 -378
  19. package/src/Num.js +965 -360
  20. package/src/Obj.js +1077 -379
  21. package/src/SQLite.js +1629 -1005
  22. package/src/Schema.js +2101 -0
  23. package/src/Str.js +1209 -432
  24. package/src/Sys.js +654 -0
  25. package/src/Time.js +1533 -440
  26. package/src/Utils.js +342 -325
  27. package/src/errors.js +335 -0
  28. package/src/internal/shared.js +415 -0
  29. package/types/index.d.ts +735 -526
  30. package/types/src/Arr.d.ts +533 -145
  31. package/types/src/Arr.d.ts.map +1 -1
  32. package/types/src/Async.d.ts +372 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +221 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +651 -232
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +664 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +300 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +620 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +189 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +412 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +717 -238
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +508 -135
  51. package/types/src/Func.d.ts.map +1 -1
  52. package/types/src/Http.d.ts +444 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +199 -66
  55. package/types/src/Id.d.ts.map +1 -1
  56. package/types/src/Logger.d.ts +414 -46
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +514 -137
  59. package/types/src/Num.d.ts.map +1 -1
  60. package/types/src/Obj.d.ts +453 -104
  61. package/types/src/Obj.d.ts.map +1 -1
  62. package/types/src/SQLite.d.ts +754 -308
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +1189 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +617 -166
  67. package/types/src/Str.d.ts.map +1 -1
  68. package/types/src/Sys.d.ts +314 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +759 -204
  71. package/types/src/Time.d.ts.map +1 -1
  72. package/types/src/Utils.d.ts +158 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +316 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/types/src/internal/shared.d.ts +123 -0
  77. package/types/src/internal/shared.d.ts.map +1 -0
  78. package/types/index.d.ts.map +0 -1
package/src/Checker.js CHANGED
@@ -1,547 +1,1210 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Type checks and validators. Every `isX` accepts anything and never throws,
5
+ * and most of them are type guards: inside `if (nc.isString(value))`, your
6
+ * editor knows `value` is a string, in JavaScript too.
7
+ *
8
+ * @example
9
+ * if (nc.isEmail(body.email) && nc.isPort(body.port)) connect(body);
10
+ * nc.assert(user, "User not found");
11
+ */
12
+
1
13
  const { types } = require("node:util");
14
+ const shared = require("./internal/shared.js");
15
+
16
+ /** @type {typeof import("node:net") | undefined} */
17
+ let _net;
18
+
19
+ const EMAIL = /^(?!\.)(?!.*\.\.)[\p{L}\p{N}.!#$%&'*+/=?^_`{|}~-]{1,64}(?<!\.)@(?:[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?\.)+(?:\p{L}{2,}|xn--[a-z0-9-]{2,})$/iu;
20
+ const TLD = /\.(?:[a-z]{2,}|xn--[a-z0-9-]{2,})\.?$/i;
21
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-([1-8])[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
22
+ const UUID_NIL_OR_MAX = /^(?:0{8}-0{4}-0{4}-0{4}-0{12}|f{8}-f{4}-f{4}-f{4}-f{12})$/i;
23
+ const ISO_DATE = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?(?:Z|([+-])(\d{2}):?(\d{2}))?)?$/;
24
+ const HOST_LABEL = /^[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?$/u;
25
+ const DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
26
+
27
+ /**
28
+ * Any built-in typed array.
29
+ * @typedef {Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array} AnyTypedArray
30
+ */
31
+
32
+ /**
33
+ * Any primitive value.
34
+ * @typedef {string | number | boolean | symbol | bigint | null | undefined} Primitive
35
+ */
36
+
37
+ /**
38
+ * Options for `isURL()`.
39
+ * @typedef {object} IsURLOptions
40
+ * @property {string[]} [protocols] Accepted protocols, colon included. Defaults to `["http:", "https:"]`.
41
+ * @property {boolean} [requireTld] Require a domain like `example.com`, rejecting `localhost` and bare IPs.
42
+ */
43
+
44
+ /**
45
+ * Options for `isUUID()`.
46
+ * @typedef {object} IsUUIDOptions
47
+ * @property {1|2|3|4|5|6|7|8} [version] Only accept this version. Otherwise versions 1 to 8 pass, plus the nil and max UUIDs.
48
+ */
49
+
50
+ /**
51
+ * Options for `isHostname()`.
52
+ * @typedef {object} IsHostnameOptions
53
+ * @property {boolean} [requireTld] Require a domain like `example.com`, rejecting `localhost`.
54
+ */
2
55
 
3
56
  /**
4
- * Runtime type-checking helper.
57
+ * Options for `isBase64()`.
58
+ * @typedef {object} IsBase64Options
59
+ * @property {boolean} [urlSafe] Expect the URL-safe alphabet (`-` and `_`), padding optional.
60
+ */
61
+
62
+ /**
63
+ * Is it an array?
64
+ *
65
+ * @example
66
+ * nc.isArray([1, 2]); // true
67
+ * nc.isArray({ length: 0 }); // false
5
68
  *
6
- * Every method is a small boolean guard that works on any input value.
7
- * The goal is to replace the noisy, error-prone `typeof` / `instanceof`
8
- * checks you write over and over with a single, readable call.
69
+ * @param {unknown} value
70
+ * @returns {value is any[]}
9
71
  */
10
- module.exports = {
11
- /**
12
- * Checks if a value is a real Array.
13
- *
14
- * @example
15
- * nodeComfort.isArray([1, 2, 3]); // true
16
- * nodeComfort.isArray("not array"); // false
17
- *
18
- * @param {*} value - Value to check.
19
- * @returns {boolean} True if the value is an Array.
20
- */
21
- isArray(value) {
22
- return Array.isArray(value);
23
- },
24
-
25
- /**
26
- * Checks if a value is a number (excluding NaN).
27
- *
28
- * @example
29
- * nodeComfort.isNumber(42); // true
30
- * nodeComfort.isNumber(NaN); // false
31
- * nodeComfort.isNumber("42"); // false
32
- *
33
- * @param {*} value - Value to check.
34
- * @returns {boolean} True if the value is a finite number or Infinity/-Infinity.
35
- */
36
- isNumber(value) {
37
- return typeof value === "number" && !Number.isNaN(value);
38
- },
39
-
40
- /**
41
- * Checks if a value is a finite number (excludes NaN, Infinity and -Infinity).
42
- *
43
- * @example
44
- * nodeComfort.isFinite(42); // true
45
- * nodeComfort.isFinite(Infinity); // false
46
- *
47
- * @param {*} value - Value to check.
48
- * @returns {boolean} True if the value is a finite number.
49
- */
50
- isFinite(value) {
51
- return typeof value === "number" && Number.isFinite(value);
52
- },
53
-
54
- /**
55
- * Checks if a value is a safe integer.
56
- *
57
- * @example
58
- * nodeComfort.isInteger(42); // true
59
- * nodeComfort.isInteger(4.2); // false
60
- *
61
- * @param {*} value - Value to check.
62
- * @returns {boolean} True if the value is an integer.
63
- */
64
- isInteger(value) {
65
- return Number.isInteger(value);
66
- },
67
-
68
- /**
69
- * Checks if a value is a floating-point number (a finite non-integer number).
70
- *
71
- * @example
72
- * nodeComfort.isFloat(4.2); // true
73
- * nodeComfort.isFloat(4); // false
74
- *
75
- * @param {*} value - Value to check.
76
- * @returns {boolean} True if the value is a non-integer finite number.
77
- */
78
- isFloat(value) {
79
- return typeof value === "number" && Number.isFinite(value) && !Number.isInteger(value);
80
- },
81
-
82
- /**
83
- * Checks if a value is a function.
84
- *
85
- * @example
86
- * nodeComfort.isFunction(() => {}); // true
87
- * nodeComfort.isFunction(class {}); // true
88
- * nodeComfort.isFunction("fn"); // false
89
- *
90
- * @param {*} value - Value to check.
91
- * @returns {boolean} True if the value is a function.
92
- */
93
- isFunction(value) {
94
- return typeof value === "function";
95
- },
96
-
97
- /**
98
- * Checks if a value is an async function (declared with `async`).
99
- *
100
- * @example
101
- * nodeComfort.isAsyncFunction(async () => {}); // true
102
- * nodeComfort.isAsyncFunction(() => {}); // false
103
- *
104
- * @param {*} value - Value to check.
105
- * @returns {boolean} True if the value is an async function.
106
- */
107
- isAsyncFunction(value) {
108
- return types.isAsyncFunction(value);
109
- },
110
-
111
- /**
112
- * Checks if a value is a non-null object.
113
- *
114
- * Note: Arrays, Maps, Sets, Dates, etc. are also considered objects here.
115
- *
116
- * @example
117
- * nodeComfort.isObject({}); // true
118
- * nodeComfort.isObject([]); // true
119
- * nodeComfort.isObject(null); // false
120
- * nodeComfort.isObject("str"); // false
121
- *
122
- * @param {*} value - Value to check.
123
- * @returns {boolean} True if the value is a non-null object.
124
- */
125
- isObject(value) {
126
- return typeof value === "object" && value !== null;
127
- },
128
-
129
- /**
130
- * Checks if a value is a plain object (created via `{}` or `new Object()`).
131
- *
132
- * Unlike {@link isObject}, this excludes arrays, Maps, Dates, class instances, etc.
133
- *
134
- * @example
135
- * nodeComfort.isPlainObject({ a: 1 }); // true
136
- * nodeComfort.isPlainObject([]); // false
137
- * nodeComfort.isPlainObject(new Date()); // false
138
- *
139
- * @param {*} value - Value to check.
140
- * @returns {boolean} True if the value is a plain object.
141
- */
142
- isPlainObject(value) {
143
- if (typeof value !== "object" || value === null) return false;
144
- const proto = Object.getPrototypeOf(value);
145
- return proto === null || proto === Object.prototype;
146
- },
147
-
148
- /**
149
- * Checks if a value is a boolean.
150
- *
151
- * @example
152
- * nodeComfort.isBoolean(true); // true
153
- * nodeComfort.isBoolean(false); // true
154
- * nodeComfort.isBoolean(0); // false
155
- *
156
- * @param {*} value - Value to check.
157
- * @returns {boolean} True if the value is a boolean.
158
- */
159
- isBoolean(value) {
160
- return typeof value === "boolean";
161
- },
162
-
163
- /**
164
- * Checks if a value is a string.
165
- *
166
- * @example
167
- * nodeComfort.isString("hello"); // true
168
- * nodeComfort.isString(123); // false
169
- *
170
- * @param {*} value - Value to check.
171
- * @returns {boolean} True if the value is a string.
172
- */
173
- isString(value) {
174
- return typeof value === "string";
175
- },
176
-
177
- /**
178
- * Checks if a value is a symbol.
179
- *
180
- * @example
181
- * nodeComfort.isSymbol(Symbol("x")); // true
182
- *
183
- * @param {*} value - Value to check.
184
- * @returns {boolean} True if the value is a symbol.
185
- */
186
- isSymbol(value) {
187
- return typeof value === "symbol";
188
- },
189
-
190
- /**
191
- * Checks if a value is a BigInt.
192
- *
193
- * @example
194
- * nodeComfort.isBigInt(10n); // true
195
- * nodeComfort.isBigInt(10); // false
196
- *
197
- * @param {*} value - Value to check.
198
- * @returns {boolean} True if the value is a BigInt.
199
- */
200
- isBigInt(value) {
201
- return typeof value === "bigint";
202
- },
203
-
204
- /**
205
- * Checks if a value is exactly undefined.
206
- *
207
- * @example
208
- * nodeComfort.isUndefined(undefined); // true
209
- * nodeComfort.isUndefined(null); // false
210
- *
211
- * @param {*} value - Value to check.
212
- * @returns {boolean} True if the value is undefined.
213
- */
214
- isUndefined(value) {
215
- return typeof value === "undefined";
216
- },
217
-
218
- /**
219
- * Checks if a value is exactly null.
220
- *
221
- * @example
222
- * nodeComfort.isNull(null); // true
223
- * nodeComfort.isNull(undefined); // false
224
- *
225
- * @param {*} value - Value to check.
226
- * @returns {boolean} True if the value is null.
227
- */
228
- isNull(value) {
229
- return value === null;
230
- },
231
-
232
- /**
233
- * Checks if a value is `null` or `undefined`.
234
- *
235
- * @example
236
- * nodeComfort.isNil(null); // true
237
- * nodeComfort.isNil(undefined); // true
238
- * nodeComfort.isNil(0); // false
239
- *
240
- * @param {*} value - Value to check.
241
- * @returns {boolean} True if the value is null or undefined.
242
- */
243
- isNil(value) {
244
- return value === null || value === undefined;
245
- },
246
-
247
- /**
248
- * Checks if a value is a primitive (string, number, boolean, symbol, bigint, null or undefined).
249
- *
250
- * @example
251
- * nodeComfort.isPrimitive(42); // true
252
- * nodeComfort.isPrimitive({}); // false
253
- *
254
- * @param {*} value - Value to check.
255
- * @returns {boolean} True if the value is a primitive.
256
- */
257
- isPrimitive(value) {
258
- return value === null || (typeof value !== "object" && typeof value !== "function");
259
- },
260
-
261
- /**
262
- * Checks if a value is a Promise (or a thenable object).
263
- *
264
- * @example
265
- * nodeComfort.isPromise(Promise.resolve()); // true
266
- * nodeComfort.isPromise({ then() {} }); // true
267
- * nodeComfort.isPromise(42); // false
268
- *
269
- * @param {*} value - Value to check.
270
- * @returns {boolean} True if the value is a Promise or thenable.
271
- */
272
- isPromise(value) {
273
- return (
274
- types.isPromise(value) ||
275
- (value !== null &&
276
- (typeof value === "object" || typeof value === "function") &&
277
- typeof value.then === "function")
278
- );
279
- },
280
-
281
- /**
282
- * Checks if a value is a RegExp instance.
283
- *
284
- * @example
285
- * nodeComfort.isRegExp(/a/); // true
286
- * nodeComfort.isRegExp("a"); // false
287
- *
288
- * @param {*} value - Value to check.
289
- * @returns {boolean} True if the value is a RegExp.
290
- */
291
- isRegExp(value) {
292
- return types.isRegExp(value);
293
- },
294
-
295
- /**
296
- * Checks if a value is a built-in Date instance.
297
- *
298
- * Uses `util.types.isDate` under the hood to handle cross-realm Dates.
299
- *
300
- * @example
301
- * nodeComfort.isDate(new Date()); // true
302
- * nodeComfort.isDate("2024-01-01"); // false
303
- *
304
- * @param {*} value - Value to check.
305
- * @returns {boolean} True if the value is a Date instance.
306
- */
307
- isDate(value) {
308
- return types.isDate(value);
309
- },
310
-
311
- /**
312
- * Checks if a value is a **valid** Date (a Date instance that is not `Invalid Date`).
313
- *
314
- * @example
315
- * nodeComfort.isValidDate(new Date()); // true
316
- * nodeComfort.isValidDate(new Date("nope")); // false
317
- *
318
- * @param {*} value - Value to check.
319
- * @returns {boolean} True if the value is a valid Date.
320
- */
321
- isValidDate(value) {
322
- return types.isDate(value) && !Number.isNaN(value.getTime());
323
- },
324
-
325
- /**
326
- * Checks if a value is a built-in Map instance.
327
- *
328
- * @example
329
- * nodeComfort.isMap(new Map()); // true
330
- * nodeComfort.isMap({}); // false
331
- *
332
- * @param {*} value - Value to check.
333
- * @returns {boolean} True if the value is a Map.
334
- */
335
- isMap(value) {
336
- return types.isMap(value);
337
- },
338
-
339
- /**
340
- * Checks if a value is a built-in Set instance.
341
- *
342
- * @example
343
- * nodeComfort.isSet(new Set()); // true
344
- * nodeComfort.isSet([]); // false
345
- *
346
- * @param {*} value - Value to check.
347
- * @returns {boolean} True if the value is a Set.
348
- */
349
- isSet(value) {
350
- return types.isSet(value);
351
- },
352
-
353
- /**
354
- * Checks if a value is a WeakMap instance.
355
- *
356
- * @param {*} value - Value to check.
357
- * @returns {boolean} True if the value is a WeakMap.
358
- */
359
- isWeakMap(value) {
360
- return types.isWeakMap(value);
361
- },
362
-
363
- /**
364
- * Checks if a value is a WeakSet instance.
365
- *
366
- * @param {*} value - Value to check.
367
- * @returns {boolean} True if the value is a WeakSet.
368
- */
369
- isWeakSet(value) {
370
- return types.isWeakSet(value);
371
- },
372
-
373
- /**
374
- * Checks if a value is an iterable (has a `Symbol.iterator`).
375
- *
376
- * Strings, arrays, Maps, Sets and generators are iterable.
377
- *
378
- * @example
379
- * nodeComfort.isIterable([1, 2]); // true
380
- * nodeComfort.isIterable("hi"); // true
381
- * nodeComfort.isIterable({}); // false
382
- *
383
- * @param {*} value - Value to check.
384
- * @returns {boolean} True if the value is iterable.
385
- */
386
- isIterable(value) {
387
- return value != null && typeof value[Symbol.iterator] === "function";
388
- },
389
-
390
- /**
391
- * Checks if a value is a Node.js Buffer.
392
- *
393
- * @param {*} value - Value to check.
394
- * @returns {boolean} True if the value is a Buffer.
395
- */
396
- isBuffer(value) {
397
- return Buffer.isBuffer(value);
398
- },
399
-
400
- /**
401
- * Checks if a value is any TypedArray (Uint8Array, Float64Array, etc.).
402
- *
403
- * @param {*} value - Value to check.
404
- * @returns {boolean} True if the value is a TypedArray.
405
- */
406
- isTypedArray(value) {
407
- return types.isTypedArray(value);
408
- },
409
-
410
- /**
411
- * Checks if a value is a native Error instance.
412
- *
413
- * Uses `util.types.isNativeError` to correctly handle errors from other realms.
414
- *
415
- * @example
416
- * nodeComfort.isError(new Error("x")); // true
417
- * nodeComfort.isError(new TypeError("x")); // true
418
- * nodeComfort.isError({ message: "x" }); // false
419
- *
420
- * @param {*} value - Value to check.
421
- * @returns {boolean} True if the value is a native Error.
422
- */
423
- isError(value) {
424
- return types.isNativeError(value);
425
- },
426
-
427
- /**
428
- * Checks if a value is "empty".
429
- *
430
- * Emptiness rules:
431
- * - `null` / `undefined` -> empty
432
- * - string / array -> empty when `length === 0`
433
- * - Map / Set -> empty when `size === 0`
434
- * - plain object -> empty when it has no own enumerable keys
435
- * - anything else (numbers, booleans, functions, ...) -> never empty
436
- *
437
- * @example
438
- * nodeComfort.isEmpty(""); // true
439
- * nodeComfort.isEmpty([]); // true
440
- * nodeComfort.isEmpty({}); // true
441
- * nodeComfort.isEmpty(new Map()); // true
442
- * nodeComfort.isEmpty("x"); // false
443
- * nodeComfort.isEmpty(0); // false
444
- *
445
- * @param {*} value - Value to check.
446
- * @returns {boolean} True if the value is considered empty.
447
- */
448
- isEmpty(value) {
449
- if (value === null || value === undefined) return true;
450
- if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
451
- if (types.isMap(value) || types.isSet(value)) return value.size === 0;
452
- if (typeof value === "object") return Object.keys(value).length === 0;
72
+ function isArray(value) {
73
+ return Array.isArray(value);
74
+ }
75
+
76
+ /**
77
+ * Is it a number? `NaN` is rejected, `Infinity` passes (see `isFinite`).
78
+ *
79
+ * @example
80
+ * nc.isNumber(42); // true
81
+ * nc.isNumber(NaN); // false
82
+ * nc.isNumber("42"); // false, use isNumeric for strings
83
+ *
84
+ * @param {unknown} value
85
+ * @returns {value is number}
86
+ */
87
+ function isNumber(value) {
88
+ return typeof value === "number" && !Number.isNaN(value);
89
+ }
90
+
91
+ /**
92
+ * Is it a finite number? Unlike the global `isFinite`, strings are rejected.
93
+ *
94
+ * @example
95
+ * nc.isFinite(42); // true
96
+ * nc.isFinite(Infinity); // false
97
+ * nc.isFinite("42"); // false
98
+ *
99
+ * @param {unknown} value
100
+ * @returns {value is number}
101
+ */
102
+ function isFinite(value) {
103
+ return typeof value === "number" && Number.isFinite(value);
104
+ }
105
+
106
+ /**
107
+ * Is it an integer?
108
+ *
109
+ * @example
110
+ * nc.isInteger(4); // true
111
+ * nc.isInteger(4.5); // false
112
+ *
113
+ * @param {unknown} value
114
+ * @returns {value is number}
115
+ */
116
+ function isInteger(value) {
117
+ return Number.isInteger(value);
118
+ }
119
+
120
+ /**
121
+ * Is it an integer JavaScript can represent exactly (up to 2^53 - 1)?
122
+ *
123
+ * @example
124
+ * nc.isSafeInteger(2 ** 53 - 1); // true
125
+ * nc.isSafeInteger(2 ** 53); // false
126
+ *
127
+ * @param {unknown} value
128
+ * @returns {value is number}
129
+ */
130
+ function isSafeInteger(value) {
131
+ return Number.isSafeInteger(value);
132
+ }
133
+
134
+ /**
135
+ * Is it a finite number with a decimal part?
136
+ *
137
+ * @example
138
+ * nc.isFloat(4.2); // true
139
+ * nc.isFloat(4); // false
140
+ *
141
+ * @param {unknown} value
142
+ * @returns {value is number}
143
+ */
144
+ function isFloat(value) {
145
+ return typeof value === "number" && Number.isFinite(value) && !Number.isInteger(value);
146
+ }
147
+
148
+ /**
149
+ * Is it a number greater than zero?
150
+ *
151
+ * @example
152
+ * nc.isPositive(3); // true
153
+ * nc.isPositive(0); // false
154
+ *
155
+ * @param {unknown} value
156
+ * @returns {value is number}
157
+ */
158
+ function isPositive(value) {
159
+ return typeof value === "number" && value > 0;
160
+ }
161
+
162
+ /**
163
+ * Is it a number lower than zero?
164
+ *
165
+ * @example
166
+ * nc.isNegative(-3); // true
167
+ * nc.isNegative(0); // false
168
+ *
169
+ * @param {unknown} value
170
+ * @returns {value is number}
171
+ */
172
+ function isNegative(value) {
173
+ return typeof value === "number" && value < 0;
174
+ }
175
+
176
+ /**
177
+ * Is it `true` or `false`? Truthy and falsy values don't count.
178
+ *
179
+ * @example
180
+ * nc.isBoolean(false); // true
181
+ * nc.isBoolean(0); // false
182
+ *
183
+ * @param {unknown} value
184
+ * @returns {value is boolean}
185
+ */
186
+ function isBoolean(value) {
187
+ return typeof value === "boolean";
188
+ }
189
+
190
+ /**
191
+ * Is it a string?
192
+ *
193
+ * @example
194
+ * nc.isString("hello"); // true
195
+ * nc.isString(new String("hello")); // false
196
+ *
197
+ * @param {unknown} value
198
+ * @returns {value is string}
199
+ */
200
+ function isString(value) {
201
+ return typeof value === "string";
202
+ }
203
+
204
+ /**
205
+ * Is it a symbol?
206
+ *
207
+ * @param {unknown} value
208
+ * @returns {value is symbol}
209
+ */
210
+ function isSymbol(value) {
211
+ return typeof value === "symbol";
212
+ }
213
+
214
+ /**
215
+ * Is it a bigint?
216
+ *
217
+ * @example
218
+ * nc.isBigInt(10n); // true
219
+ * nc.isBigInt(10); // false
220
+ *
221
+ * @param {unknown} value
222
+ * @returns {value is bigint}
223
+ */
224
+ function isBigInt(value) {
225
+ return typeof value === "bigint";
226
+ }
227
+
228
+ /**
229
+ * Is it `undefined`?
230
+ *
231
+ * @param {unknown} value
232
+ * @returns {value is undefined}
233
+ */
234
+ function isUndefined(value) {
235
+ return value === undefined;
236
+ }
237
+
238
+ /**
239
+ * Is it `null`?
240
+ *
241
+ * @param {unknown} value
242
+ * @returns {value is null}
243
+ */
244
+ function isNull(value) {
245
+ return value === null;
246
+ }
247
+
248
+ /**
249
+ * Is it `null` or `undefined`?
250
+ *
251
+ * @example
252
+ * nc.isNil(undefined); // true
253
+ * nc.isNil(0); // false
254
+ *
255
+ * @param {unknown} value
256
+ * @returns {value is null | undefined}
257
+ */
258
+ function isNil(value) {
259
+ return value === null || value === undefined;
260
+ }
261
+
262
+ /**
263
+ * Is it anything but `null` or `undefined`? Pass it to `filter()` to drop
264
+ * missing values and keep the right type.
265
+ *
266
+ * @example
267
+ * const ids = [1, null, 2, undefined].filter(nc.isDefined); // number[]
268
+ *
269
+ * @template T
270
+ * @param {T} value
271
+ * @returns {value is NonNullable<T>}
272
+ */
273
+ function isDefined(value) {
274
+ return value !== null && value !== undefined;
275
+ }
276
+
277
+ /**
278
+ * Is it a primitive (string, number, boolean, symbol, bigint, `null` or `undefined`)?
279
+ *
280
+ * @param {unknown} value
281
+ * @returns {value is Primitive}
282
+ */
283
+ function isPrimitive(value) {
284
+ return value === null || (typeof value !== "object" && typeof value !== "function");
285
+ }
286
+
287
+ /**
288
+ * Is it callable? Classes, async and generator functions count.
289
+ *
290
+ * @param {unknown} value
291
+ * @returns {value is (...args: any[]) => any}
292
+ */
293
+ function isFunction(value) {
294
+ return typeof value === "function";
295
+ }
296
+
297
+ /**
298
+ * Was it declared with `async`? A function that merely returns a promise
299
+ * doesn't count.
300
+ *
301
+ * @example
302
+ * nc.isAsyncFunction(async () => {}); // true
303
+ * nc.isAsyncFunction(() => Promise.resolve()); // false
304
+ *
305
+ * @param {unknown} value
306
+ * @returns {value is (...args: any[]) => Promise<any>}
307
+ */
308
+ function isAsyncFunction(value) {
309
+ return types.isAsyncFunction(value);
310
+ }
311
+
312
+ /**
313
+ * Is it a generator function (`function*`)?
314
+ *
315
+ * @param {unknown} value
316
+ * @returns {value is (...args: any[]) => Generator}
317
+ */
318
+ function isGeneratorFunction(value) {
319
+ return types.isGeneratorFunction(value);
320
+ }
321
+
322
+ /**
323
+ * Is it a generator object, the result of calling a `function*`?
324
+ *
325
+ * @param {unknown} value
326
+ * @returns {value is Generator}
327
+ */
328
+ function isGenerator(value) {
329
+ return types.isGeneratorObject(value);
330
+ }
331
+
332
+ /**
333
+ * Is it a `class` rather than a plain function?
334
+ *
335
+ * @example
336
+ * nc.isClass(class User {}); // true
337
+ * nc.isClass(function () {}); // false
338
+ *
339
+ * @param {unknown} value
340
+ * @returns {value is new (...args: any[]) => any}
341
+ */
342
+ function isClass(value) {
343
+ return typeof value === "function" && /^class[\s{]/.test(Function.prototype.toString.call(value));
344
+ }
345
+
346
+ /**
347
+ * Is it an object, and not `null`? Arrays, dates and class instances count;
348
+ * use `isPlainObject` for `{}` literals only.
349
+ *
350
+ * @param {unknown} value
351
+ * @returns {value is object}
352
+ */
353
+ function isObject(value) {
354
+ return typeof value === "object" && value !== null;
355
+ }
356
+
357
+ /**
358
+ * Is it a plain object, made with `{}`, `new Object()` or
359
+ * `Object.create(null)`? Arrays, dates, maps and class instances are rejected.
360
+ *
361
+ * @example
362
+ * nc.isPlainObject({ a: 1 }); // true
363
+ * nc.isPlainObject(new Date()); // false
364
+ *
365
+ * @param {unknown} value
366
+ * @returns {value is Record<string, unknown>}
367
+ */
368
+ function isPlainObject(value) {
369
+ return shared.isPlainObject(value);
370
+ }
371
+
372
+ /**
373
+ * Can it be awaited like a promise (anything with a `then` method)?
374
+ *
375
+ * @param {unknown} value
376
+ * @returns {value is PromiseLike<any>}
377
+ */
378
+ function isPromise(value) {
379
+ return (
380
+ types.isPromise(value) ||
381
+ (value !== null &&
382
+ (typeof value === "object" || typeof value === "function") &&
383
+ typeof (/** @type {{ then?: unknown }} */ (value)).then === "function")
384
+ );
385
+ }
386
+
387
+ /**
388
+ * Is it a regular expression?
389
+ *
390
+ * @param {unknown} value
391
+ * @returns {value is RegExp}
392
+ */
393
+ function isRegExp(value) {
394
+ return types.isRegExp(value);
395
+ }
396
+
397
+ /**
398
+ * Is it a `Date`, valid or not? Use `isValidDate` to reject `Invalid Date`.
399
+ *
400
+ * @param {unknown} value
401
+ * @returns {value is Date}
402
+ */
403
+ function isDate(value) {
404
+ return types.isDate(value);
405
+ }
406
+
407
+ /**
408
+ * Is it a `Date` holding a real point in time?
409
+ *
410
+ * @example
411
+ * nc.isValidDate(new Date()); // true
412
+ * nc.isValidDate(new Date("nope")); // false
413
+ *
414
+ * @param {unknown} value
415
+ * @returns {value is Date}
416
+ */
417
+ function isValidDate(value) {
418
+ return types.isDate(value) && !Number.isNaN(/** @type {Date} */ (value).getTime());
419
+ }
420
+
421
+ /**
422
+ * Is it a `Map`?
423
+ *
424
+ * @param {unknown} value
425
+ * @returns {value is Map<unknown, unknown>}
426
+ */
427
+ function isMap(value) {
428
+ return types.isMap(value);
429
+ }
430
+
431
+ /**
432
+ * Is it a `Set`?
433
+ *
434
+ * @param {unknown} value
435
+ * @returns {value is Set<unknown>}
436
+ */
437
+ function isSet(value) {
438
+ return types.isSet(value);
439
+ }
440
+
441
+ /**
442
+ * Is it a `WeakMap`?
443
+ *
444
+ * @param {unknown} value
445
+ * @returns {value is WeakMap<object, unknown>}
446
+ */
447
+ function isWeakMap(value) {
448
+ return types.isWeakMap(value);
449
+ }
450
+
451
+ /**
452
+ * Is it a `WeakSet`?
453
+ *
454
+ * @param {unknown} value
455
+ * @returns {value is WeakSet<object>}
456
+ */
457
+ function isWeakSet(value) {
458
+ return types.isWeakSet(value);
459
+ }
460
+
461
+ /**
462
+ * Does it work with `for...of`? Strings, arrays, maps, sets and generators do.
463
+ *
464
+ * @param {unknown} value
465
+ * @returns {value is Iterable<unknown>}
466
+ */
467
+ function isIterable(value) {
468
+ return value != null && typeof (/** @type {any} */ (value))[Symbol.iterator] === "function";
469
+ }
470
+
471
+ /**
472
+ * Does it work with `for await...of`, like streams and async generators?
473
+ *
474
+ * @param {unknown} value
475
+ * @returns {value is AsyncIterable<unknown>}
476
+ */
477
+ function isAsyncIterable(value) {
478
+ return value != null && typeof (/** @type {any} */ (value))[Symbol.asyncIterator] === "function";
479
+ }
480
+
481
+ /**
482
+ * Is it a Node.js `Buffer`?
483
+ *
484
+ * @param {unknown} value
485
+ * @returns {value is Buffer}
486
+ */
487
+ function isBuffer(value) {
488
+ return Buffer.isBuffer(value);
489
+ }
490
+
491
+ /**
492
+ * Is it a typed array (`Uint8Array`, `Float64Array`...)? Buffers count.
493
+ *
494
+ * @param {unknown} value
495
+ * @returns {value is AnyTypedArray}
496
+ */
497
+ function isTypedArray(value) {
498
+ return types.isTypedArray(value);
499
+ }
500
+
501
+ /**
502
+ * Is it an `Error`, including subclasses and errors from other realms?
503
+ *
504
+ * @param {unknown} value
505
+ * @returns {value is Error}
506
+ */
507
+ function isError(value) {
508
+ return types.isNativeError(value) || value instanceof Error;
509
+ }
510
+
511
+ /**
512
+ * Is it empty? `null`, `undefined`, `""`, `[]`, `{}` and empty maps and sets
513
+ * are. Numbers, booleans, functions, dates, regular expressions and errors
514
+ * never are.
515
+ *
516
+ * @example
517
+ * nc.isEmpty({}); // true
518
+ * nc.isEmpty(0); // false
519
+ * nc.isEmpty(" "); // false, see isBlank
520
+ *
521
+ * @param {unknown} value
522
+ * @returns {boolean}
523
+ */
524
+ function isEmpty(value) {
525
+ if (value === null || value === undefined) return true;
526
+ if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
527
+ if (typeof value !== "object") return false;
528
+ if (types.isMap(value) || types.isSet(value)) return /** @type {Map<unknown, unknown>} */ (value).size === 0;
529
+ if (types.isDate(value) || types.isRegExp(value) || types.isNativeError(value)) return false;
530
+ return Object.keys(value).length === 0;
531
+ }
532
+
533
+ /**
534
+ * Is it `null`, `undefined`, or a string with nothing but whitespace?
535
+ *
536
+ * @example
537
+ * nc.isBlank(" \n"); // true
538
+ * nc.isBlank(" a "); // false
539
+ *
540
+ * @param {unknown} value
541
+ * @returns {boolean}
542
+ */
543
+ function isBlank(value) {
544
+ if (value === null || value === undefined) return true;
545
+ return typeof value === "string" && value.trim() === "";
546
+ }
547
+
548
+ /**
549
+ * Is it an array where every item passes `guard`?
550
+ *
551
+ * @example
552
+ * if (nc.isArrayOf(input, nc.isString)) input.join(", "); // input: string[]
553
+ *
554
+ * @template T
555
+ * @param {unknown} value
556
+ * @param {(item: unknown) => item is T} guard Checked against every item, like `nc.isString`.
557
+ * @returns {value is T[]}
558
+ */
559
+ function isArrayOf(value, guard) {
560
+ return Array.isArray(value) && value.every((item) => guard(item));
561
+ }
562
+
563
+ /**
564
+ * Is it one of the allowed values? With a constant list, the value gets the
565
+ * matching literal type.
566
+ *
567
+ * @example
568
+ * const ROLES = ["admin", "user"] as const;
569
+ * if (nc.isOneOf(input, ROLES)) input; // "admin" | "user"
570
+ *
571
+ * @template {readonly unknown[]} L
572
+ * @param {unknown} value
573
+ * @param {L} allowed
574
+ * @returns {value is L[number]}
575
+ */
576
+ function isOneOf(value, allowed) {
577
+ return allowed.includes(value);
578
+ }
579
+
580
+ /**
581
+ * Does it look like an email address? The rules are practical rather than
582
+ * the full RFC: one `@`, no stray dots, a real top-level domain.
583
+ * International addresses are accepted.
584
+ *
585
+ * @example
586
+ * nc.isEmail("jose@exemple.fr"); // true
587
+ * nc.isEmail("john..doe@example.com"); // false
588
+ * nc.isEmail("john@localhost"); // false
589
+ *
590
+ * @param {unknown} value
591
+ * @returns {value is string}
592
+ */
593
+ function isEmail(value) {
594
+ return typeof value === "string" && value.length <= 254 && EMAIL.test(value);
595
+ }
596
+
597
+ /**
598
+ * Is it an absolute URL with an allowed protocol (http and https by default)?
599
+ *
600
+ * @example
601
+ * nc.isURL("https://example.com/a?b=1"); // true
602
+ * nc.isURL("ftp://example.com", { protocols: ["ftp:"] }); // true
603
+ * nc.isURL("http://localhost:3000", { requireTld: true }); // false
604
+ *
605
+ * @param {unknown} value
606
+ * @param {IsURLOptions} [options]
607
+ * @returns {value is string}
608
+ */
609
+ function isURL(value, options = {}) {
610
+ if (typeof value !== "string") return false;
611
+ const url = URL.parse(value);
612
+ if (!url) return false;
613
+ const protocols = options.protocols;
614
+ if (protocols ? !protocols.includes(url.protocol) : url.protocol !== "http:" && url.protocol !== "https:") return false;
615
+ return !options.requireTld || TLD.test(url.hostname);
616
+ }
617
+
618
+ /**
619
+ * Is it a UUID?
620
+ *
621
+ * @example
622
+ * nc.isUUID(nc.id.uuid()); // true
623
+ * nc.isUUID(id, { version: 7 }); // only v7
624
+ *
625
+ * @param {unknown} value
626
+ * @param {IsUUIDOptions} [options]
627
+ * @returns {value is string}
628
+ */
629
+ function isUUID(value, options = {}) {
630
+ if (typeof value !== "string") return false;
631
+ if (value.length !== 36) return false;
632
+ const match = UUID.exec(value);
633
+ if (!match) return options.version === undefined && UUID_NIL_OR_MAX.test(value);
634
+ return options.version === undefined || Number(match[1]) === options.version;
635
+ }
636
+
637
+ /**
638
+ * Is it a string of valid JSON?
639
+ *
640
+ * @example
641
+ * nc.isJSON('{"a":1}'); // true
642
+ * nc.isJSON("{a:1}"); // false
643
+ *
644
+ * @param {unknown} value
645
+ * @returns {value is string}
646
+ */
647
+ function isJSON(value) {
648
+ if (typeof value !== "string") return false;
649
+ try {
650
+ JSON.parse(value);
651
+ return true;
652
+ } catch {
453
653
  return false;
454
- },
455
-
456
- /**
457
- * Checks if a string is a valid, well-formed email address.
458
- *
459
- * This is a pragmatic check (not a full RFC 5322 parser) that covers the
460
- * vast majority of real-world addresses.
461
- *
462
- * @example
463
- * nodeComfort.isEmail("john@example.com"); // true
464
- * nodeComfort.isEmail("nope"); // false
465
- *
466
- * @param {*} value - Value to check.
467
- * @returns {boolean} True if the value looks like an email address.
468
- */
469
- isEmail(value) {
470
- if (typeof value !== "string") return false;
471
- return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(value);
472
- },
473
-
474
- /**
475
- * Checks if a string is a valid HTTP(S) URL.
476
- *
477
- * @example
478
- * nodeComfort.isURL("https://example.com"); // true
479
- * nodeComfort.isURL("ftp://example.com"); // false (not http/https)
480
- *
481
- * @param {*} value - Value to check.
482
- * @param {{ protocols?: string[] }} [options] - Allowed protocols (default: http, https).
483
- * @returns {boolean} True if the value is a valid URL with an allowed protocol.
484
- */
485
- isURL(value, options = {}) {
486
- if (typeof value !== "string") return false;
487
- const protocols = options.protocols ?? ["http:", "https:"];
488
- try {
489
- const url = new URL(value);
490
- return protocols.includes(url.protocol);
491
- } catch {
492
- return false;
493
- }
494
- },
495
-
496
- /**
497
- * Checks if a string is a valid UUID (versions 1-5, any variant).
498
- *
499
- * @example
500
- * nodeComfort.isUUID("123e4567-e89b-12d3-a456-426614174000"); // true
501
- *
502
- * @param {*} value - Value to check.
503
- * @returns {boolean} True if the value is a UUID.
504
- */
505
- isUUID(value) {
506
- if (typeof value !== "string") return false;
507
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
508
- },
509
-
510
- /**
511
- * Checks if a string is valid, parseable JSON.
512
- *
513
- * @example
514
- * nodeComfort.isJSON('{"a":1}'); // true
515
- * nodeComfort.isJSON("{a:1}"); // false
516
- *
517
- * @param {*} value - Value to check.
518
- * @returns {boolean} True if the value is a JSON-parseable string.
519
- */
520
- isJSON(value) {
521
- if (typeof value !== "string") return false;
522
- try {
523
- JSON.parse(value);
524
- return true;
525
- } catch {
526
- return false;
654
+ }
655
+ }
656
+
657
+ /**
658
+ * Is it a finite number, or a string that is exactly one (spaces around are fine)?
659
+ *
660
+ * @example
661
+ * nc.isNumeric("4.2e3"); // true
662
+ * nc.isNumeric(" 12 "); // true
663
+ * nc.isNumeric("12px"); // false
664
+ *
665
+ * @param {unknown} value
666
+ * @returns {value is number | string}
667
+ */
668
+ function isNumeric(value) {
669
+ if (typeof value === "number") return Number.isFinite(value);
670
+ if (typeof value !== "string" || value.trim() === "") return false;
671
+ return Number.isFinite(Number(value));
672
+ }
673
+
674
+ /**
675
+ * Is it an IP address?
676
+ *
677
+ * @example
678
+ * nc.isIP("192.168.0.1"); // true
679
+ * nc.isIP("::1"); // true
680
+ * nc.isIP("::1", 4); // false
681
+ *
682
+ * @param {unknown} value
683
+ * @param {4|6} [version] Only accept IPv4 or IPv6.
684
+ * @returns {value is string}
685
+ */
686
+ function isIP(value, version) {
687
+ if (typeof value !== "string") return false;
688
+ const detected = (_net ??= require("node:net")).isIP(value);
689
+ return version === undefined ? detected !== 0 : detected === version;
690
+ }
691
+
692
+ /**
693
+ * Is it an IPv4 address, like `192.168.0.1`?
694
+ *
695
+ * @param {unknown} value
696
+ * @returns {value is string}
697
+ */
698
+ function isIPv4(value) {
699
+ return isIP(value, 4);
700
+ }
701
+
702
+ /**
703
+ * Is it an IPv6 address, like `::1`?
704
+ *
705
+ * @param {unknown} value
706
+ * @returns {value is string}
707
+ */
708
+ function isIPv6(value) {
709
+ return isIP(value, 6);
710
+ }
711
+
712
+ /**
713
+ * Is it a valid port (0 to 65535), as a number or a numeric string?
714
+ *
715
+ * @example
716
+ * nc.isPort("8080"); // true
717
+ * nc.isPort(70000); // false
718
+ *
719
+ * @param {unknown} value
720
+ * @returns {boolean}
721
+ */
722
+ function isPort(value) {
723
+ const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
724
+ return Number.isInteger(n) && /** @type {number} */ (n) >= 0 && /** @type {number} */ (n) <= 65535;
725
+ }
726
+
727
+ /**
728
+ * Is it a hexadecimal string? A `0x` prefix is allowed.
729
+ *
730
+ * @param {unknown} value
731
+ * @returns {value is string}
732
+ */
733
+ function isHex(value) {
734
+ return typeof value === "string" && /^(0x)?[0-9a-f]+$/i.test(value);
735
+ }
736
+
737
+ /**
738
+ * Is it a CSS hex color (`#rgb`, `#rgba`, `#rrggbb` or `#rrggbbaa`)?
739
+ *
740
+ * @example
741
+ * nc.isHexColor("#f80"); // true
742
+ * nc.isHexColor("f80"); // false, the # is required
743
+ *
744
+ * @param {unknown} value
745
+ * @returns {value is string}
746
+ */
747
+ function isHexColor(value) {
748
+ return typeof value === "string" && /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value);
749
+ }
750
+
751
+ /**
752
+ * Is it valid Base64?
753
+ *
754
+ * @example
755
+ * nc.isBase64("aGVsbG8="); // true
756
+ * nc.isBase64("aGVsbG8", { urlSafe: true }); // true
757
+ *
758
+ * @param {unknown} value
759
+ * @param {IsBase64Options} [options]
760
+ * @returns {value is string}
761
+ */
762
+ function isBase64(value, options = {}) {
763
+ if (typeof value !== "string" || value === "") return false;
764
+ if (options.urlSafe) return /^[A-Za-z0-9_-]+={0,2}$/.test(value) && value.replace(/=+$/, "").length % 4 !== 1;
765
+ return value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
766
+ }
767
+
768
+ /**
769
+ * Is it a semantic version? A leading `v` is fine.
770
+ *
771
+ * @example
772
+ * nc.isSemver("v2.0.0-rc.1"); // true
773
+ * nc.isSemver("1.2"); // false
774
+ *
775
+ * @param {unknown} value
776
+ * @returns {value is string}
777
+ */
778
+ function isSemver(value) {
779
+ return (
780
+ typeof value === "string" &&
781
+ /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/.test(value)
782
+ );
783
+ }
784
+
785
+ /**
786
+ * Is it an ISO 8601 date or date-time, and a real calendar date?
787
+ *
788
+ * @example
789
+ * nc.isISODate("2024-02-29T10:00:00Z"); // true
790
+ * nc.isISODate("2023-02-29"); // false, not a leap year
791
+ *
792
+ * @param {unknown} value
793
+ * @returns {value is string}
794
+ */
795
+ function isISODate(value) {
796
+ if (typeof value !== "string") return false;
797
+ const match = ISO_DATE.exec(value);
798
+ if (!match) return false;
799
+ const [, y, m, d, hh = "0", mm = "0", ss = "0", , offsetHours = "0", offsetMinutes = "0"] = match;
800
+ const year = Number(y);
801
+ const month = Number(m);
802
+ const day = Number(d);
803
+ if (month < 1 || month > 12 || day < 1) return false;
804
+ const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
805
+ if (day > (month === 2 && leap ? 29 : DAYS_IN_MONTH[month - 1])) return false;
806
+ return Number(hh) <= 23 && Number(mm) <= 59 && Number(ss) <= 59 && Number(offsetHours) <= 14 && Number(offsetMinutes) <= 59;
807
+ }
808
+
809
+ /**
810
+ * Is it a URL slug like `my-first-post`? See `nc.str.slugify()` to make one.
811
+ *
812
+ * @param {unknown} value
813
+ * @returns {value is string}
814
+ */
815
+ function isSlug(value) {
816
+ return typeof value === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
817
+ }
818
+
819
+ /**
820
+ * Is it made only of letters, in any alphabet?
821
+ *
822
+ * @example
823
+ * nc.isAlpha("Élodie"); // true
824
+ * nc.isAlpha("abc1"); // false
825
+ *
826
+ * @param {unknown} value
827
+ * @returns {value is string}
828
+ */
829
+ function isAlpha(value) {
830
+ return typeof value === "string" && /^\p{L}+$/u.test(value);
831
+ }
832
+
833
+ /**
834
+ * Is it made only of letters and digits, in any alphabet?
835
+ *
836
+ * @param {unknown} value
837
+ * @returns {value is string}
838
+ */
839
+ function isAlphanumeric(value) {
840
+ return typeof value === "string" && /^[\p{L}\p{N}]+$/u.test(value);
841
+ }
842
+
843
+ /**
844
+ * Could it be a card number? Checks the length and the Luhn checksum;
845
+ * spaces and dashes are ignored.
846
+ *
847
+ * @example
848
+ * nc.isCreditCard("4242 4242 4242 4242"); // true
849
+ *
850
+ * @param {unknown} value
851
+ * @returns {value is string}
852
+ */
853
+ function isCreditCard(value) {
854
+ if (typeof value !== "string") return false;
855
+ const digits = value.replace(/[\s-]/g, "");
856
+ if (!/^\d{12,19}$/.test(digits)) return false;
857
+ let sum = 0;
858
+ let double = false;
859
+ for (let i = digits.length - 1; i >= 0; i--) {
860
+ let digit = digits.charCodeAt(i) - 48;
861
+ if (double) {
862
+ digit *= 2;
863
+ if (digit > 9) digit -= 9;
527
864
  }
528
- },
529
-
530
- /**
531
- * Checks if a value is numeric • a number, or a string that fully represents one.
532
- *
533
- * @example
534
- * nodeComfort.isNumeric(42); // true
535
- * nodeComfort.isNumeric("42"); // true
536
- * nodeComfort.isNumeric("4.2e3"); // true
537
- * nodeComfort.isNumeric("4px"); // false
538
- *
539
- * @param {*} value - Value to check.
540
- * @returns {boolean} True if the value is numeric.
541
- */
542
- isNumeric(value) {
543
- if (typeof value === "number") return Number.isFinite(value);
544
- if (typeof value !== "string" || value.trim() === "") return false;
545
- return Number.isFinite(Number(value));
546
- },
865
+ sum += digit;
866
+ double = !double;
867
+ }
868
+ return sum % 10 === 0;
869
+ }
870
+
871
+ /**
872
+ * Does it look like a JSON Web Token? The signature is not verified; use
873
+ * `nc.crypto.verifyJWT()` for that.
874
+ *
875
+ * @param {unknown} value
876
+ * @returns {value is string}
877
+ */
878
+ function isJWT(value) {
879
+ if (typeof value !== "string") return false;
880
+ const parts = value.split(".");
881
+ if (parts.length !== 3 || !parts.slice(0, 2).every((p) => /^[A-Za-z0-9_-]+$/.test(p))) return false;
882
+ if (parts[2] !== "" && !/^[A-Za-z0-9_-]+$/.test(parts[2])) return false;
883
+ try {
884
+ const header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
885
+ return isPlainObject(header) && typeof header.alg === "string";
886
+ } catch {
887
+ return false;
888
+ }
889
+ }
890
+
891
+ /**
892
+ * Is it a host name, like `example.com`, `api.internal` or `localhost`?
893
+ * International names are accepted. IP addresses aren't: see `isIP`.
894
+ *
895
+ * @example
896
+ * nc.isHostname("api.example.com"); // true
897
+ * nc.isHostname("localhost"); // true
898
+ * nc.isHostname("localhost", { requireTld: true }); // false
899
+ * nc.isHostname("-bad-.com"); // false
900
+ *
901
+ * @param {unknown} value
902
+ * @param {IsHostnameOptions} [options]
903
+ * @returns {value is string}
904
+ */
905
+ function isHostname(value, options = {}) {
906
+ if (typeof value !== "string" || value.length === 0 || value.length > 253) return false;
907
+ const labels = value.split(".");
908
+ if (!labels.every((label) => HOST_LABEL.test(label))) return false;
909
+ if (/^\d+$/.test(labels[labels.length - 1])) return false;
910
+ return !options.requireTld || (labels.length > 1 && TLD.test(value));
911
+ }
912
+
913
+ /**
914
+ * Is it a MAC address? Accepts `aa:bb:cc:dd:ee:ff`, `aa-bb-cc-dd-ee-ff` and
915
+ * `aabb.ccdd.eeff`.
916
+ *
917
+ * @example
918
+ * nc.isMACAddress("00:1B:44:11:3A:B7"); // true
919
+ * nc.isMACAddress("00:1B:44:11:3A"); // false
920
+ *
921
+ * @param {unknown} value
922
+ * @returns {value is string}
923
+ */
924
+ function isMACAddress(value) {
925
+ return typeof value === "string" && /^(?:[0-9a-f]{2}(?::[0-9a-f]{2}){5}|[0-9a-f]{2}(?:-[0-9a-f]{2}){5}|[0-9a-f]{4}(?:\.[0-9a-f]{4}){2})$/i.test(value);
926
+ }
927
+
928
+ /**
929
+ * Is it a valid IBAN? Checks the format and the checksum, spaces allowed.
930
+ *
931
+ * @example
932
+ * nc.isIBAN("FR76 3000 6000 0112 3456 7890 189"); // true
933
+ * nc.isIBAN("FR76 3000 6000 0112 3456 7890 180"); // false, wrong checksum
934
+ *
935
+ * @param {unknown} value
936
+ * @returns {value is string}
937
+ */
938
+ function isIBAN(value) {
939
+ if (typeof value !== "string") return false;
940
+ const iban = value.replace(/\s+/g, "").toUpperCase();
941
+ if (!/^[A-Z]{2}\d{2}[A-Z0-9]{11,30}$/.test(iban)) return false;
942
+ const rearranged = iban.slice(4) + iban.slice(0, 4);
943
+ let remainder = 0;
944
+ for (let i = 0; i < rearranged.length; i++) {
945
+ const code = rearranged.charCodeAt(i);
946
+ // Letters count as two digits (A = 10 ... Z = 35).
947
+ remainder = code >= 65 ? (remainder * 100 + code - 55) % 97 : (remainder * 10 + code - 48) % 97;
948
+ }
949
+ return remainder === 1;
950
+ }
951
+
952
+ /**
953
+ * Is it a phone number in the international E.164 format: a `+`, then 7 to
954
+ * 15 digits, no spaces? That's what SMS and telephony APIs expect.
955
+ *
956
+ * @example
957
+ * nc.isE164("+33612345678"); // true
958
+ * nc.isE164("06 12 34 56 78"); // false
959
+ *
960
+ * @param {unknown} value
961
+ * @returns {value is string}
962
+ */
963
+ function isE164(value) {
964
+ return typeof value === "string" && /^\+[1-9]\d{6,14}$/.test(value);
965
+ }
966
+
967
+ /**
968
+ * Is it a MongoDB ObjectId, 24 hexadecimal characters?
969
+ *
970
+ * @example
971
+ * nc.isObjectId("507f1f77bcf86cd799439011"); // true
972
+ *
973
+ * @param {unknown} value
974
+ * @returns {value is string}
975
+ */
976
+ function isObjectId(value) {
977
+ return typeof value === "string" && value.length === 24 && /^[0-9a-f]{24}$/i.test(value);
978
+ }
979
+
980
+ /**
981
+ * Is it a ULID, like the ones `nc.id.ulid()` makes?
982
+ *
983
+ * @example
984
+ * nc.isULID(nc.id.ulid()); // true
985
+ *
986
+ * @param {unknown} value
987
+ * @returns {value is string}
988
+ */
989
+ function isULID(value) {
990
+ return typeof value === "string" && value.length === 26 && /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i.test(value);
991
+ }
992
+
993
+ /**
994
+ * Is it a `data:` URL, like an inline image?
995
+ *
996
+ * @example
997
+ * nc.isDataURL("data:image/png;base64,iVBORw0KGgo="); // true
998
+ * nc.isDataURL("data:,Hello"); // true
999
+ *
1000
+ * @param {unknown} value
1001
+ * @returns {value is string}
1002
+ */
1003
+ function isDataURL(value) {
1004
+ return typeof value === "string" && /^data:(?:[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+)?(?:;[a-z0-9-]+=[^;,]*)*(?:;base64)?,/i.test(value);
1005
+ }
1006
+
1007
+ /**
1008
+ * Is it a media type, like `application/json` or
1009
+ * `text/html; charset=utf-8`?
1010
+ *
1011
+ * @example
1012
+ * nc.isMimeType("image/svg+xml"); // true
1013
+ * nc.isMimeType("json"); // false
1014
+ *
1015
+ * @param {unknown} value
1016
+ * @returns {value is string}
1017
+ */
1018
+ function isMimeType(value) {
1019
+ return typeof value === "string" && /^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*(?:\s*;\s*[a-z0-9-]+=(?:"[^"]*"|[^;\s]+))*$/i.test(value);
1020
+ }
1021
+
1022
+ /**
1023
+ * Is it a time zone Node.js knows, like `"Europe/Paris"` or `"UTC"`?
1024
+ *
1025
+ * @example
1026
+ * nc.isTimeZone("America/New_York"); // true
1027
+ * nc.isTimeZone("Mars/Olympus"); // false
1028
+ *
1029
+ * @param {unknown} value
1030
+ * @returns {value is string}
1031
+ */
1032
+ function isTimeZone(value) {
1033
+ if (typeof value !== "string" || value === "") return false;
1034
+ try {
1035
+ shared.intl.dateTime("en", { timeZone: value });
1036
+ return true;
1037
+ } catch {
1038
+ return false;
1039
+ }
1040
+ }
1041
+
1042
+ /**
1043
+ * Is it a well-formed locale, like `"fr"`, `"en-GB"` or `"zh-Hant-TW"`?
1044
+ *
1045
+ * @example
1046
+ * nc.isLocale("pt-BR"); // true
1047
+ * nc.isLocale("en_US"); // false, use a dash
1048
+ *
1049
+ * @param {unknown} value
1050
+ * @returns {value is string}
1051
+ */
1052
+ function isLocale(value) {
1053
+ if (typeof value !== "string" || value === "") return false;
1054
+ try {
1055
+ return Intl.getCanonicalLocales(value).length === 1;
1056
+ } catch {
1057
+ return false;
1058
+ }
1059
+ }
1060
+
1061
+ /**
1062
+ * Is it a cron expression `nc.time.cron()` understands?
1063
+ *
1064
+ * @example
1065
+ * nc.isCron("0 9 * * 1-5"); // true
1066
+ * nc.isCron("@daily"); // true
1067
+ * nc.isCron("61 * * * *"); // false
1068
+ *
1069
+ * @param {unknown} value
1070
+ * @returns {value is string}
1071
+ */
1072
+ function isCron(value) {
1073
+ if (typeof value !== "string") return false;
1074
+ try {
1075
+ shared.parseCron(value);
1076
+ return true;
1077
+ } catch {
1078
+ return false;
1079
+ }
1080
+ }
1081
+
1082
+ /**
1083
+ * Is it made only of ASCII characters?
1084
+ *
1085
+ * @example
1086
+ * nc.isASCII("hello"); // true
1087
+ * nc.isASCII("héllo"); // false
1088
+ *
1089
+ * @param {unknown} value
1090
+ * @returns {value is string}
1091
+ */
1092
+ function isASCII(value) {
1093
+ return typeof value === "string" && shared.ASCII.test(value);
1094
+ }
1095
+
1096
+ /**
1097
+ * Throws an `AssertionError` if `condition` is falsy. Afterwards, TypeScript
1098
+ * knows the condition holds.
1099
+ *
1100
+ * @example
1101
+ * const user = users.find((u) => u.id === id);
1102
+ * nc.assert(user, `User ${id} not found`);
1103
+ * user.name; // no "possibly undefined" here
1104
+ *
1105
+ * @param {unknown} condition
1106
+ * @param {string | (() => string)} [message="Assertion failed"] A message, or a function that builds it.
1107
+ * @returns {asserts condition}
1108
+ * @throws {AssertionError}
1109
+ */
1110
+ function assert(condition, message = "Assertion failed") {
1111
+ if (!condition) throw new (require("./errors.js").AssertionError)(typeof message === "function" ? message() : message);
1112
+ }
1113
+
1114
+ /**
1115
+ * Throws an `AssertionError` unless `value` passes `guard`. Afterwards,
1116
+ * `value` has the guarded type.
1117
+ *
1118
+ * @example
1119
+ * nc.assertType(config.port, nc.isInteger, "port must be an integer");
1120
+ *
1121
+ * @template T
1122
+ * @param {unknown} value
1123
+ * @param {(value: unknown) => value is T} guard A check like `nc.isString`.
1124
+ * @param {string | (() => string)} [message]
1125
+ * @returns {asserts value is T}
1126
+ * @throws {AssertionError}
1127
+ */
1128
+ function assertType(value, guard, message) {
1129
+ if (!guard(value)) {
1130
+ const text = typeof message === "function" ? message() : message;
1131
+ throw new (require("./errors.js").AssertionError)(text ?? `Expected value to pass ${guard.name || "the type guard"}`);
1132
+ }
1133
+ }
1134
+
1135
+ module.exports = {
1136
+ isArray,
1137
+ isNumber,
1138
+ isFinite,
1139
+ isInteger,
1140
+ isSafeInteger,
1141
+ isFloat,
1142
+ isPositive,
1143
+ isNegative,
1144
+ isBoolean,
1145
+ isString,
1146
+ isSymbol,
1147
+ isBigInt,
1148
+ isUndefined,
1149
+ isNull,
1150
+ isNil,
1151
+ isDefined,
1152
+ isPrimitive,
1153
+ isFunction,
1154
+ isAsyncFunction,
1155
+ isGeneratorFunction,
1156
+ isGenerator,
1157
+ isClass,
1158
+ isObject,
1159
+ isPlainObject,
1160
+ isPromise,
1161
+ isRegExp,
1162
+ isDate,
1163
+ isValidDate,
1164
+ isMap,
1165
+ isSet,
1166
+ isWeakMap,
1167
+ isWeakSet,
1168
+ isIterable,
1169
+ isAsyncIterable,
1170
+ isBuffer,
1171
+ isTypedArray,
1172
+ isError,
1173
+ isEmpty,
1174
+ isBlank,
1175
+ isArrayOf,
1176
+ isOneOf,
1177
+ isEmail,
1178
+ isURL,
1179
+ isUUID,
1180
+ isJSON,
1181
+ isNumeric,
1182
+ isIP,
1183
+ isIPv4,
1184
+ isIPv6,
1185
+ isPort,
1186
+ isHex,
1187
+ isHexColor,
1188
+ isBase64,
1189
+ isSemver,
1190
+ isISODate,
1191
+ isSlug,
1192
+ isAlpha,
1193
+ isAlphanumeric,
1194
+ isCreditCard,
1195
+ isJWT,
1196
+ isHostname,
1197
+ isMACAddress,
1198
+ isIBAN,
1199
+ isE164,
1200
+ isObjectId,
1201
+ isULID,
1202
+ isDataURL,
1203
+ isMimeType,
1204
+ isTimeZone,
1205
+ isLocale,
1206
+ isCron,
1207
+ isASCII,
1208
+ assert,
1209
+ assertType,
547
1210
  };