UNPKG

10.6 kBJavaScriptView Raw
1/**
2 * psycho-type
3 * Copyright(c) 2020 Dmitry Kokhanevych
4 * MIT Licensed
5 */
6
7'use strict';
8
9/**
10 * Module exports.
11 */
12
13var 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
26type.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
42type.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
62type.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
81type.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
103type.bigInt =
104type.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
119type.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
135type.bool =
136type.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
165type.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
186type.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
201type.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
216type.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
231type.NaN =
232type.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
248type.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
265type.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
299type.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
329type.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
361type.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
392type.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
424type.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
468type.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
508type.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