@sygn/types
v1.1.1
Published
Type checking utilities — is*, not*, has*, all*, getFirst*, force* for every JS type, plus typeOf, intoArray and instanceOf
Readme
@sygn/types
🧬 The modern successor to types.js — a zero-dependency ESM + TypeScript rewrite. The type-checking API is compatible (
is/not/has/all/getFirst, plustypeOf,intoArray,*StringOrNumber);force*is redesigned (no auto-conversion) and the legacyenumand logging helpers are dropped for good.
Tiny, fast, dynamic type checking for JavaScript and TypeScript. Every JS type gets
six forms — is, not, has, all, getFirst, force — shipped as tree-shakeable
ES modules with first-class TypeScript type guards.
Why
Static types vanish at runtime. @sygn/types lets you assert and enforce types on
real values as your app runs — safely handling null, NaN, boxed primitives, and
the other awkward corners of JavaScript — so a bad value becomes a handled case
instead of a crash.
- Six forms per type:
is[Type],not[Type],has[Type],all[Type],getFirst[Type],force[Type] - Check many values at once with
has/all, or pick one withgetFirst forcea guaranteed return type — the value, a valid replacement, or a type literal- Real TypeScript type guards —
is[Type]narrows inifblocks - Extras:
typeOf,intoArray,instanceOf, and*StringOrNumberchecks - Zero dependencies, ESM, importable per type or per function
Install
npm install @sygn/typesUsage
Import from the package root, or from a per-type / per-function subpath so bundlers only pull in what you use:
// everything from the barrel
import { isString, allNumber, forceArray } from '@sygn/types';
// just one type's five forms
import { isString, notString, hasString, allString, forceString } from '@sygn/types/string';
// a single function
import { isString } from '@sygn/types/isString';Browser (global)
No build step or bundler? Load the prebuilt bundle with a plain <script> and it
binds a global Types:
<script src="https://cdn.jsdelivr.net/npm/@sygn/types"></script>
<script>
Types.isString( 'hi' ); // true
Types.forceNumber( 'x', 0 ); // 0
Types.instanceOf( [], Array ); // true
</script>Types is a flat object holding every is / not / has / all / getFirst /
force function plus typeOf (and Types.typeof), intoArray, instanceOf,
notInstanceOf, the *StringOrNumber checks, and createForce — e.g. Types.isArray,
Types.getFirstString, Types.typeOf( x ).
The six forms
is[Type]( value ) and not[Type]( value ) — single-value checks.
isString( 'hello' ); // true
isString( 23456 ); // false
isBoolean( false ); // true
isArray( [ 1, 2, 3 ] ); // true
isObject( [ 1, 2, 3 ] ); // false
isObject( /re/g ); // false
isObject( { a: 1 } ); // true
isNaN( Number( 'x' ) ); // true
notNull( '' ); // true
notUndefined( undefined );// false
isDefined( null ); // true (only `undefined` is "not defined")
isDefined( undefined ); // falseall[Type]( ...values ) — true when every argument is of the type.
allString( '', ' ', 'text' ); // true
allString( '', ' ', 'text', 123 ); // false
allArray( [ 1, 2 ], [ {} ], [ false, true ] );// true
allArray( [ 1, 2 ], [ {} ], /re/ ); // falsehas[Type]( ...values ) — true when at least one argument is of the type.
hasString( 123, { v: 1 }, [ '?' ] ); // false
hasFunction( 123, {}, () => {} ); // true
hasUndefined( 'x', 123, null ); // false
hasUndefined( 'x', 123, undefined ); // trueNote:
all[Type]()with no arguments istrue(vacuously), andhas[Type]()with no arguments isfalse— they followArray.prototype.every/.some.
getFirst[Type]( ...values ) — the first argument of the type, else undefined.
getFirstString( 1, 'a', 'b' ); // 'a'
getFirstNumber( 'x', {}, 5 ); // 5
getFirstArray( 1, [ 9 ], [ 2 ] ); // [ 9 ]
getFirstString( 1, 2 ); // undefinedforce[Type]( value, ...replacements ) — always returns a value of the type.
It returns value if it matches, otherwise the first replacement that matches,
otherwise a literal of that type. No coercion happens — a value either is the
type or it doesn't count.
forceString( undefined ); // '' (type literal)
forceString( null, 'ok' ); // 'ok' (replacement matches)
forceString( 33 ); // '' (33 is not a string — not converted)
forceString( 33, 'fallback' ); // 'fallback'
forceNumber( '35px' ); // 0 (not parsed; type literal)
forceNumber( '35px', 42 ); // 42
forceArray( 'text' ); // []
forceArray( 'text', [ 1, 2 ] ); // [ 1, 2 ]Because forceFunction always returns something callable, you can guard calls in one
line without crashing:
const log = null;
forceFunction( log, console.log )( 'safe!' ); // logs 'safe!' (replacement used)
forceFunction( log, null )( 'nothing' ); // no-op — returns the empty () => {} literalinstanceOf
For arbitrary constructors, instanceOf / notInstanceOf take one or more classes and
check them all:
import { instanceOf, notInstanceOf } from '@sygn/types/instanceOf';
instanceOf( new Date(), Date ); // true
instanceOf( x, Date, Object ); // true only if x is an instance of every class
notInstanceOf( 'plain', Date, RegExp ); // trueMore helpers
typeOf( value ) — a reliable, lowercase type name (a saner typeof). Matches
types.js for the shared types and extends to the modern ones:
typeOf( [] ); // 'array'
typeOf( null ); // 'null'
typeOf( Number( 'x' ) ); // 'nan'
typeOf( /re/ ); // 'regexp'
typeOf( new Map() ); // 'map'
typeOf( 5n ); // 'bigint'intoArray( ...values ) — normalize arguments, or a space-delimited string, into
an array:
intoArray( '1 2 3' ); // [ '1', '2', '3' ]
intoArray( 'a', 'b' ); // [ 'a', 'b' ]
intoArray( [ 1, 2 ] ); // [ 1, 2 ]*StringOrNumber — the is / not / has / all / getFirst forms for the
"string or number" union:
isStringOrNumber( 5 ); // true
allStringOrNumber( 'a', 1 ); // true
getFirstStringOrNumber( {}, 3 ); // 3TypeScript
Every is[Type] is a type guard, so it narrows inside conditionals:
function greet( value: unknown ) {
if ( isString( value ) ) {
return value.toUpperCase(); // value is `string` here
}
return forceString( value ); // always a `string`
}createForce
force[Type] is built on createForce, which you can use to make your own enforcers
for any predicate:
import createForce from '@sygn/types/createForce';
import { isString } from '@sygn/types/string';
const forceUpper = createForce( isString, s => s.toUpperCase(), '' );
forceUpper( 'hi' ); // 'HI'
forceUpper( 42 ); // '' (no match, no replacement -> literal)
forceUpper( 42, 'ok' ); // 'OK' (replacement matches, then transformed)Auto-converting force
force* never coerces — a value is the type or it isn't. If you want the old
types.js auto-convert behaviour (e.g. '35px' → 35), build it explicitly with
createForce. Coercion stays opt-in and local, instead of a global flag:
import { isNumber } from '@sygn/types/number';
const toNumber = createForce(
v => isNumber( v ) || ( isString( v ) && Number.isInteger( parseInt( v, 10 ) ) ),
v => isString( v ) ? parseInt( v, 10 ) : v,
0
);
toNumber( '35px' ); // 35
toNumber( 'abc' ); // 0 (not convertible -> literal)
toNumber( 'abc', 12 ); // 12 (replacement)Supported types
Each type below exposes is / not / has / all / getFirst / force. Import it as
@sygn/types/<name>.
| Name | is narrows to (TS) | force fallback |
| --------------- | ---------------------------------- | ----------------------- |
| array | unknown[] | [] |
| arrowFunction | ( ...args: unknown[] ) => unknown| () => {} |
| bigint | bigint | 0n |
| boolean | boolean | false |
| date | Date | new Date( 0 ) |
| defined | T (from T \| undefined) | null |
| error | Error | new Error() |
| function | ( ...args: unknown[] ) => unknown| () => {} |
| map | Map<unknown, unknown> | new Map() |
| nan | number (an actual NaN) | NaN |
| null | null | null |
| number | number | 0 |
| object | Record<string, unknown> | {} |
| promise | Promise<unknown> | Promise.resolve() |
| regExp | RegExp | /(?:)/ |
| set | Set<unknown> | new Set() |
| string | string | '' |
| symbol | symbol | Symbol( 'default' ) |
| undefined | undefined | undefined |
| url | URL | new URL( 'about:blank' ) |
| weakMap | WeakMap<object, unknown> | new WeakMap() |
| weakSet | WeakSet<object> | new WeakSet() |
Plus instanceOf / notInstanceOf (see above).
License
ISC © SYGN — Dennis van der Sluis
