Skip to content

Instantly share code, notes, and snippets.

@unitario
Created September 9, 2025 17:04
Show Gist options
  • Select an option

  • Save unitario/fdaf00c20f364c6909a857e9910965a8 to your computer and use it in GitHub Desktop.

Select an option

Save unitario/fdaf00c20f364c6909a857e9910965a8 to your computer and use it in GitHub Desktop.
A tiny curried TypeScript type guard
/**
* Maps a reference type to its runtime value type.
*/
type ValueTypeOf<T> = T extends
| StringConstructor
| NumberConstructor
| BooleanConstructor
| SymbolConstructor
| BigIntConstructor
? ReturnType<T>
: T extends new (
...args: unknown[]
) => unknown
? InstanceType<T>
: T
/**
* Runtime type guard that checks if `value` matches the given `reference`.
*
* `reference` can be:
* - A primitive constructor: `String`, `Number`, `Boolean`, `Symbol`, `BigInt`
* - A class/constructor
* - A primitive value (e.g., `"foo"`, `123`, `null`, `undefined`)
*
* Two call forms:
*
* 1) Binary:
* `is(reference, value): value is ValueTypeOf<typeof reference>`
*
* 2) Curried:
* `is(reference)(value): value is ValueTypeOf<typeof reference>`
*
* @typeParam T Reference type used to determine the predicate’s narrowed type.
*
* @example
* is(String, "string") // returns true
* is("string", "string") // returns true
* is("string", String) // returns true
* is(Number, 1234) // returns true
* is(Boolean, true) // returns true
* is(Array, []) // returns true
* is(Class, Class) // returns true
* is(Class, new Class()) // returns true
* is(ParentClass, ChildClass) // returns true
*
* is(String, 1234) // returns false
* is(1234, "1234") // returns false
*
* [1, 2, 3].every(is(Number)) // returns true
* [1, 2, "3"].every(is(Number)) // returns false
*
* @remarks
* - By design, this compares constructor/function names and prototype chains.
* If two unrelated classes share the same name, they are considered a match.
*/
function is<T>(reference: T, value: unknown): value is ValueTypeOf<T>;
function is<T>(reference: T): (value: unknown) => value is ValueTypeOf<T>;
function is<T>(reference: T, ...args: [] | [unknown]) {
const getPrototypeName = (value: unknown) => {
if (value instanceof Function) {
return value.name
}
if (value instanceof Object) {
return value.constructor.name
}
return Object.prototype.toString.call(value).slice(8, -1)
}
if (args.length === 0) {
return (value: unknown) => is(reference, value)
}
return (
getPrototypeName(reference) === getPrototypeName(args[0]) ||
(args[0] instanceof Object && is(reference, Object.getPrototypeOf(args[0])))
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment