@ix-xs/node-comfort 1.2.0 → 2.0.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 (75) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +136 -549
  3. package/index.js +80 -35
  4. package/package.json +160 -33
  5. package/src/Arr.js +887 -419
  6. package/src/Async.js +585 -0
  7. package/src/Cache.js +329 -0
  8. package/src/Checker.js +971 -539
  9. package/src/Cli.js +1034 -0
  10. package/src/Color.js +415 -0
  11. package/src/Crypto.js +707 -0
  12. package/src/Emitter.js +289 -0
  13. package/src/Env.js +555 -0
  14. package/src/FS.js +1483 -894
  15. package/src/Func.js +670 -353
  16. package/src/Http.js +538 -0
  17. package/src/Id.js +384 -201
  18. package/src/Logger.js +739 -378
  19. package/src/Num.js +844 -361
  20. package/src/Obj.js +855 -372
  21. package/src/SQLite.js +1308 -1005
  22. package/src/Schema.js +1586 -0
  23. package/src/Str.js +983 -436
  24. package/src/Sys.js +511 -0
  25. package/src/Time.js +1260 -422
  26. package/src/Utils.js +321 -325
  27. package/src/errors.js +292 -0
  28. package/types/index.d.ts +693 -526
  29. package/types/src/Arr.d.ts +455 -145
  30. package/types/src/Arr.d.ts.map +1 -1
  31. package/types/src/Async.d.ts +321 -0
  32. package/types/src/Async.d.ts.map +1 -0
  33. package/types/src/Cache.d.ts +195 -0
  34. package/types/src/Cache.d.ts.map +1 -0
  35. package/types/src/Checker.d.ts +501 -235
  36. package/types/src/Checker.d.ts.map +1 -1
  37. package/types/src/Cli.d.ts +582 -0
  38. package/types/src/Cli.d.ts.map +1 -0
  39. package/types/src/Color.d.ts +294 -0
  40. package/types/src/Color.d.ts.map +1 -0
  41. package/types/src/Crypto.d.ts +534 -0
  42. package/types/src/Crypto.d.ts.map +1 -0
  43. package/types/src/Emitter.d.ts +170 -0
  44. package/types/src/Emitter.d.ts.map +1 -0
  45. package/types/src/Env.d.ts +382 -0
  46. package/types/src/Env.d.ts.map +1 -0
  47. package/types/src/FS.d.ts +686 -238
  48. package/types/src/FS.d.ts.map +1 -1
  49. package/types/src/Func.d.ts +380 -135
  50. package/types/src/Func.d.ts.map +1 -1
  51. package/types/src/Http.d.ts +374 -0
  52. package/types/src/Http.d.ts.map +1 -0
  53. package/types/src/Id.d.ts +149 -68
  54. package/types/src/Id.d.ts.map +1 -1
  55. package/types/src/Logger.d.ts +363 -46
  56. package/types/src/Logger.d.ts.map +1 -1
  57. package/types/src/Num.d.ts +473 -137
  58. package/types/src/Num.d.ts.map +1 -1
  59. package/types/src/Obj.d.ts +390 -104
  60. package/types/src/Obj.d.ts.map +1 -1
  61. package/types/src/SQLite.d.ts +646 -309
  62. package/types/src/SQLite.d.ts.map +1 -1
  63. package/types/src/Schema.d.ts +940 -0
  64. package/types/src/Schema.d.ts.map +1 -0
  65. package/types/src/Str.d.ts +569 -166
  66. package/types/src/Str.d.ts.map +1 -1
  67. package/types/src/Sys.d.ts +269 -0
  68. package/types/src/Sys.d.ts.map +1 -0
  69. package/types/src/Time.d.ts +663 -204
  70. package/types/src/Time.d.ts.map +1 -1
  71. package/types/src/Utils.d.ts +154 -102
  72. package/types/src/Utils.d.ts.map +1 -1
  73. package/types/src/errors.d.ts +292 -0
  74. package/types/src/errors.d.ts.map +1 -0
  75. package/types/index.d.ts.map +0 -1
package/src/Checker.js CHANGED
@@ -1,547 +1,979 @@
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");
2
14
 
