safe-integer-ts
Version:
Newtype wrapper of number type as safe-integer (53-bit precise integer)
78 lines (66 loc) • 2.41 kB
text/typescript
/**
* A number, which is validated as *safe-integer* or 53-bit precise integer.
*
* @see [Number.isSafeInteger()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger)
*/
export type SafeInteger = number & { [IS_SAFE_INTEGER]: true }
declare const IS_SAFE_INTEGER: unique symbol
/**
* Checks if an unknown value is safe-integer without any conversion.
*/
export const isSafeInteger = Number.isSafeInteger as (value: unknown) => value is SafeInteger
/**
* Casts an unknown value as safe-integer without any conversion; or null if unable.
*/
export const asSafeInteger = (value: unknown): SafeInteger | null =>
Number.isSafeInteger(value)
? value as SafeInteger
: null
/**
* Parses a string as safe-integer; or null if unable.
*
* This function just forwards to [Number.parseInt()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/parseInt).
*/
export const parseSafeInteger = (str: string, radix?: number): SafeInteger | null =>
asSafeInteger(Number.parseInt(str, radix))
/**
* Tries to convert an unknown value to safe-integer, using some conversions if necessary: `Math.round`, `parseInt` or `.valueOf`.
*
* Mapping:
*
* - number:
* - safe-integer: the value
* - finite number: `Math.round` and `asSafeInteger` is used.
* - infinity, NaN: null
* - string: `parseInt` and `asSafeInteger` is used.
* - object:
* - non-null and has `valueOf`: `.valueOf()` and `asSafeInteger` is used.
* - other: null
* - undefined, boolean, function and anything else: null
*/
// TODO: Support bigint in future
export const toSafeInteger = (value: unknown): SafeInteger | null => {
switch (typeof value) {
case "number":
return numberToSafeInteger(value)
case "string":
return parseSafeInteger(value)
case "object":
return objectToSafeInteger(value)
default:
return null
}
}
const numberToSafeInteger = (value: number): SafeInteger | null =>
Number.isFinite(value)
? asSafeInteger(Math.round(value))
: null
const objectToSafeInteger = (obj: object | null): SafeInteger | null => {
if (typeof (obj as { valueOf?(): unknown } | null)?.valueOf !== "function") {
return null
}
const value = (obj as { valueOf(): unknown }).valueOf()
return typeof value === "number"
? numberToSafeInteger(value)
: null
}