1 | /**
|
2 | * psycho-type
|
3 | * Copyright(c) 2020 Dmitry Kokhanevych
|
4 | * MIT Licensed
|
5 | */
|
6 |
|
7 | ;
|
8 |
|
9 | /**
|
10 | * Module exports.
|
11 | */
|
12 |
|
13 | var type = module.exports = {};
|
14 |
|
15 | /**
|
16 | * Allow any type.
|
17 | *
|
18 | * @return {Function}:
|
19 | * @param {Any} object
|
20 | * @return {Boolean}
|
21 | * @private
|
22 | *
|
23 | * @public
|
24 | */
|
25 |
|
26 | type.any = () => {
|
27 | return () => true;
|
28 | };
|
29 |
|
30 | /**
|
31 | * Allow string.
|
32 | * Works with both " 'string' " and " new String() ".
|
33 | *
|
34 | * @return {Function}:
|
35 | * @param {Any} object
|
36 | * @return {Boolean}
|
37 | * @private
|
38 | *
|
39 | * @public
|
40 | */
|
41 |
|
42 | type.string = () => {
|
43 | return object => typeof object === 'string' ||
|
44 | object instanceof String;
|
45 | };
|
46 |
|
47 | /**
|
48 | * Allow number.
|
49 | * Works with all numeric type object: Number and Bigint,
|
50 | * and they can be created at any way.
|
51 | *
|
52 | * ! NaN is a valid number in this type !
|
53 | *
|
54 | * @return {Function}:
|
55 | * @param {Any} object
|
56 | * @return {Boolean}
|
57 | * @private
|
58 | *
|
59 | * @public
|
60 | */
|
61 |
|
62 | type.anyNumber = () => {
|
63 | return object => typeof object === 'number' ||
|
64 | typeof object === 'bigint' ||
|
65 | object instanceof Number;
|
66 | };
|
67 |
|
68 | /**
|
69 | * Only allow small number type.
|
70 | * Works with both " 3 " and " new Number() ".
|
71 | * Doesn't works for BigInt and NaN.
|
72 | *
|
73 | * @return {Function}:
|
74 | * @param {Any} object
|
75 | * @return {Boolean}
|
76 | * @private
|
77 | *
|
78 | * @public
|
79 | */
|
80 |
|
81 | type.number = () => {
|
82 | return object => !Number.isNaN( object ) && (
|
83 | typeof object === 'number' ||
|
84 | object instanceof Number
|
85 | );
|
86 | };
|
87 |
|
88 | /**
|
89 | * Only allow big number type.
|
90 | * Works with both " 9007199254740991n " and " new BigInt() ".
|
91 | * Doesn't works for regular numbers and NaN.
|
92 | *
|
93 | * Alias: type.bigInt
|
94 | *
|
95 | * @return {Function}:
|
96 | * @param {Any} object
|
97 | * @return {Boolean}
|
98 | * @private
|
99 | *
|
100 | * @public
|
101 | */
|
102 |
|
103 | type.bigInt =
|
104 | type.bigNumber = () => {
|
105 | return object => typeof object === 'bigint';
|
106 | };
|
107 |
|
108 | /**
|
109 | * Allow symbol type.
|
110 | *
|
111 | * @return {Function}:
|
112 | * @param {Any} object
|
113 | * @return {Boolean}
|
114 | * @private
|
115 | *
|
116 | * @public
|
117 | */
|
118 |
|
119 | type.symbol = () => {
|
120 | return object => typeof object === 'symbol';
|
121 | };
|
122 |
|
123 | /**
|
124 | * Allow boolean type.
|
125 | * Works with both " true " and " new Boolean( 1 ) ".
|
126 | *
|
127 | * @return {Function}:
|
128 | * @param {Any} object
|
129 | * @return {Boolean}
|
130 | * @private
|
131 | *
|
132 | * @public
|
133 | */
|
134 |
|
135 | type.bool =
|
136 | type.boolean = () => {
|
137 | return object => typeof object === 'boolean' ||
|
138 | object instanceof Boolean;
|
139 | };
|
140 |
|
141 | /**
|
142 | * Allow object type.
|
143 | * Works only with objects, that not used for
|
144 | * standart data types.
|
145 | *
|
146 | * Examples:
|
147 | *
|
148 | * type.object()( new String( 'something' ) )'
|
149 | * // => false
|
150 | *
|
151 | * type.object()( { "param": "value" } )'
|
152 | * // => true
|
153 | *
|
154 | * type.object()( {} )'
|
155 | * // => true
|
156 | *
|
157 | * @return {Function}:
|
158 | * @param {Any} object
|
159 | * @return {Boolean}
|
160 | * @private
|
161 | *
|
162 | * @public
|
163 | */
|
164 |
|
165 | type.object = () => {
|
166 | return object => !!(
|
167 | object &&
|
168 | object.constructor &&
|
169 | object.constructor === Object
|
170 | );
|
171 | };
|
172 |
|
173 | /**
|
174 | * Allow functions.
|
175 | *
|
176 | * ! Any class/constructor will return true on check !
|
177 | *
|
178 | * @return {Function}:
|
179 | * @param {Any} object
|
180 | * @return {Boolean}
|
181 | * @private
|
182 | *
|
183 | * @public
|
184 | */
|
185 |
|
186 | type.function = () => {
|
187 | return object => typeof object === 'function';
|
188 | };
|
189 |
|
190 | /**
|
191 | * Allow undefined type.
|
192 | *
|
193 | * @return {Function}:
|
194 | * @param {Any} object
|
195 | * @return {Boolean}
|
196 | * @private
|
197 | *
|
198 | * @public
|
199 | */
|
200 |
|
201 | type.undefined = () => {
|
202 | return object => object === undefined;
|
203 | };
|
204 |
|
205 | /**
|
206 | * Only allow null.
|
207 | *
|
208 | * @return {Function}:
|
209 | * @param {Any} object
|
210 | * @return {Boolean}
|
211 | * @private
|
212 | *
|
213 | * @public
|
214 | */
|
215 |
|
216 | type.null = () => {
|
217 | return object => object === null;
|
218 | };
|
219 |
|
220 | /**
|
221 | * Only allow NaN.
|
222 | *
|
223 | * @return {Function}:
|
224 | * @param {Any} object
|
225 | * @return {Boolean}
|
226 | * @private
|
227 | *
|
228 | * @public
|
229 | */
|
230 |
|
231 | type.NaN =
|
232 | type.nan = () => {
|
233 | return object => Number.isNaN( object );
|
234 | };
|
235 |
|
236 | /**
|
237 | * Allow " no value " types.
|
238 | * Works with undefined, null and NaN.
|
239 | *
|
240 | * @return {Function}:
|
241 | * @param {Any} object
|
242 | * @return {Boolean}
|
243 | * @private
|
244 | *
|
245 | * @public
|
246 | */
|
247 |
|
248 | type.noValue = () => {
|
249 | return object => object == undefined ||
|
250 | Number.isNaN( object );
|
251 | };
|
252 |
|
253 | /**
|
254 | * Only allow " empty " values.
|
255 | * Works with {}, [], '' and 0.
|
256 | *
|
257 | * @return {Function}:
|
258 | * @param {Any} object
|
259 | * @return {Boolean}
|
260 | * @private
|
261 | *
|
262 | * @public
|
263 | */
|
264 |
|
265 | type.empty = () => {
|
266 | return object => {
|
267 | const type = typeof object;
|
268 |
|
269 | // Strings and Arrays have valid length property.
|
270 | if ( type === 'string' || object instanceof Array ) {
|
271 | return object.length === 0;
|
272 | }
|
273 |
|
274 | // Because " new Date() " have zero keys --
|
275 | // we need to check constructor of the object.
|
276 | if ( object && object.constructor && object.constructor === Object ) {
|
277 | return Object.keys( object ).length === 0;
|
278 | }
|
279 |
|
280 | if ( type === 'number' ) {
|
281 | return object === 0;
|
282 | }
|
283 |
|
284 | return false;
|
285 | };
|
286 | };
|
287 |
|
288 | /**
|
289 | * Allow array with any types inside.
|
290 | *
|
291 | * @return {Function}:
|
292 | * @param {Any} object
|
293 | * @return {Boolean}
|
294 | * @private
|
295 | *
|
296 | * @public
|
297 | */
|
298 |
|
299 | type.array = () => {
|
300 | return object => object instanceof Array;
|
301 | };
|
302 |
|
303 | /**
|
304 | * Allow array with specified types.
|
305 | *
|
306 | * Examples:
|
307 | *
|
308 | * // Valid types
|
309 | * type.arrayOf( type.string(), type.number() );
|
310 | * type.arrayOf( [ type.string(), type.number() ] );
|
311 | * // => function([ 3, 'string' ]);
|
312 | * // => true
|
313 | *
|
314 | * // Invalid types
|
315 | * type.arrayOf( type.string(), type.number() );
|
316 | * type.arrayOf( [ type.string(), type.number() ] );
|
317 | * // => function([ { }, [ ] ]);
|
318 | * // => false
|
319 | *
|
320 | * @param {Function} ...types
|
321 | * @return {Function}:
|
322 | * @param {Array}
|
323 | * @return {Boolean}
|
324 | * @private
|
325 | *
|
326 | * @public
|
327 | */
|
328 |
|
329 | type.arrayOf = ( ...types ) => {
|
330 | return array => array instanceof Array &&
|
331 | array.every( v => types.some( t => t( v ) ) );
|
332 | };
|
333 |
|
334 | /**
|
335 | * Only allow some values.
|
336 | *
|
337 | * Example:
|
338 | *
|
339 | * // Valid data
|
340 | * type.enum( 'foo', 'bar' );
|
341 | * type.enum( [ 'foo', 'bar' ] );
|
342 | * // => function( 'foo' );
|
343 | function( 'bar' );
|
344 | * // => true
|
345 | *
|
346 | * // Invalid data
|
347 | * type.enum( 'foo', 'bar' );
|
348 | * type.enum( [ 'foo', 'bar' ] );
|
349 | * // => function( 'not foo' );
|
350 | * // => false
|
351 | *
|
352 | * @param {Any} ...values
|
353 | * @return {Function}:
|
354 | * @param {Any}
|
355 | * @return {Boolean}
|
356 | * @private
|
357 | *
|
358 | * @public
|
359 | */
|
360 |
|
361 | type.enum = ( ...values ) => {
|
362 | return object => values.includes( object ); // TODO: fix 'new Object()' issue
|
363 | };
|
364 |
|
365 | /**
|
366 | * Only allow some types.
|
367 | *
|
368 | * Example:
|
369 | *
|
370 | * // Valid types
|
371 | * type.someOf( type.string(), type.boolean() );
|
372 | * type.someOf( [ type.string(), type.boolean() ] );
|
373 | * // => function( 'foo' );
|
374 | function( false );
|
375 | * // => true
|
376 | *
|
377 | * // Invalid types
|
378 | * type.someOf( type.string(), type.boolean() );
|
379 | * type.someOf( [ type.string(), type.boolean() ] );
|
380 | * // => function( 3 );
|
381 | * // => false
|
382 | *
|
383 | * @param {Function} ...types
|
384 | * @return {Function}:
|
385 | * @param {Any}
|
386 | * @return {Boolean}
|
387 | * @private
|
388 | *
|
389 | * @public
|
390 | */
|
391 |
|
392 | type.someOf = ( ...types ) => {
|
393 | return object => types.some( t => t( object ) );
|
394 | };
|
395 |
|
396 | /**
|
397 | * Only disallow some types.
|
398 | *
|
399 | * Example:
|
400 | *
|
401 | * // Valid types
|
402 | * type.not( type.string(), type.boolean() );
|
403 | * type.not( [ type.string(), type.boolean() ] );
|
404 | * // => function( 3 );
|
405 | function( {} );
|
406 | * // => true
|
407 | *
|
408 | * // Invalid types
|
409 | * type.not( type.string(), type.boolean() );
|
410 | * type.not( [ type.string(), type.boolean() ] );
|
411 | * // => function( 'foo' );
|
412 | function( true );
|
413 | * // => false
|
414 | *
|
415 | * @param {Function} ...types
|
416 | * @return {Function}:
|
417 | * @param {Any}
|
418 | * @return {Boolean}
|
419 | * @private
|
420 | *
|
421 | * @public
|
422 | */
|
423 |
|
424 | type.not = ( ...types ) => {
|
425 | return object => !types.some( t => t( object ) );
|
426 | };
|
427 |
|
428 | /**
|
429 | * Check if input have valid type relying on reference.
|
430 | * Input|Reference can be ( nested ) objects or
|
431 | * just pair type|value respectively.
|
432 | *
|
433 | * Example:
|
434 | *
|
435 | * type.check(
|
436 | * { // Reference
|
437 | * 'foo': type.string(), //
|
438 | * 'bar': type.boolean() //
|
439 | * },
|
440 | * { // Input
|
441 | * 'foo': 'String', //
|
442 | * 'bar': false //
|
443 | * }
|
444 | * );
|
445 | * // => true
|
446 | *
|
447 | * type.check(
|
448 | * { // Reference
|
449 | * 'foo': type.string(), //
|
450 | * 'bar': type.boolean() //
|
451 | * },
|
452 | * { // Input
|
453 | * 'foo': true, //
|
454 | * 'bar': false //
|
455 | * }
|
456 | * );
|
457 | * // => false
|
458 | *
|
459 | * type.check( type.string(), 'string' );
|
460 | * // => true
|
461 | *
|
462 | * @param {Function} reference
|
463 | * @param {Any} input
|
464 | * @return {Boolean}
|
465 | * @public
|
466 | */
|
467 |
|
468 | type.check = ( reference, input ) => {
|
469 | // Simple check
|
470 | const checkType = ( t, v ) => t( v );
|
471 |
|
472 | // Recursion for ( nested ) objects
|
473 | const checkObject = ( ref, obj ) => {
|
474 | return !type.object()( ref ) // if ref isn't object:
|
475 | ? false // return false
|
476 | : Object.entries( ref ).every( ([ k, t ]) => {
|
477 | return t.constructor === Object
|
478 | ? checkObject( t, obj[ k ] )
|
479 | : t( obj[ k ] );
|
480 | });
|
481 | };
|
482 |
|
483 | return typeof reference === 'function'
|
484 | ? checkType( reference, input )
|
485 | : checkObject( reference, input ); // If reference is not the function
|
486 | // it expected to be an object.
|
487 | };
|
488 |
|
489 | /**
|
490 | * Returns real type of input.
|
491 | *
|
492 | * Example:
|
493 | *
|
494 | * type.of( Number ); // Because it's the constructor.
|
495 | * //=> 'function' //
|
496 | *
|
497 | * type.of( new Number(3) );
|
498 | * //=> 'number'
|
499 | *
|
500 | * type.of( NaN );
|
501 | * //=> 'NaN'
|
502 | *
|
503 | * @param {Any} item
|
504 | * @return {String}
|
505 | * @public
|
506 | */
|
507 |
|
508 | type.of = item => {
|
509 | // Typof 'number' can be real number ot NaN.
|
510 | const numberOrNaN = i => Number.isNaN( i )
|
511 | ? 'NaN'
|
512 | : 'number';
|
513 |
|
514 | // Detects real type for items,
|
515 | // that have typeof 'object'.
|
516 | const detectObjectType = i => {
|
517 | switch ( true ) {
|
518 | case item instanceof Number:
|
519 | return numberOrNaN( i );
|
520 | case item instanceof String:
|
521 | return 'string';
|
522 | case item instanceof Boolean:
|
523 | return 'boolean';
|
524 | case item instanceof Array:
|
525 | return 'array';
|
526 | case item === null:
|
527 | return 'null';
|
528 | default:
|
529 | return 'object';
|
530 | }
|
531 | };
|
532 |
|
533 | switch ( typeof item ) {
|
534 | case 'object':
|
535 | return detectObjectType( item );
|
536 | case 'number':
|
537 | return numberOrNaN( item );
|
538 | default:
|
539 | return typeof item;
|
540 | }
|
541 | };
|
542 |
|