3
15
  /**
4
- * Runtime type-checking helper.
16
+ * Any built-in typed array.
17
+ * @typedef {Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array} AnyTypedArray
18
+ */
19
+
20
+ /**
21
+ * Any primitive value.
22
+ * @typedef {string | number | boolean | symbol | bigint | null | undefined} Primitive
23
+ */
24
+
25
+ /**
26
+ * Options for `isURL()`.
27
+ * @typedef {object} IsURLOptions
28
+ * @property {string[]} [protocols] Accepted protocols, colon included. Defaults to `["http:", "https:"]`.
29
+ * @property {boolean} [requireTld] Require a domain like `example.com`, rejecting `localhost` and bare IPs.
30
+ */
31
+
32
+ /**
33
+ * Options for `isUUID()`.
34
+ * @typedef {object} IsUUIDOptions
35
+ * @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.
36
+ */
37
+
38
+ /**
39
+ * Options for `isBase64()`.
40
+ * @typedef {object} IsBase64Options
41
+ * @property {boolean} [urlSafe] Expect the URL-safe alphabet (`-` and `_`), padding optional.
42
+ */
43
+
44
+ /**
45
+ * Is it an array?
5
46
  *
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.
47
+ * @example
48
+ * nc.isArray([1, 2]); // true
49
+ * nc.isArray({ length: 0 }); // false
50
+ *
51
+ * @param {unknown} value
52
+ * @returns {value is any[]}
9
53
  */
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;
54
+ function isArray(value) {
55
+ return Array.isArray(value);
56
+ }
57
+
58
+ /**
59
+ * Is it a number? `NaN` is rejected, `Infinity` passes (see `isFinite`).
60
+ *
61
+ * @example
62
+ * nc.isNumber(42); // true
63
+ * nc.isNumber(NaN); // false
64
+ * nc.isNumber("42"); // false, use isNumeric for strings
65
+ *
66
+ * @param {unknown} value
67
+ * @returns {value is number}
68
+ */
69
+ function isNumber(value) {
70
+ return typeof value === "number" && !Number.isNaN(value);
71
+ }
72
+
73
+ /**
74
+ * Is it a finite number? Unlike the global `isFinite`, strings are rejected.
75
+ *
76
+ * @example
77
+ * nc.isFinite(42); // true
78
+ * nc.isFinite(Infinity); // false
79
+ * nc.isFinite("42"); // false
80
+ *
81
+ * @param {unknown} value
82
+ * @returns {value is number}
83
+ */
84
+ function isFinite(value) {
85
+ return typeof value === "number" && Number.isFinite(value);
86
+ }
87
+
88
+ /**
89
+ * Is it an integer?
90
+ *
91
+ * @example
92
+ * nc.isInteger(4); // true
93
+ * nc.isInteger(4.5); // false
94
+ *
95
+ * @param {unknown} value
96
+ * @returns {value is number}
97
+ */
98
+ function isInteger(value) {
99
+ return Number.isInteger(value);
100
+ }
101
+
102
+ /**
103
+ * Is it an integer JavaScript can represent exactly (up to 2^53 - 1)?
104
+ *
105
+ * @example
106
+ * nc.isSafeInteger(2 ** 53 - 1); // true
107
+ * nc.isSafeInteger(2 ** 53); // false
108
+ *
109
+ * @param {unknown} value
110
+ * @returns {value is number}
111
+ */
112
+ function isSafeInteger(value) {
113
+ return Number.isSafeInteger(value);
114
+ }
115
+
116
+ /**
117
+ * Is it a finite number with a decimal part?
118
+ *
119
+ * @example
120
+ * nc.isFloat(4.2); // true
121
+ * nc.isFloat(4); // false
122
+ *
123
+ * @param {unknown} value
124
+ * @returns {value is number}
125
+ */
126
+ function isFloat(value) {
127
+ return typeof value === "number" && Number.isFinite(value) && !Number.isInteger(value);
128
+ }
129
+
130
+ /**
131
+ * Is it a number greater than zero?
132
+ *
133
+ * @example
134
+ * nc.isPositive(3); // true
135
+ * nc.isPositive(0); // false
136
+ *
137
+ * @param {unknown} value
138
+ * @returns {value is number}
139
+ */
140
+ function isPositive(value) {
141
+ return typeof value === "number" && value > 0;
142
+ }
143
+
144
+ /**
145
+ * Is it a number lower than zero?
146
+ *
147
+ * @example
148
+ * nc.isNegative(-3); // true
149
+ * nc.isNegative(0); // false
150
+ *
151
+ * @param {unknown} value
152
+ * @returns {value is number}
153
+ */
154
+ function isNegative(value) {
155
+ return typeof value === "number" && value < 0;
156
+ }
157
+
158
+ /**
159
+ * Is it `true` or `false`? Truthy and falsy values don't count.
160
+ *
161
+ * @example
162
+ * nc.isBoolean(false); // true
163
+ * nc.isBoolean(0); // false
164
+ *
165
+ * @param {unknown} value
166
+ * @returns {value is boolean}
167
+ */
168
+ function isBoolean(value) {
169
+ return typeof value === "boolean";
170
+ }
171
+
172
+ /**
173
+ * Is it a string?
174
+ *
175
+ * @example
176
+ * nc.isString("hello"); // true
177
+ * nc.isString(new String("hello")); // false
178
+ *
179
+ * @param {unknown} value
180
+ * @returns {value is string}
181
+ */
182
+ function isString(value) {
183
+ return typeof value === "string";
184
+ }
185
+
186
+ /**
187
+ * Is it a symbol?
188
+ *
189
+ * @param {unknown} value
190
+ * @returns {value is symbol}
191
+ */
192
+ function isSymbol(value) {
193
+ return typeof value === "symbol";
194
+ }
195
+
196
+ /**
197
+ * Is it a bigint?
198
+ *
199
+ * @example
200
+ * nc.isBigInt(10n); // true
201
+ * nc.isBigInt(10); // false
202
+ *
203
+ * @param {unknown} value
204
+ * @returns {value is bigint}
205
+ */
206
+ function isBigInt(value) {
207
+ return typeof value === "bigint";
208
+ }
209
+
210
+ /**
211
+ * Is it `undefined`?
212
+ *
213
+ * @param {unknown} value
214
+ * @returns {value is undefined}
215
+ */
216
+ function isUndefined(value) {
217
+ return value === undefined;
218
+ }
219
+
220
+ /**
221
+ * Is it `null`?
222
+ *
223
+ * @param {unknown} value
224
+ * @returns {value is null}
225
+ */
226
+ function isNull(value) {
227
+ return value === null;
228
+ }
229
+
230
+ /**
231
+ * Is it `null` or `undefined`?
232
+ *
233
+ * @example
234
+ * nc.isNil(undefined); // true
235
+ * nc.isNil(0); // false
236
+ *
237
+ * @param {unknown} value
238
+ * @returns {value is null | undefined}
239
+ */
240
+ function isNil(value) {
241
+ return value === null || value === undefined;
242
+ }
243
+
244
+ /**
245
+ * Is it anything but `null` or `undefined`? Pass it to `filter()` to drop
246
+ * missing values and keep the right type.
247
+ *
248
+ * @example
249
+ * const ids = [1, null, 2, undefined].filter(nc.isDefined); // number[]
250
+ *
251
+ * @template T
252
+ * @param {T} value
253
+ * @returns {value is NonNullable<T>}
254
+ */
255
+ function isDefined(value) {
256
+ return value !== null && value !== undefined;
257
+ }
258
+
259
+ /**
260
+ * Is it a primitive (string, number, boolean, symbol, bigint, `null` or `undefined`)?
261
+ *
262
+ * @param {unknown} value
263
+ * @returns {value is Primitive}
264
+ */
265
+ function isPrimitive(value) {
266
+ return value === null || (typeof value !== "object" && typeof value !== "function");
267
+ }
268
+
269
+ /**
270
+ * Is it callable? Classes, async and generator functions count.
271
+ *
272
+ * @param {unknown} value
273
+ * @returns {value is (...args: any[]) => any}
274
+ */
275
+ function isFunction(value) {
276
+ return typeof value === "function";
277
+ }
278
+
279
+ /**
280
+ * Was it declared with `async`? A function that merely returns a promise
281
+ * doesn't count.
282
+ *
283
+ * @example
284
+ * nc.isAsyncFunction(async () => {}); // true
285
+ * nc.isAsyncFunction(() => Promise.resolve()); // false
286
+ *
287
+ * @param {unknown} value
288
+ * @returns {value is (...args: any[]) => Promise<any>}
289
+ */
290
+ function isAsyncFunction(value) {
291
+ return types.isAsyncFunction(value);
292
+ }
293
+
294
+ /**
295
+ * Is it a generator function (`function*`)?
296
+ *
297
+ * @param {unknown} value
298
+ * @returns {value is (...args: any[]) => Generator}
299
+ */
300
+ function isGeneratorFunction(value) {
301
+ return types.isGeneratorFunction(value);
302
+ }
303
+
304
+ /**
305
+ * Is it a generator object, the result of calling a `function*`?
306
+ *
307
+ * @param {unknown} value
308
+ * @returns {value is Generator}
309
+ */
310
+ function isGenerator(value) {
311
+ return types.isGeneratorObject(value);
312
+ }
313
+
314
+ /**
315
+ * Is it a `class` rather than a plain function?
316
+ *
317
+ * @example
318
+ * nc.isClass(class User {}); // true
319
+ * nc.isClass(function () {}); // false
320
+ *
321
+ * @param {unknown} value
322
+ * @returns {value is new (...args: any[]) => any}
323
+ */
324
+ function isClass(value) {
325
+ return typeof value === "function" && /^class[\s{]/.test(Function.prototype.toString.call(value));
326
+ }
327
+
328
+ /**
329
+ * Is it an object, and not `null`? Arrays, dates and class instances count;
330
+ * use `isPlainObject` for `{}` literals only.
331
+ *
332
+ * @param {unknown} value
333
+ * @returns {value is object}
334
+ */
335
+ function isObject(value) {
336
+ return typeof value === "object" && value !== null;
337
+ }
338
+
339
+ /**
340
+ * Is it a plain object, made with `{}`, `new Object()` or
341
+ * `Object.create(null)`? Arrays, dates, maps and class instances are rejected.
342
+ *
343
+ * @example
344
+ * nc.isPlainObject({ a: 1 }); // true
345
+ * nc.isPlainObject(new Date()); // false
346
+ *
347
+ * @param {unknown} value
348
+ * @returns {value is Record<string, unknown>}
349
+ */
350
+ function isPlainObject(value) {
351
+ if (typeof value !== "object" || value === null) return false;
352
+ const proto = Object.getPrototypeOf(value);
353
+ // The last check accepts objects from another realm (a worker, a `vm`).
354
+ return proto === null || proto === Object.prototype || Object.getPrototypeOf(proto) === null;
355
+ }
356
+
357
+ /**
358
+ * Can it be awaited like a promise (anything with a `then` method)?
359
+ *
360
+ * @param {unknown} value
361
+ * @returns {value is PromiseLike<any>}
362
+ */
363
+ function isPromise(value) {
364
+ return (
365
+ types.isPromise(value) ||
366
+ (value !== null &&
367
+ (typeof value === "object" || typeof value === "function") &&
368
+ typeof (/** @type {{ then?: unknown }} */ (value)).then === "function")
369
+ );
370
+ }
371
+
372
+ /**
373
+ * Is it a regular expression?
374
+ *
375
+ * @param {unknown} value
376
+ * @returns {value is RegExp}
377
+ */
378
+ function isRegExp(value) {
379
+ return types.isRegExp(value);
380
+ }
381
+
382
+ /**
383
+ * Is it a `Date`, valid or not? Use `isValidDate` to reject `Invalid Date`.
384
+ *
385
+ * @param {unknown} value
386
+ * @returns {value is Date}
387
+ */
388
+ function isDate(value) {
389
+ return types.isDate(value);
390
+ }
391
+
392
+ /**
393
+ * Is it a `Date` holding a real point in time?
394
+ *
395
+ * @example
396
+ * nc.isValidDate(new Date()); // true
397
+ * nc.isValidDate(new Date("nope")); // false
398
+ *
399
+ * @param {unknown} value
400
+ * @returns {value is Date}
401
+ */
402
+ function isValidDate(value) {
403
+ return types.isDate(value) && !Number.isNaN(/** @type {Date} */ (value).getTime());
404
+ }
405
+
406
+ /**
407
+ * Is it a `Map`?
408
+ *
409
+ * @param {unknown} value
410
+ * @returns {value is Map<unknown, unknown>}
411
+ */
412
+ function isMap(value) {
413
+ return types.isMap(value);
414
+ }
415
+
416
+ /**
417
+ * Is it a `Set`?
418
+ *
419
+ * @param {unknown} value
420
+ * @returns {value is Set<unknown>}
421
+ */
422
+ function isSet(value) {
423
+ return types.isSet(value);
424
+ }
425
+
426
+ /**
427
+ * Is it a `WeakMap`?
428
+ *
429
+ * @param {unknown} value
430
+ * @returns {value is WeakMap<object, unknown>}
431
+ */
432
+ function isWeakMap(value) {
433
+ return types.isWeakMap(value);
434
+ }
435
+
436
+ /**
437
+ * Is it a `WeakSet`?
438
+ *
439
+ * @param {unknown} value
440
+ * @returns {value is WeakSet<object>}
441
+ */
442
+ function isWeakSet(value) {
443
+ return types.isWeakSet(value);
444
+ }
445
+
446
+ /**
447
+ * Does it work with `for...of`? Strings, arrays, maps, sets and generators do.
448
+ *
449
+ * @param {unknown} value
450
+ * @returns {value is Iterable<unknown>}
451
+ */
452
+ function isIterable(value) {
453
+ return value != null && typeof (/** @type {any} */ (value))[Symbol.iterator] === "function";
454
+ }
455
+
456
+ /**
457
+ * Does it work with `for await...of`, like streams and async generators?
458
+ *
459
+ * @param {unknown} value
460
+ * @returns {value is AsyncIterable<unknown>}
461
+ */
462
+ function isAsyncIterable(value) {
463
+ return value != null && typeof (/** @type {any} */ (value))[Symbol.asyncIterator] === "function";
464
+ }
465
+
466
+ /**
467
+ * Is it a Node.js `Buffer`?
468
+ *
469
+ * @param {unknown} value
470
+ * @returns {value is Buffer}
471
+ */
472
+ function isBuffer(value) {
473
+ return Buffer.isBuffer(value);
474
+ }
475
+
476
+ /**
477
+ * Is it a typed array (`Uint8Array`, `Float64Array`...)? Buffers count.
478
+ *
479
+ * @param {unknown} value
480
+ * @returns {value is AnyTypedArray}
481
+ */
482
+ function isTypedArray(value) {
483
+ return types.isTypedArray(value);
484
+ }
485
+
486
+ /**
487
+ * Is it an `Error`, including subclasses and errors from other realms?
488
+ *
489
+ * @param {unknown} value
490
+ * @returns {value is Error}
491
+ */
492
+ function isError(value) {
493
+ return types.isNativeError(value) || value instanceof Error;
494
+ }
495
+
496
+ /**
497
+ * Is it empty? `null`, `undefined`, `""`, `[]`, `{}` and empty maps and sets
498
+ * are. Numbers, booleans and functions never are.
499
+ *
500
+ * @example
501
+ * nc.isEmpty({}); // true
502
+ * nc.isEmpty(0); // false
503
+ * nc.isEmpty(" "); // false, see isBlank
504
+ *
505
+ * @param {unknown} value
506
+ * @returns {boolean}
507
+ */
508
+ function isEmpty(value) {
509
+ if (value === null || value === undefined) return true;
510
+ if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
511
+ if (types.isMap(value) || types.isSet(value)) return /** @type {Map<unknown, unknown>} */ (value).size === 0;
512
+ if (typeof value === "object") return Object.keys(value).length === 0;
513
+ return false;
514
+ }
515
+
516
+ /**
517
+ * Is it `null`, `undefined`, or a string with nothing but whitespace?
518
+ *
519
+ * @example
520
+ * nc.isBlank(" \n"); // true
521
+ * nc.isBlank(" a "); // false
522
+ *
523
+ * @param {unknown} value
524
+ * @returns {boolean}
525
+ */
526
+ function isBlank(value) {
527
+ if (value === null || value === undefined) return true;
528
+ return typeof value === "string" && value.trim() === "";
529
+ }
530
+
531
+ /**
532
+ * Is it an array where every item passes `guard`?
533
+ *
534
+ * @example
535
+ * if (nc.isArrayOf(input, nc.isString)) input.join(", "); // input: string[]
536
+ *
537
+ * @template T
538
+ * @param {unknown} value
539
+ * @param {(item: unknown) => item is T} guard Checked against every item, like `nc.isString`.
540
+ * @returns {value is T[]}
541
+ */
542
+ function isArrayOf(value, guard) {
543
+ return Array.isArray(value) && value.every((item) => guard(item));
544
+ }
545
+
546
+ /**
547
+ * Is it one of the allowed values? With a constant list, the value gets the
548
+ * matching literal type.
549
+ *
550
+ * @example
551
+ * const ROLES = ["admin", "user"] as const;
552
+ * if (nc.isOneOf(input, ROLES)) input; // "admin" | "user"
553
+ *
554
+ * @template {readonly unknown[]} L
555
+ * @param {unknown} value
556
+ * @param {L} allowed
557
+ * @returns {value is L[number]}
558
+ */
559
+ function isOneOf(value, allowed) {
560
+ return allowed.includes(value);
561
+ }
562
+
563
+ /**
564
+ * Does it look like an email address? The rules are practical rather than
565
+ * the full RFC: one `@`, no stray dots, a real top-level domain.
566
+ * International addresses are accepted.
567
+ *
568
+ * @example
569
+ * nc.isEmail("jose@exemple.fr"); // true
570
+ * nc.isEmail("john..doe@example.com"); // false
571
+ * nc.isEmail("john@localhost"); // false
572
+ *
573
+ * @param {unknown} value
574
+ * @returns {value is string}
575
+ */
576
+ function isEmail(value) {
577
+ if (typeof value !== "string" || value.length > 254) return false;
578
+ return /^(?!\.)(?!.*\.\.)[\p{L}\p{N}.!#$%&'*+/=?^_`{|}~-]+(?<!\.)@(?:[\p{L}\p{N}](?:[\p{L}\p{N}-]{0,61}[\p{L}\p{N}])?\.)+\p{L}{2,}$/u.test(value);
579
+ }
580
+
581
+ /**
582
+ * Is it an absolute URL with an allowed protocol (http and https by default)?
583
+ *
584
+ * @example
585
+ * nc.isURL("https://example.com/a?b=1"); // true
586
+ * nc.isURL("ftp://example.com", { protocols: ["ftp:"] }); // true
587
+ * nc.isURL("http://localhost:3000", { requireTld: true }); // false
588
+ *
589
+ * @param {unknown} value
590
+ * @param {IsURLOptions} [options]
591
+ * @returns {value is string}
592
+ */
593
+ function isURL(value, options = {}) {
594
+ if (typeof value !== "string") return false;
595
+ const protocols = options.protocols ?? ["http:", "https:"];
596
+ try {
597
+ const url = new URL(value);
598
+ if (!protocols.includes(url.protocol)) return false;
599
+ if (options.requireTld && !/\.[a-z]{2,}$/i.test(url.hostname)) return false;
600
+ return true;
601
+ } catch {
453
602
  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;
603
+ }
604
+ }
605
+
606
+ /**
607
+ * Is it a UUID?
608
+ *
609
+ * @example
610
+ * nc.isUUID(nc.id.uuid()); // true
611
+ * nc.isUUID(id, { version: 7 }); // only v7
612
+ *
613
+ * @param {unknown} value
614
+ * @param {IsUUIDOptions} [options]
615
+ * @returns {value is string}
616
+ */
617
+ function isUUID(value, options = {}) {
618
+ if (typeof value !== "string") return false;
619
+ if (options.version === undefined && /^(0{8}-0{4}-0{4}-0{4}-0{12}|f{8}-f{4}-f{4}-f{4}-f{12})$/i.test(value)) return true;
620
+ const version = options.version === undefined ? "[1-8]" : String(options.version);
621
+ return new RegExp(`^[0-9a-f]{8}-[0-9a-f]{4}-${version}[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`, "i").test(value);
622
+ }
623
+
624
+ /**
625
+ * Is it a string of valid JSON?
626
+ *
627
+ * @example
628
+ * nc.isJSON('{"a":1}'); // true
629
+ * nc.isJSON("{a:1}"); // false
630
+ *
631
+ * @param {unknown} value
632
+ * @returns {value is string}
633
+ */
634
+ function isJSON(value) {
635
+ if (typeof value !== "string") return false;
636
+ try {
637
+ JSON.parse(value);
638
+ return true;
639
+ } catch {
640
+ return false;
641
+ }
642
+ }
643
+
644
+ /**
645
+ * Is it a finite number, or a string that is exactly one (spaces around are fine)?
646
+ *
647
+ * @example
648
+ * nc.isNumeric("4.2e3"); // true
649
+ * nc.isNumeric(" 12 "); // true
650
+ * nc.isNumeric("12px"); // false
651
+ *
652
+ * @param {unknown} value
653
+ * @returns {value is number | string}
654
+ */
655
+ function isNumeric(value) {
656
+ if (typeof value === "number") return Number.isFinite(value);
657
+ if (typeof value !== "string" || value.trim() === "") return false;
658
+ return Number.isFinite(Number(value));
659
+ }
660
+
661
+ /**
662
+ * Is it an IP address?
663
+ *
664
+ * @example
665
+ * nc.isIP("192.168.0.1"); // true
666
+ * nc.isIP("::1"); // true
667
+ * nc.isIP("::1", 4); // false
668
+ *
669
+ * @param {unknown} value
670
+ * @param {4|6} [version] Only accept IPv4 or IPv6.
671
+ * @returns {value is string}
672
+ */
673
+ function isIP(value, version) {
674
+ if (typeof value !== "string") return false;
675
+ const detected = require("node:net").isIP(value);
676
+ return version === undefined ? detected !== 0 : detected === version;
677
+ }
678
+
679
+ /**
680
+ * Is it an IPv4 address, like `192.168.0.1`?
681
+ *
682
+ * @param {unknown} value
683
+ * @returns {value is string}
684
+ */
685
+ function isIPv4(value) {
686
+ return isIP(value, 4);
687
+ }
688
+
689
+ /**
690
+ * Is it an IPv6 address, like `::1`?
691
+ *
692
+ * @param {unknown} value
693
+ * @returns {value is string}
694
+ */
695
+ function isIPv6(value) {
696
+ return isIP(value, 6);
697
+ }
698
+
699
+ /**
700
+ * Is it a valid port (0 to 65535), as a number or a numeric string?
701
+ *
702
+ * @example
703
+ * nc.isPort("8080"); // true
704
+ * nc.isPort(70000); // false
705
+ *
706
+ * @param {unknown} value
707
+ * @returns {boolean}
708
+ */
709
+ function isPort(value) {
710
+ const n = typeof value === "string" && /^\d+$/.test(value.trim()) ? Number(value) : value;
711
+ return Number.isInteger(n) && /** @type {number} */ (n) >= 0 && /** @type {number} */ (n) <= 65535;
712
+ }
713
+
714
+ /**
715
+ * Is it a hexadecimal string? A `0x` prefix is allowed.
716
+ *
717
+ * @param {unknown} value
718
+ * @returns {value is string}
719
+ */
720
+ function isHex(value) {
721
+ return typeof value === "string" && /^(0x)?[0-9a-f]+$/i.test(value);
722
+ }
723
+
724
+ /**
725
+ * Is it a CSS hex color (`#rgb`, `#rgba`, `#rrggbb` or `#rrggbbaa`)?
726
+ *
727
+ * @example
728
+ * nc.isHexColor("#f80"); // true
729
+ * nc.isHexColor("f80"); // false, the # is required
730
+ *
731
+ * @param {unknown} value
732
+ * @returns {value is string}
733
+ */
734
+ function isHexColor(value) {
735
+ return typeof value === "string" && /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value);
736
+ }
737
+
738
+ /**
739
+ * Is it valid Base64?
740
+ *
741
+ * @example
742
+ * nc.isBase64("aGVsbG8="); // true
743
+ * nc.isBase64("aGVsbG8", { urlSafe: true }); // true
744
+ *
745
+ * @param {unknown} value
746
+ * @param {IsBase64Options} [options]
747
+ * @returns {value is string}
748
+ */
749
+ function isBase64(value, options = {}) {
750
+ if (typeof value !== "string" || value === "") return false;
751
+ if (options.urlSafe) return /^[A-Za-z0-9_-]+={0,2}$/.test(value) && value.replace(/=+$/, "").length % 4 !== 1;
752
+ return value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value);
753
+ }
754
+
755
+ /**
756
+ * Is it a semantic version? A leading `v` is fine.
757
+ *
758
+ * @example
759
+ * nc.isSemver("v2.0.0-rc.1"); // true
760
+ * nc.isSemver("1.2"); // false
761
+ *
762
+ * @param {unknown} value
763
+ * @returns {value is string}
764
+ */
765
+ function isSemver(value) {
766
+ return (
767
+ typeof value === "string" &&
768
+ /^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)
769
+ );
770
+ }
771
+
772
+ /**
773
+ * Is it an ISO 8601 date or date-time, and a real calendar date?
774
+ *
775
+ * @example
776
+ * nc.isISODate("2024-02-29T10:00:00Z"); // true
777
+ * nc.isISODate("2023-02-29"); // false, not a leap year
778
+ *
779
+ * @param {unknown} value
780
+ * @returns {value is string}
781
+ */
782
+ function isISODate(value) {
783
+ if (typeof value !== "string") return false;
784
+ const match = /^(\d{4})-(\d{2})-(\d{2})(?:[T ](\d{2}):(\d{2})(?::(\d{2})(?:\.\d{1,9})?)?(Z|[+-]\d{2}:?\d{2})?)?$/.exec(value);
785
+ if (!match) return false;
786
+ const [, y, m, d, hh = "0", mm = "0", ss = "0"] = match;
787
+ const year = Number(y);
788
+ const month = Number(m);
789
+ const day = Number(d);
790
+ if (month < 1 || month > 12 || day < 1) return false;
791
+ const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate();
792
+ return day <= daysInMonth && Number(hh) <= 23 && Number(mm) <= 59 && Number(ss) <= 59;
793
+ }
794
+
795
+ /**
796
+ * Is it a URL slug like `my-first-post`? See `nc.str.slugify()` to make one.
797
+ *
798
+ * @param {unknown} value
799
+ * @returns {value is string}
800
+ */
801
+ function isSlug(value) {
802
+ return typeof value === "string" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
803
+ }
804
+
805
+ /**
806
+ * Is it made only of letters, in any alphabet?
807
+ *
808
+ * @example
809
+ * nc.isAlpha("Élodie"); // true
810
+ * nc.isAlpha("abc1"); // false
811
+ *
812
+ * @param {unknown} value
813
+ * @returns {value is string}
814
+ */
815
+ function isAlpha(value) {
816
+ return typeof value === "string" && /^\p{L}+$/u.test(value);
817
+ }
818
+
819
+ /**
820
+ * Is it made only of letters and digits, in any alphabet?
821
+ *
822
+ * @param {unknown} value
823
+ * @returns {value is string}
824
+ */
825
+ function isAlphanumeric(value) {
826
+ return typeof value === "string" && /^[\p{L}\p{N}]+$/u.test(value);
827
+ }
828
+
829
+ /**
830
+ * Could it be a card number? Checks the length and the Luhn checksum;
831
+ * spaces and dashes are ignored.
832
+ *
833
+ * @example
834
+ * nc.isCreditCard("4242 4242 4242 4242"); // true
835
+ *
836
+ * @param {unknown} value
837
+ * @returns {value is string}
838
+ */
839
+ function isCreditCard(value) {
840
+ if (typeof value !== "string") return false;
841
+ const digits = value.replace(/[\s-]/g, "");
842
+ if (!/^\d{12,19}$/.test(digits)) return false;
843
+ let sum = 0;
844
+ let double = false;
845
+ for (let i = digits.length - 1; i >= 0; i--) {
846
+ let digit = digits.charCodeAt(i) - 48;
847
+ if (double) {
848
+ digit *= 2;
849
+ if (digit > 9) digit -= 9;
527
850
  }
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
- },
851
+ sum += digit;
852
+ double = !double;
853
+ }
854
+ return sum % 10 === 0;
855
+ }
856
+
857
+ /**
858
+ * Does it look like a JSON Web Token? The signature is not verified; use
859
+ * `nc.crypto.verifyJWT()` for that.
860
+ *
861
+ * @param {unknown} value
862
+ * @returns {value is string}
863
+ */
864
+ function isJWT(value) {
865
+ if (typeof value !== "string") return false;
866
+ const parts = value.split(".");
867
+ if (parts.length !== 3 || !parts.slice(0, 2).every((p) => /^[A-Za-z0-9_-]+$/.test(p))) return false;
868
+ if (parts[2] !== "" && !/^[A-Za-z0-9_-]+$/.test(parts[2])) return false;
869
+ try {
870
+ const header = JSON.parse(Buffer.from(parts[0], "base64url").toString("utf8"));
871
+ return isPlainObject(header) && typeof header.alg === "string";
872
+ } catch {
873
+ return false;
874
+ }
875
+ }
876
+
877
+ /**
878
+ * Throws an `AssertionError` if `condition` is falsy. Afterwards, TypeScript
879
+ * knows the condition holds.
880
+ *
881
+ * @example
882
+ * const user = users.find((u) => u.id === id);
883
+ * nc.assert(user, `User ${id} not found`);
884
+ * user.name; // no "possibly undefined" here
885
+ *
886
+ * @param {unknown} condition
887
+ * @param {string | (() => string)} [message="Assertion failed"] A message, or a function that builds it.
888
+ * @returns {asserts condition}
889
+ * @throws {AssertionError}
890
+ */
891
+ function assert(condition, message = "Assertion failed") {
892
+ if (!condition) throw new (require("./errors.js").AssertionError)(typeof message === "function" ? message() : message);
893
+ }
894
+
895
+ /**
896
+ * Throws an `AssertionError` unless `value` passes `guard`. Afterwards,
897
+ * `value` has the guarded type.
898
+ *
899
+ * @example
900
+ * nc.assertType(config.port, nc.isInteger, "port must be an integer");
901
+ *
902
+ * @template T
903
+ * @param {unknown} value
904
+ * @param {(value: unknown) => value is T} guard A check like `nc.isString`.
905
+ * @param {string | (() => string)} [message]
906
+ * @returns {asserts value is T}
907
+ * @throws {AssertionError}
908
+ */
909
+ function assertType(value, guard, message) {
910
+ if (!guard(value)) {
911
+ const text = typeof message === "function" ? message() : message;
912
+ throw new (require("./errors.js").AssertionError)(text ?? `Expected value to pass ${guard.name || "the type guard"}`);
913
+ }
914
+ }
915
+
916
+ module.exports = {
917
+ isArray,
918
+ isNumber,
919
+ isFinite,
920
+ isInteger,
921
+ isSafeInteger,
922
+ isFloat,
923
+ isPositive,
924
+ isNegative,
925
+ isBoolean,
926
+ isString,
927
+ isSymbol,
928
+ isBigInt,
929
+ isUndefined,
930
+ isNull,
931
+ isNil,
932
+ isDefined,
933
+ isPrimitive,
934
+ isFunction,
935
+ isAsyncFunction,
936
+ isGeneratorFunction,
937
+ isGenerator,
938
+ isClass,
939
+ isObject,
940
+ isPlainObject,
941
+ isPromise,
942
+ isRegExp,
943
+ isDate,
944
+ isValidDate,
945
+ isMap,
946
+ isSet,
947
+ isWeakMap,
948
+ isWeakSet,
949
+ isIterable,
950
+ isAsyncIterable,
951
+ isBuffer,
952
+ isTypedArray,
953
+ isError,
954
+ isEmpty,
955
+ isBlank,
956
+ isArrayOf,
957
+ isOneOf,
958
+ isEmail,
959
+ isURL,
960
+ isUUID,
961
+ isJSON,
962
+ isNumeric,
963
+ isIP,
964
+ isIPv4,
965
+ isIPv6,
966
+ isPort,
967
+ isHex,
968
+ isHexColor,
969
+ isBase64,
970
+ isSemver,
971
+ isISODate,
972
+ isSlug,
973
+ isAlpha,
974
+ isAlphanumeric,
975
+ isCreditCard,
976
+ isJWT,
977
+ assert,
978
+ assertType,
547
979
  };