Common
The @perfective/common package provides functions to work with undefined and null values,
utility types to work with undefined and null
(similar to NonNullable<T>).
It also provides functions to work with type and instance definitions.
Value
-
type Defined<T> = T extends undefined ? never : T— constructs a type by excludingundefinedfromT.-
defined<T>(value: T | undefined): T— returns a givenvalue, if it is defined. Throws aTypeErrorotherwise. -
isDefined<T>(value: T | undefined): value is Defined<T>— a type guard that returnstrue, if a given value is notundefined.
-
-
type Undefined<T> = T extends undefined ? T : never— constructs a type by excluding defined types fromT.-
isUndefined<T>(value: T | undefined): value is undefined— a type guard that returnstrueif a givenvalueisundefined.
-
-
type NotNull<T> = T extends null ? never : T— constructs a type by excludingnullfromT.-
notNull<T>(value: T | null): T— returns a givenvalue, if it is not null. Throws aTypeErrorotherwise. -
isNotNull<T>(value: T | null): value is NotNull<T>— a type guard that returnstrueif a given value is notnull.
-
-
type Null<T> = T extends null ? T : never— constructs a type by excluding non-nulltypes fromT.-
isNull<T>(value: T | null): value is null— a type guard that returnstrueif a given value isnull.
-
-
type Present<T> = T extends null | undefined ? never : T— constructs a type by excludingnullandundefinedfromT.Same as
NonNullable<T>, added for consistency with the terminology of the package. It is defined as a conditional type (notT & {}) to ensure support of TS compiler before v4.9.-
present<T>(value: T | null | undefined): T— returns a givenvalue, if it is defined and is not null. Throws aTypeErrorotherwise. -
isPresent<T>(value: T | null | undefined): value is Present<T>— a type guard that returnstrueif a givenvalueis defined and is notnull.
-
-
type Absent<T> = T extends null | undefined ? T : never— constructs a type by excluding non-nulland defined types fromT.-
isAbsent<T>(value: T | null | undefined): value is null | undefined— a type guard that returntrueif a givenvalueisnullorundefined.
-
|
|
Type and Instance
-
TypeGuard<T, V extends T> = (value: T) ⇒ value is V— a function type to check if the value is of a certain type. -
Instance<T, U extends any[] = any[]> = abstract new(…args: U) ⇒ T— definition for class type reference:-
isInstanceOf<T, U extends any[] = any[], V = unknown>(type: Instance<T, U>): (value: T | V) ⇒ value is T— creates a type guard that returns true if a passed argument is an instance of a giventype. -
isNotInstanceOf<T, U extends any[] = any[], V = unknown>(type: Instance<T, U>): (value: T | V) ⇒ value is V— creates a type guard that returns true if a passed argument is not an instance of a giventype.
-
|
The
|
ECMA and TypeScript types
JavaScript provides the typeof operator that returns 'object' for arrays and null.
This results in additional checks needed using this operator.
To reduce this complexity, the typeOf<T> function is provided with the conditional TypeOf<T> type.
-
Types
-
EcmaType— is one ofundefined,boolean,number,bigint,string,symbol,function, orobject. -
TsType— is one ofEcmaTypeornull,array, andunknown. -
TypeOf<T>— a conditional type that returnsTsTypedynamically.
-
-
Unit functions:
-
ecmaType(type: EcmaType): EcmaType— Type guards anEcmaTypevalue in compile time (unlike theasoperator which allows to type cast any string asEcmaType). Throws aTypeErrorwhen the value in runtime is not anEcmaType. -
tsType(type: TsType): TsType— Type guards aTsTypevalue in compile time (unlike theasoperator which allows to type cast any string asTsType). Throws aTypeErrorwhen the value in runtime is not aTsType. -
typeOf<T>(value: T | null | undefined): TypeOf<T> & TsType. — returns the name of a TypeScript type of a givenvalue.
-
-
Type guards:
-
isEcmaType(input: string): input is EcmaType— returns true and narrows the variable type, if the given input string is anEcmaType. -
isTsType(input: string): input is TsType— returns true and narrows the variable type, if the given input string is aTsType.
-
-
Predicates:
-
isTypeOf<T>(type: TypeOf): (value: T | null | undefined) ⇒ boolean— creates a predicate that returnstrue, if a passed argument is of a given TypeScript type. -
isNotTypeOf<T>(type: TypeOf): (value: T | null | undefined) ⇒ boolean— creates a predicate that returnstrue, if a passed argument is not of a given TypeScript type.
-
Handling void
In TypeScript, void type
is treated differently from undefined and null,
and linters provide different rules to restrict its usage only to correct cases.
Unfortunately, different packages (like AWS SDK) may use void as a synonym to null | undefined.
This approach creates conflicts in the code linted with the
@perfective/eslint-config,
so a special type and function are introduced to "type cast" any void value into null | undefined:
-
Voidable<T> = T | void— a value that is either of typeTor isvoid. -
voidable<T>(value: T | void): T | null | undefined— casts a givenVoidablevalue into an optional nullable type.
Array
The @perfective/common/array package provides functions for the standard JS
Array class:
Constructors
-
array<T>(…elements: T[]): T[]— creates an array from givenelements. -
elements<T>(value: Iterable<T> | ArrayLike<T>): T[]— creates an array from elements of a givenIterableor anArrayLikevalue. -
copy<T>(array: T[]): T[]— creates a shallow copy of a givenarray. -
concatenated<T>(arrays: T[][]): T[]— concatenates givenarraysin order they are given. -
concatenated<T>(initial: T[], …arrays: T[][]): T[]— concatenates given arrays in order they are given. -
intersection<T>(array1: T[], array2: T[]): T[]— returns an array of unique elements that are included in both given arrays. -
replicated<T>(value: T, count: number): T[]— creates an array with a givenvaluereplicated a givencountof times. -
replicated<T>(count: number): Unary<T, T[]>— creates a callback that replicates the inputvaluea givencountof times. -
reversed<T>(array: T[]): T[]— creates a shallow copy of a givenarraywith elements in reversed order. -
sorted<T>(array: T[], order?: Compare<T>): T[]— returns a shallow copy of a givenarraysorted with a givenordercallback. -
sorted<T>(order?: Compare<T>): Unary<T[], T[]>— creates a callback to return a shallow copy of the input array sorted with a givenordercallback. -
unique<T>(array: T[]): T[]— returns an array with only the first occurrence of each value in a givenarray. -
wrapped<T>(value: T | T[]): T[]— returns an array that consists only of a givenvalue. If a givenvalueis an array, returns the originalvalue.
Type guards
-
isArray<T, V = unknown>(value: T[] | V): value is T[]— returnstrueif a givenvalueis an array. Otherwise, returnsfalse. -
isNotArray<T, V = unknown>(value: T[] | V): value is V— returnstrueif a givenvalueis not an array. Otherwise, returnsfalse.
Predicates
-
isEmpty<T>(value: T[]): boolean— returnstrueif a givenarrayis empty. Otherwise, returnsfalse. -
isNotEmpty<T>(value: T[]): boolean— returnstrueif a givenarrayis not empty. Otherwise, returnsfalse. -
includes<T>(search: T, from?: number): Predicate<T[]>— creates a callback that returnstrueif a givenvalueis included in the input array (optionally, startingfroma given index). -
includedIn<T>(array: T[], from?: number): Predicate<T>— creates a callback that returnstrueif the input value is present in a givenarray(optionally, checkingfroma given index). -
every<T>(condition: Predicate<T>): Predicate<T[]>— creates a callback that returnstrueif all elements of the inputarraysatisfy a givencondition. -
some<T>(condition: Predicate<T>): Predicate<T[]>— creates a callback that returnstrueif the input array contains at least one element that satisfies a givencondition.
Iterators
-
entries<T>(array: T[]): IterableIterator<[number, T]>— returns a new array iterator that contains the key/value pairs for each index of a given array. -
keys<T>(array: T[]): IterableIterator<number>— returns a new array iterator that contains the keys of each index in the array. -
values<T>(array: T[]): IterableIterator<T>— returns a new array iterator that iterates the value of each item in a givenarray.
Filter
Filter<T, S extends T> — a callback that can be passed into the Array.prototype.filter() method.
-
filter<T>(condition: Predicate<T>): Unary<T[], T[]>— creates a callback that returns an array with element the inputarraythat satisfy a givencondition. -
by<T, K extends keyof T>(property: K, condition: Predicate<T[K]>): Filter<T, T>— creates a filter callback that returnstrueif a givenpropertyof the inputvaluesatisfies a givencondition. -
isFirstOccurrence<T>(value: T, index: number, array: T[]): boolean— returnstrue, if a givenvaluehas its first occurrence at givenindexin a givenarray. Otherwise, returnsfalse. -
isLastOccurrence<T>(value: T, index: number, array: T[]): boolean— returnstrue, if a givenvaluehas its last occurrence at givenindexin a givenarray. Otherwise, returnsfalse.
Map
Map<T, U> — a callback that can be passed into the Array.prototype.map() method.
-
map<T, V>(callback: Unary<T, V>): Unary<T[], V[]>— creates a callback that returns an array of results of a givencallbackapplied to each element of the input array.
Reduce
Reduce<T, V> — a callback that can passed into
the Array.prototype.reduce() and Array.prototype.reduceRight() methods.
-
reduce<T, V>(reducer: Reducer<T, V>, initial: V): Unary<T[], V>— creates a callback that reduces the input array with a givenreducercallback using a giveninitialvalue. -
reduceTo<T>(reducer: Reducer<T, T>): Unary<T[], T>— creates a callback that reduces the input array with a givenreducercallback without an initial value. -
reduceRight<T, V>(reducer: Reducer<T, V>, initial: V): Unary<T[], V>— creates a callback that reduces the input array with a givenreducercallback using a giveninitialvalue starting from the end of the array. -
reduceRightTo<T>(reducer: Reducer<T, T>): Unary<T[], T>— creates a callback that reduces the input array with a givenreducercallback without an initial value starting from te end of the array. -
join<T>(separator: string = ','): Unary<T[], string>— creates a callback that returns a string concatenated from elements of the inputarraywith a givenseparator.
Sort
Compare<T> = (a: T, b: T) ⇒ number — a function that defines the sort order
and can be passed into the Array.prototype.sort() method.
-
sort<T>(order?: Compare<T>): Unary<T[], T[]>— creates a callback that returns the input array sorted in-place using a givenorderfunction.
Element
type Element<A> = A extends readonly (infer T)[] ? T : undefined
— infers the element type of a given array A.
-
head<T>(array: T[]): T | undefined— returns the first element of a givenarray. -
tail<T>(array: T[]): T[]— returns a sub-array of a givenarray, without the first element. -
end<T>(array: T[]): T | undefined— returns the last element of a givenarray. -
init<T>(array: T[]): T[]— returns a sub-array of a givenarray, without the last element. -
element<T>(index: number): Unary<T[], T | undefined>— creates a callback that returns an element at a givenindexin thearrayinput. -
find<T>(condition: Predicate<T>): Unary<T[], T | undefined>— creates a callback that returns the first element of the inputarraythat satisfies a givencondition; or returnsundefinedif no elements satisfy thecondition. -
pop<T>(array: T[]): T | undefined— returns the last element of a givenarrayand removes it from thearray. Returnsundefinedif the array is empty. -
push<T>(…elements: T[]): Unary<T[], number>— creates a callback that adds givenelementsto the end of the input array and returns its new length. -
pushInto<T>(array: T[]): (…elements: T[]) ⇒ number— creates a callback that adds the inputelementsto the end of a givenarrayand returns its new length. -
shift<T>(array: T[]): T | undefined— removes the first element of a givenarrayand it. -
unshift<T>(…elements: T[]): Unary<T[], number>— creates a callback that adds givenelementsto the beginning of the input array and returns its new length. -
findIndex<T>(condition: Predicate<T>): Unary<T[], number | -1>— creates a callback that returns the index of the first element of the inputarraythat satisfies a givencondition; or returns-1if no elements satisfy thecondition. -
indexOf<T>(search: T, from?: number): Unary<T[], number | -1>— creates a callback that returns the first index of a givenvaluein the input array (optionally, startingfroma given index); or returns-1if thevalueis not present. -
lastIndexOf<T>(search: T, from?: number): Unary<T[], number | -1>— creates a callback that returns the last index of a givenvaluein the input array; or returns-1if thevalueis not present. -
first<T>(count: number = 1): Unary<T[], T[]>— creates a callback that returns an array of the firstcountof the inputarrayelements. -
last<T>(count: number = 1): Unary<T[], T[]>— creates a callback that returns an array of the lastcountof the inputarrayelements. -
append<T>(element: T): Unary<T[], T[]>— creates a callback that returns a shallow copy of the inputarraywith a givenelementadded as the last element. -
prepend<T>(element: T): Unary<T[], T[]>— creates a callback that returns a shallow copy of the inputarraywith a givenelementadded as the first element. -
insert<T>(index: number, element: T): Unary<T[], T[]>— creates a callback that returns a shallow copy of the inputarraywith a givenelementinserted at a givenindex. -
insertInto<T>(array: T[], index: number): Unary<T, T[]>— creates a callback that returns a shallow copy of a givenarraywith the inputelementinserted at a givenindex. -
replace<T>(index: number, element: T): Unary<T[], T[]>— creates a callback that returns a shallow copy of the inputarraywith a givenelementreplacing the input array element at a givenindex. -
remove<T>(index: number): Unary<T[], T[]>— creates a callback that returns a shallow copy of the inputarraywithout an element at a givenindex. -
concat<T>(…items: ConcatArray<T>[]): Unary<T[], T[]>— creates a callback that merges givenitemsto the inputarray. -
slice<T>(start?: number, end?: number): Unary<T[], T[]>— creates a callback that returns an array of elements between givenstartandend(exclusive) indices of the input array. -
copyWithin<T>(target: number, start: number = 0, end?: number): Unary<T[], T[]>— creates a callback that shallow copies elements within a givenstarttoendrange starting from thetargetindex. -
fill<T>(value: T, start?: number, end?: number): Unary<T[], T[]>— creates a callback that changes all elements of the inputarraywithin a givenstarttoendrange to a givenvalue. -
reverse<T>(array: T[]): T[]— reverses a givenarray(in-place). -
splice<T>(start: number, deleteCount?: number): Unary<T[], T[]>— creates a callback that removes (in-place)countnumber of elements of the input array from a givenstartindex and returns the array. -
spliceWith<T>(start: number, deleteCount: number, …elements: T[]): Unary<T[], T[]>— creates a callback that replaces (in-place)countnumber of elements of the input array from a givenstartindex with givenelementsand returns the array. -
forEach<T>(procedure: UnaryVoid<T>): UnaryVoid<T[]>— creates a callback that executes a givenprocedureon every element of the inputarray.
Boolean
The @perfective/common/boolean package provides types and functions to work with the boolean type.
Proposition
Proposition<T> is a boolean value,
or a nullary function that returns a boolean value.
-
isTrue(proposition: Proposition): boolean— returnstrueif a givenpropositionistrueor returnstrue. Otherwise, returnsfalse. -
isFalse(proposition: Proposition): boolean— returnstrueif a givenpropositionisfalseor returnsfalse. Otherwise, returnsfalse. -
negated(proposition: Proposition): boolean— returnstrueif a givenpropositionisfalseor returnsfalse. Otherwise, returnsfalse.
Predicate
Predicate<T> is a unary function that returns a boolean value.
-
isBoolean<T>(value: T | boolean): value is boolean— returnstrueif a givenvalueisboolean. -
isNotBoolean<T>(value: T | boolean): value is T— returnstrueif a givenvalueis notboolean. -
isTruthy<T>(value: T): boolean— returnstruewhen the value is neitherundefined,null,false,NaN,0,-0,0n(aBigIntzero),""(an empty string), or thedocument.allbuiltin. -
isFalsy<T>(value: T): boolean— returnstruewhen the value isundefined,null,false,NaN,0,-0,0n(aBigIntzero),""(an empty string), or thedocument.allbuiltin. -
is<T>(input: T): Predicate<T>— creates aPredicatethat istrueif its argument strictly equals a giveninput. -
isNot<T>(input: T): Predicate<T>— creates aPredicatethat istrueif its argument does not equal a giveninput. -
not<T>(predicate: Predicate<T>): Predicate<T>— creates aPredicatethat inverses the result of a givenPredicate. -
all<T>(…predicates: Predicate<T>[]): Predicate<T>— creates aPredicatethat istruewhen all givenpredicatesaretrue(logicalAND). -
either<T>(…predicates: Predicate<T>[]): Predicate<T>— creates aPredicatethat istruewhen at least one of givenpredicatesistrue(logicalOR). -
neither<T>(…predicates: Predicate<T>[]): Predicate<T>— creates aPredicatethat istruewhen none of givenpredicatesistrue. -
atLeast<T>(minimum: number, …predicates: Predicate<T>[]): Predicate<T>— creates aPredicatethat istruewhen at least a givenminimumnumber of givenpredicatesis true. -
atMost<T>(maximum: number, …predicates: Predicate<T>[]): Predicate<T>— creates aPredicatethat istruewhen no more than a givenmaximumnumber of givenpredicatesis true. -
exactly<T>(count: number, …predicates: Predicate<T>[]): Predicate<T>— creates aPredicatethat istruewhen exactly a givencountnumber of givenpredicatesis true.
Date
The @perfective/common/date package provides functions to work with the
Date type.
There are a couple important differences in how @perfective handles the Date values.
-
@perfectivetreatsDateobjects as immutable, so setter methods are not supported. -
@perfectivefunctions returnnullinstead of theInvalid Dateinstances.Using
nullallows to combine the results of the functions usingMaybe:import { date, Timestamp, timestamp } from '@perfective/common/date`; import { Maybe, just } from '@perfective/common/maybe'; function parseTime(input: string): Maybe<number> { (1) const time = Date.parse(input); if (Number.isNaN(time)) { return nothing(); } return just(time); } function maybeTime(input: string): Maybe<Timestamp> { (2) return just(input).to(date).to(timestamp); }1 Date.parse(input)returns anumberorNaN, so additional checks are required.2 date()function returnsDate | null, so it’s easily chained intoMaybe.
Date
-
Constructors:
-
date(date: Date): Date | null;— creates a copy of a givendate. If the givendateisInvalid Date, returnsnull. -
date(timestamp: Timestamp): Date | null— creates aDatefor a givenTimestamp. If the givenTimestampis invalid, returnsnull. -
date(input: string): Date | null— parses aDatefrom a given inputstring. If the given input cannot be parsed, returnsnull. -
now(): Date— creates aDatefor the current moment. UsesDate.now(), so it can be mocked in the tests (for example, in Jest). -
epoch(): Date— creates aDatefor the Unix epoch (i.e.,January 1, 1970, 00:00:00 UTC).
-
-
Predicates:
-
isValid(date: Date): boolean— returnstrueif a givenDateis a valid (not anInvalid Date). -
isInvalid(date: Date): boolean— returnstrueif a givenDateis anInvalid Date.
-
Timestamp
-
Timestamp = number— an alias type fornumberand is specified in milliseconds since the Unix epoch. -
timestamp(date: Date): Timestamp | null— returns aTimestampfor a given date. If the givenDateisInvalid Date, returnsnull. -
timestamp(input: string): Timestamp | null— parses a given date/timeinputstring as aTimestamp. If the giveninputis invalid, returnsnull.Use instead of the
Date.parse()function, as it’s easy to compose usingmaybe().
Error
The @perfective/common/error package helps organize exceptions and error handling.
It defines an Exception, based on the JS
Error class,
that supports localizable error messages and error chaining;
and provides functions to handle error stack output.
Exception
Exception type definition.class Exception extends Error {
public readonly name: string = 'Exception';
public message: string; (1)
public readonly template: string; (2)
public readonly tokens: ExceptionTokens; (3)
public readonly context: ExceptionContext; (4)
public readonly previous: Error | null; (5)
public toString(): string; (6)
}
| 1 | Inherited from Error, message is generated from the template and tokens. |
| 2 | Message template with tokens wrapped in {{ }},
e.g. User ID {{id}} or User {{user_id}}. |
| 3 | Token/Value map to be used in the error message. |
| 4 | Additional context information. Context is not rendered by default, but may be useful in applications. |
| 5 | Provide a previous error, if exists. |
| 6 | Human-readable output of all the errors. |
Constructors
-
exception(message: string, tokens: ExceptionTokens = {}, context: ExceptionContext = {}): Exception— creates anException. -
chained(message: string, tokens: ExceptionTokens = {}, context: ExceptionContext = {}): (previous: Error) ⇒ Exception— creates a function to wrap a previousErrorinto anException. -
causedBy(previous: Error, message: string, tokens: ExceptionTokens = {}, context: ExceptionContext = {}): Exception— creates anExceptionwith a previousError. -
caughtError(value: unknown): Error | Exception— wraps a non-Errorvalueinto anException. TheException.messagestarts withCaughtand contains the caughtvaluecoerced to a string.If given an
Error, returns it as is.
Custom exceptions
-
invalidArgumentException(argument: string, expected: string, actual: string): Exception— creates an exception with the message about an invalid argument.
Usage
import { causedBy, chainStack, exception, rethrows, throws } from '@perfective/common/error';
import { decimal } from '@perfective/common/number';
function positive(input: number): string {
if (input <= 0) {
return throws(exception('Invalid input {{input}}', { (1)
input: decimal(input);
}));
}
return decimal(10);
}
function field(name: string, value: number): string {
try {
return `${name} = ${positive(value)}`;
}
catch (error) {
return throws(causedBy(error, 'Failed to output field {{field}}', { (2)
field: name,
}));
}
}
function record(): Record<string, string> {
try {
return {
username: field('username', -42),
}
}
catch (error) {
return rethrows(error, 'Failed to build a record'); (3)
}
}
try {
record();
}
catch (error) {
console.error(error.toString()); (4)
// Outputs:
// Exception: Failed to build a record
// - Exception: Failed to output field `username`
// - Exception: Invalid input `-42`
console.error(chainStack(error)); (5)
// Outputs:
// Exception: Failed to build a record
// at Object.rethrows (...)
// at Object.record (...)
// ...
// Caused by: Exception: Failed to output field `username`
// at Object.causedBy (...)
// at Object.throws (...)
// at Object.field (...)
// ...
// Caused by: Exception: Invalid input `-42`
// at Object.exception (...)
// at Object.throws (...)
// at Object.positive (...)
// ...
}
| 1 | throws can be used with any Error type,
but Exception supports passing context information separately,
which can be convenient for localization or logging purposes. |
| 2 | causedBy is a function to create an Exception with a present Exception.previous property. |
| 3 | rethrows is a shortcut for the throws(causedBy()) call.
It supports message context as well. |
| 4 | error.toString() is explicitly re-defined on Exception to output all error messages. |
| 5 | chainStack() allows to output all errors with their stack information
(platform dependent). |
Panic
type Panic = (cause?: unknown) ⇒ never
— a function that throws an Error.
-
panic(message: string, tokens?: ExceptionTokens, context?: ExceptionContext): Panic— creates a function that throws anExceptionwith a givenmessagetemplate withtokensand additionalcontextdata. If thecauseis defined, sets thecauseas apreviouserror.Useful working with
Promise.catch()and RxJScatchError(). -
panic<E extends Error>(error: Value<E>): Panic— creates a function that throws a givenError. Ignores thecause, even when if is defined.Usepanicto create a callback for lazy evaluation.import { panic } from '@perfective/common/error'; import { maybe } from '@perfective/common/maybe'; export function example(input: string | null | undefined): string { return maybe(input) .or(panic('Input is not present')); (1) }1 Must use panic(), as the fallback inMaybe.or()is called only when theinputis not present. Usingthrows()will result in throwing an exception every time a function is called. -
throws(message: string, tokens?: ExceptionTokens, context?: ExceptionContext): never— throws anExceptionwith a givenmessagetemplate withtokensand additionalcontextdata. -
throws<E extends Error>(error: Value<E>): never— throws a givenError. If given a callback, throws anErrorreturned by the callback.Can be used to throw an exception from a one-line arrow function.
-
rethrows(previous: Error, message: string, tokens: ExceptionTokens = {}, context: ExceptionContext = {}): never— throws anExceptionwith a givenmessagecaused by apreviousError. Exception message may contain giventokensand additionalcontextdata.Similar to
throws, but requires to provide a previous error.
Recovery
type Recovery<T> = (error: Error) ⇒ T
— a function that handles a given Error and returns a recovery value.
Failure
The default JS Error class does not have toJSON method
and is serialized as an empty object by JSON.stringify.
This creates a problem for any attempt to transfer error information.
Type Failure solved this problem by providing a record type to "serialize" Error and Exception.
It omits stack information, but keeps the list of previous errors.
-
Failure-
failure<E extends Error>(error: E): Failure— convert andErroror anExceptioninto aFailurerecord.
-
Standard built-in JS Error types
-
-
error(message: string): Error— instantiates a newError. -
isError<T>(value: Error | T): value is Error— returnstruewhen the value is an instance ofError. -
isNotError<T>(value: Error | T): value is T— returnstruewhen the value is not an instance ofError.
-
-
-
evalError(message: string): EvalError— instantiates a newEvalError. -
isEvalError<T>(value: EvalError | T): value is EvalError— returnstruewhen the value is an instance ofEvalError. -
isNotEvalError<T>(value: EvalError | T): value is T— returnstruewhen the value is not an instance ofEvalError.
-
-
-
rangeError(message: string): RangeError— instantiates a newRangeError. -
isRangeError<T>(value: RangeError | T): value is RangeError— returnstruewhen the value is an instance ofRangeError. -
isNotRangeError<T>(value: RangeError | T): value is T— returnstruewhen the value is not an instance ofRangeError.
-
-
-
referenceError(message: string): ReferenceError— instantiates a newReferenceError. -
isReferenceError<T>(value: ReferenceError | T): value is ReferenceError— returnstruewhen the value is an instance ofReferenceError. -
isNotReferenceError<T>(value: ReferenceError | T): value is T— returnstruewhen the value is not an instance ofReferenceError.
-
-
-
syntaxError(message: string): SyntaxError— instantiates a newSyntaxError. -
isSyntaxError<T>(value: SyntaxError | T): value is SyntaxError— returnstruewhen the value is an instance ofSyntaxError. -
isNotSyntaxError<T>(value: SyntaxError | T): value is T— returnstruewhen the value is not an instance ofSyntaxError.
-
-
-
typeError(message: string): TypeError— instantiates a newTypeError. -
isTypeError<T>(value: TypeError | T): value is TypeError— returnstruewhen the value is an instance ofTypeError. -
isNotTypeError<T>(value: TypeError | T): value is T— returnstruewhen the value is not an instance ofTypeError.
-
|
Roadmap
-
Provide functions to parse standard error messages and predicates to check for them.
Function
The @perfective/common/function package provides types and functions for the functional programming style.
As functions with more than three arguments are considered a code smell,
this package only declares the Nullary, Unary, Binary, and Ternary types.
The functions of higher arity is unlikely to be added to the package.
Type Guards
-
isFunction<T>(value: Function | T): value is Function— returnstrueif a givenvalueis aFunction. Otherwise, returnsfalse. -
isNotFunction<T>(value: Function | T): value is T— returnstrueif a givenvalueis not aFunction. Otherwise, returnsfalse.
Nullary functions
-
Nullary<T>— a function without arguments:-
isNullary<F extends Function>(f: F): boolean— returnstrueif a given functionfhas length0(excluding a variadic argument). Otherwise, returnsfalse. -
constant<T>(value: T): Nullary<T>— creates a nullary function that returns a givenvalue.
-
-
Void— a procedure without arguments:-
naught(): void— an empty function to be passed as a callback when a no-op behavior is required.
-
-
Value<T> = T | Nullary<T>— a value itself or a nullary function that returns a value. (e.g. for lazy evaluation).-
valueOf<T>(value: Value<T>): T— When given a nullary function, evaluates the function and returns the result. Otherwise, returns the givenvalue.
-
Unary functions
-
Unary<X, V>— a function with one argument:-
isUnary<F extends Function>(f: F): boolean— returnstrueif a given functionfhas length1(excluding a variadic argument). -
same<T>(value: T): T— theidentityfunction. Returns a given value.
-
-
UnaryVoid<T> = (value: T) ⇒ void— a procedure with one argument.
Binary functions
-
Binary<X, Y, V>— a function with two arguments.-
isBinary<F extends Function>(f: F): boolean— Returnstrueif a given functionfhas length 2 (excluding a variadic argument). Otherwise, returnsfalse.
-
Ternary functions
-
Ternary<X, Y, Z, V>— a function with three arguments.-
isTernary<F extends Function>(f: F): boolean— returnstrueif a given functionfhas length3(excluding a variadic argument). Otherwise, returnsfalse.
-
Length
Length type defines a kind of objects that have "length" (arrays, strings, etc).
interface Length {
length: number;
}
-
Functions
-
length<L extends Length>(value: L): number— returns thelengthproperty of a givenvalue.
-
-
Predicates
-
hasLength<L extends Length>(length: number): Predicate<L>— returns a predicate that checks if the input value has a givenlength. -
isNotEmpty<L extends Length>(value: L): boolean— returnstrueif a givenvaluehas non-positive length. -
isEmpty<L extends Length>(value: L): boolean— returnstrueif a givenvaluehaslengthgreater than 0.
-
-
Reducers
These functions can be used as a callback for
Array.prototype.reduce.-
toShortest<T extends Length>(shortest: T, value: T): T— returns the shortest of two given values. -
toLongest<T extends Length>(longest: T, array: T): T— returns the longest of two given values.
-
Arguments
-
BiMap<T1, U1, T2, U2> = [Unary<T1, U1>, Unary<T2, U2>]— a pair of callbacks to map both arguments of a given function at the same time. -
BiFold<T1, T2, U> = [Unary<T1, U>, Unary<T2, U>]— a pair of unary callbacks to map both argumentsT1andT2of a function into the same typeU.
Match
The @perfective/common/match package provides functions and types for pattern matching.
-
match<T>(value: T | Nullary<T>): Match<T>-
Match.cases<U>(…cases: Case<T, U>[]): Maybe<U> -
Match.cases<U>(cases: Case<T, U>[]): Maybe<U>— applies given value to theCase.conditionpredicate until the first match, returns the result of applying the value to theCase.statementfunction; when a match is not found, returnsNothing.
-
-
when<T>(condition: T | Predicate<T>): When<T>—Case<T, U>builder;-
to<U>(value: U | Unary<T, U>): Case<T, U>
-
-
fromEntries<T, U>(entries: CaseEntry<T, U>[]): Case<T, U>[]— creates cases from an array of tuples[Predicate<T>, Unary<T, U>].
import { match, when, fromEntries } from '@perfective/common/match';
import { Maybe } from '@perfective/common/maybe';
import { isGreaterThan } from '@perfective/common/number';
function square(x: number): number {
return x * x;
}
function squares(root: number): Maybe<number> {
return match(root).cases(
when(1.41).to(2),
when(1.73).to(3),
when(2.23).to(5),
when(isGreaterThan(3)).to(square), (1)
);
}
function squares(root: number): Maybe<string> {
return match(root).cases(fromEntries([
[1.41, '2']
[1.73, '3'],
[2.23, '5'],
[true, 'unknown'], (2)
]));
}
| 1 | when.to() passes the original match value to a callback. |
| 2 | fromEntries function allows passing constant values as conditions and as result statements. |
Maybe monad
@perfective/common/maybe package provides an Option type implementation.
It is inspired by the Haskell
Maybe monad
and satisfies the monad laws.
Maybe type simplifies handling of the absent (null/undefined) values
and provides methods that are called only when the value is present.
It allows the creation of chained calls similar to Promise.then() and RxJS pipe().
Handling null and undefined values
In JavaScript, two types represent an "absence" of value: undefined and null.
So the dichotomies like Just | Nothing or Some | None do not fit precisely
and require additional logic to cover all the cases: T | null | undefined.
For example, when you create a new Maybe<T> with a null value,
it has to maintain the value as null and should not change it to undefined.
Maybe<T> maintains the original value.import { maybe, nil, nothing } from '@perfective/common/maybe';
import { isGreaterThan } from '@perfective/common/number';
maybe(3.14).value === 3.14;
maybe(undefined).value === undefined;
nothing().value === undefined; (1)
maybe(3.14)
.that(isGreaterThan(4))
.value === undefined; (2)
maybe(null).value === null;
nil().value === null; (3)
maybe(null)
.that(isGreaterThan(4))
.value === null; (4)
| 1 | The nothing() function is the default unit function for Nothing<T>
and returns a memoized new Nothing(undefined). |
| 2 | By default, Maybe uses undefined for the absent value. |
| 3 | The nil() function is the secondary unit function for Nothing<T>
and returns a memoized new Nothing(null). |
| 4 | If the original value is null,
null will be returned as an absent value after all transformations. |
It is not always desired to have both undefined and null excluded.
For example, consider you have a field in an API that is null when there is no data,
but the field is not returned if a user does not have access to it.
In this case, you may prefer to fall back from null to a default value
but to throw an error for undefined.
To cover these cases, @perfective/common/maybe provides the Maybe.into() method:
it allows to apply a function that handles either T, null, or undefined value.
null or undefined) is maintained through transformations.import { isNull, isUndefined } from '@perfective/common';
import { panic } from '@perfective/common/error';
import { just } from '@perfective/common/maybe';
interface ApiResponse {
username?: string | null;
}
function username(response: ApiResponse) {
return just(response)
.pick('username')
.into((username: string | null | undefined) => { (1)
if (isUndefined(username)) {
return panic('Unknown username');
}
if (isNull(username)) {
return 'anonymous';
}
return username;
});
}
| 1 | As Maybe preserves the original type,
the result of the pick() can be either nothing() or nil().
So Maybe.into() will have a correct value on input. |
Preserving the monad type
This package strictly preserves the type of the monad.
For example,
if you have a Just type and apply a function that returns a present value,
then the result will also be of type Just.
Maybe, Just and Nothing contexts.function maybeDecimal(value: number | null | undefined): Maybe<string> {
return maybe(value).to(v => v.toString(10)); (1)
}
function justDecimal(value: number): Just<string> {
return maybe(value).to(v => v.toString(10)); (2)
}
function nothingDecimal(value: null | undefined): Nothing<string> {
return maybe<number>(value).to(a => a.toString(10)); (3)
}
| 1 | The argument of the maybeDecimal function is number | null | undefined.
So the maybe() function returns Maybe<number>
(which is either Just<number> or Nothing<number>).
The result of the function may also be Just or Nothing,
because we can not be sure that the to() method will be called,
even the v ⇒ v.toString(10) returns a string for any number input. |
| 2 | The argument of the justDecimal is always a number.
The maybe() function returns Just<number>,
because the value is always present.
maybe has a custom overload signature,
and compiler also knows,
that maybe returns Just<number>.
As the |
| 3 | Similarly, when the value can only be null or undefined,
the maybe() function returns Nothing<number> in compile time and in runtime.
And the return type of the whole chain can be set to Nothing<string>. |
Using onto() and to() methods
Both Maybe.onto() and Maybe.to() methods apply a given function
only when the value is present.
But onto requires the function to return the next Maybe instance,
while to will wrap the returned value into Maybe.
When writing functions that use Maybe chaining,
the best practice is to return the packed value (as Maybe, Just, or Nothing).
This allows a consumer of the function to decide how they want to unpack it
or to keep it as Maybe for the next chain.
When you have a function of non-Maybe types,
then you have to use Maybe.to.
function isValidDate(date: Date): boolean {
return date.toString() !== 'Invalid Date'; (1)
}
function parsedDate(input: string): Maybe<Date> { (2)
const date = new Date(input);
if (isValidDate(date)) {
return just(date);
}
return nothing();
}
interface BlogPost {
createdAt: string;
}
function dbDate(input: BlogPost): Date { (3)
return just(input)
.pick('createdAt')
.onto(parsedDate)
.or(panic('Invalid "Created At" Date'));
}
function jsonDate(input: BlogPost): string|null { (4)
return just(input)
.pick('createAt')
.onto(parsedDate)
.or(null);
}
function formattedCreatedAt(input: BlogPost): string { (5)
return just(input)
.pick('createdAt')
.onto(parsedDate)
.or('Unknown date');
}
| 1 | The new Date() constructor creates a Date object even for
invalid inputs. |
| 2 | We postpone the decision of how to handle an invalid value.
By returning Maybe<Date> (instead of Date|null or throwing an error)
we allow consumers of the function to make a decision that is most appropriate to their situation. |
| 3 | When we record value to the database, it has to be valid. So we must throw an error when the date is invalid. |
| 4 | When we return an API response,
a null for invalid dates is ok. |
| 5 | When we try to format a date in the UI, we may prefer a readable fallback. |
Using the into() method with the maybeFrom() function
The Maybe.into() method allows reducing a Maybe instance into a different type.
It applies the argument function for present and absent values.
In combination with the maybeFrom() function,
it allows to apply functions with custom handling of absent values
and return a new Maybe instance.
import { isAbsent } from '@perfective/common';
import { just, maybe, maybeFrom } from '@perfective/common/maybe';
function decimal(value: number | null | undefined): string {
if (isAbsent(value)) {
return '0'; (1)
}
return value.toString(10);
}
maybe(null).onto(x => maybe(decimal(x))) != just(decimal(null)); (2)
maybe(null).to(decimal) != just(decimal(null)); (3)
maybe(null).into(x => maybe(decimal(x)) == just(decimal(null)) (4)
maybe(null).into(maybeFrom(decimal)) == just(decimal(null)) (5)
| 1 | The decimal() function returns a default value for the absent values
instead of returning another absent value
(or throwing an error). |
| 2 | As a result, when decimal() is applied through the Maybe.onto() method,
it breaks the left-identity monad law. |
| 3 | Applying decimal() through Maybe.to() gives the same incorrect result. |
| 4 | Using the Maybe.into() method allows working around this issue
because Maybe.into() is called for all Maybe values
(not only present values). |
| 5 | Use the maybeFrom() function as a shortcut. |
|
Since v0.9.0,
|
Reference
Types
-
Maybe<T>— an abstract class, represents eitherJust<T>orNothing<T>. -
Just<T>— represents a defined non-null value of typeT. -
Nothing<T>— represents anundefinedornullvalue.
Functions
-
maybe<T>(value: T | null | undefined): Maybe<T>— creates an instance ofJustwhen thevalueis present, or returns a memoized instance ofNothingwith eithernullorundefinedvalue. -
maybeFrom<T, U>(map: Unary<T | null | undefined, U | null | undefined>): Unary<T | null | undefined, Maybe<U>> -
just<T>(value: Present<T>): Just<T>— creates an instance ofJustwith a given defined non-nullvalue. A unit (return) function for theMaybemonad. -
justFrom<T, U>(map: Unary<T | null | undefined, Present<U>>): Unary<T | null | undefined, Just<U>>— creates a function that applies a givenmapto a value and returns the result wrapped into aJust. -
nothing<T>(): Nothing<Present<T>>— returns a memoized instance ofNothingwith anundefinedvalue. -
nil<T>(): Nothing<Present<T>>— returns a memoized instance ofNothingwith anullvalue.
Type Guards
-
isMaybe<T, U>(value: Maybe<T> | U): value is Maybe<T>— returnstrueif a given value is aMaybe.-
isNotMaybe<T, U>(value: Maybe<T> | U): value is U— returnstrueif a given value is not aMaybe.
-
-
isJust<T, U>(value: Just<T> | U): value is Just<T>— returnstrueif a given value is aJust.-
isNotJust<T, U>(value: Just<T> | U): value is U— returnstrueif a given value is not aJust.
-
-
isNothing<T, U>(value: Nothing<T> | U): value is Nothing<T>— returnstrueif a given value is aNothing+isNotNothing<T, U>(value: Nothing<T> | U): value is U— returnstrueif a given value is not aNothing.
Maybe.onto()
-
Maybe.onto<U>(flatMap: (value: T) ⇒ Maybe<Present<U>>): Maybe<Present<U>>-
for a
Just, applies a givenflatMapcallback to theJust.valueand returns the result; -
for a
Nothing, ignore theflatMapcallback and returns the sameNothing.
-
This method is similar to the mergeMap/switchMap operator in rxjs
and the flatMap method in java.util.Optional.
Maybe.to()
-
Maybe.to<U>(map: (value: T) ⇒ U | null | undefined): Maybe<U>-
for a
Just, applies a givenmapcallback to theJust.valueand returns the result wrapped into aMaybe. -
for a
Nothing, ignores themapcallback and returns the sameNothing.
-
Maybe.to() chainingimport { Maybe, maybe } from '@perfective/common/maybe';
import { lowercase } from '@perfective/common/string';
interface Name {
first: string;
last: string;
}
interface User {
name?: Name;
}
function nameOutput(name: Name): string { (1)
return `${name.first} ${name.last}`;
}
function usernameOutput(user?: User): Maybe<string> {
return maybe(user)
.pick('name')
.to(nameOutput)
.to(lowercase);
}
| 1 | The to method wraps the result into maybe. |
This method is similar to the map operator in rxjs
and the map method in java.util.Optional.
Maybe.into()
-
Maybe.into<U>(reduce: (value: T | null | undefined) ⇒ U): U— applies a givenreducecallback to theMaybe.valueand returns the result. The purpose ofMaybe.into()is to terminate theMaybeand switch to a different type.
|
Unlike Unlike |
Maybe.into()import { Maybe, maybe } from '@perfective/common/maybe';
import { isPresent } from '@perfective/common';
function usernameRequest(userId: number | null | undefined): Promise<string> {
if (isPresent(userId)) {
return Promise.resolve({ userId });
}
return Promise.reject("UserId is missing");
}
function username(userId: Maybe<number>): Promise<string> {
return userId.into(usernameRequest) // === usernameRequest(userId.value)
.then(response => response.username) (1)
.catch(() => "Unknown");
}
| 1 | While passing the Maybe.value directly into the function is possible,
the Maybe.into() method allows to switch the chain to a different monadic type
and continue the chain with that new type. |
Maybe.pick()
-
Maybe.pick<K extends keyof T>(property: Value<K>): Maybe<Present<T[K]>>-
for a
Just, returns the value of a givenpropertyofJust.valuewrapped into aMaybe; -
for a
Nothing, ignores thepropertyand returns the sameNothing.
-
|
Only properties that are defined on the value type are allowed. |
It is similar to the
optional chaining
introduced in TypeScript 3.7
but does not generate excessive JS code for each null and undefined check in the chain.
Maybe.pick() for optional chainingimport { panic } from '@perfective/common/error';
import { maybe } from '@perfective/common/maybe';
interface Name {
first?: string;
last?: string;
}
interface User {
id: number;
name?: Name;
}
function firstName(user?: User): string {
return maybe(user).pick('name').pick('first').or(panic('Unknown first name')); (1)
}
function userId(user: User): number {
return just(user).pick('id').value; (2)
}
| 1 | maybe(user).pick('email') will not compile,
as, in this example, the User type does not have an email property. |
| 2 | When the value is Just<T>, and you pick a required property,
the result is Just<U> (where U is the type of that property).
Hence, starting a maybe-chain with Just is strongly recommended
if the value is already present. |
This method is similar to the pluck operator in rxjs.
Maybe.that()
-
Maybe.that(filter: Predicate<T>): Maybe<T>-
for a
Just, if the value matches a givenfilterpredicate, returns the sameJust, otherwise returnsNothing. -
for a
Nothing, ignores thefilterand returns itself.
-
Maybe.that() to filter out a valueimport { isNot } from '@perfective/common/function';
import { Maybe, just } from '@perfective/common/maybe';
function quotient(dividend: number, divisor: number): Maybe<number> {
return just(divisor)
.that(isNot(0)) (1)
.to(divisor => dividend / divisor);
}
| 1 | Returns Nothing, so to() will not be running its function. |
This method is similar to the filter operator in rxjs
and the filter method in java.util.Optional.
Maybe.which()
-
Maybe.which<U extends T>(filter: TypeGuard<T, U>): Maybe<U>-
for a
Just, if the value matches a givenfiltertype guard, returns the sameJustwith a narrowed-down (differentiated) type. -
for a
Nothing, ignores thefilterand returns itself.
-
Maybe.which() is a filter method that requires passing a
It narrows down the result type based on the type guard.
Maybe.which() to filter out values with absent properties.import { Maybe, just } from '@perfective/common/maybe';
import { hasDefinedProperty } from '@perfective/common/object';
interface Name {
first: string;
last: string;
}
interface Username {
first?: string;
middle?: string;
last?: string;
}
function nameOutput(name: Name): string {
return `${name.first} ${name.last}`;
}
function usernameOutput(user: User): Maybe<string> {
return just(user)
.which(hasDefinedProperty('first', 'last')) (1)
.to(nameOutput); (2)
}
| 1 | A broader hasPresentProperty('first', 'last') can also be used.
to guarantee that these properties' values are not null too.
But it is not required by the TS compiler strictNullCheck,
as these properties are optional, not nullable. |
| 2 | Name type requires both first and last properties to be defined and not null,
so without the which filter (with TS strictNullChecks enabled),
this code will not compile. |
Maybe.when()
-
Maybe.when(condition: Proposition): Maybe<T>-
for a
Just, if a givenconditionistrue, returns the sameJust, otherwise returnsNothing. -
for a
Nothing, ignores theconditionand returns itself.
-
|
|
Maybe.when() to filter out values based on a global condition.import { just } from '@perfective/common/maybe';
function tokenLogOutput(token: string, isLog: boolean): Maybe<string> {
return just(token)
.when(isLog) (1)
.to(token => '***');
}
| 1 | You can use when(() ⇒ isLog)
if you only want to run the computation when the value is present. |
Maybe.otherwise()
-
Maybe.otherwise(fallback: Value<T | null | undefined>): Maybe<T>-
for a
Just, ignores a givenfallbackvalue and returns itself. -
for a
Nothing, returns a givenfallbackwrapped into aMaybe.
-
Maybe.otherwise(fallback) method allows passing a fallback value or throwing an error
if the value is absent.
Maybe.otherwise() to continue the chain after the fallback.import { panic } from '@perfective/common/error';
import { isNot } from '@perfective/common/function';
import { maybe } from '@perfective/common/maybe';
function range(min?: number, max?: number): number {
return maybe(min)
.otherwise(max) (1)
.that(isNot(0))
.otherwise(panic('Invalid range'));
}
| 1 | otherwise wraps the fallback value into the next Maybe. |
Maybe.or()
-
Maybe.or(fallback: Value<T | null | undefined>): T | null | undefined-
for a
Just, ignores a givenfallbackvalue and returns theJust.value. -
for a
Nothing, returns the givenfallbackvalue.
-
The Maybe.or(fallback) method allows getting the present monad value
and providing a fallback value or throwing an error
when the value is missing.
Maybe.or()import { panic } from '@perfective/common/error';
import { maybe } from '@perfective/common/maybe';
interface Name {
first: string;
last: string;
}
interface User {
name?: Name;
}
function nameOutput(name?: Name): string {
return maybe(name)
.to(name => `${name.first} ${name.last}`)
.or('Unknown name'); (1)
}
function userOutput(user?: User): string {
return maybe(user)
.pick('name')
.to(nameOutput)
.or(panic('Undefined user')); (2)
}
| 1 | The fallback value type can be present or absent.
It allows returning only undefined or null if the value is absent. |
| 2 | Using panic or any other function that throws an error when called
allows guaranteeing a present value is returned. |
This method is similar to the orElse, orElseGet, and orElseThrow methods in java.util.Optional.
Maybe.through()
-
Maybe.through(procedure: (value: T) ⇒ void): Maybe<T>-
for a
Just, runs a givenprocedurewith theJust.valueas an argument, the returns the originalJust. -
for a
Nothing, ignores theprocedureand returns itself.
-
|
The |
import { maybe } from '@perfective/common/maybe';
function logError(error?: Error): Error|undefined {
return maybe(error)
.through(console.error);
}
|
This method is similar to the |
Lifting functions
Each method has a corresponding lifting function to be used in the Array.prototype.map
(or any other mapping method or operator).
import { Maybe, just, nil, nothing, or } from '@perfective/common/maybe';
const numbers: Maybe<number>[] = [
just(2.71),
just(3.14),
nothing<number>(),
nil<number>(),
];
numbers.map(or(0)) === [2.71, 3.14, 0, 0];
Type classes
Monad
The Maybe<T> type is a monad that provides:
-
the
Maybe.onto()method as a bind operator (>>=); -
the
just()constructor as a unit (return) function.
It satisfies the three monad laws for defined non-null T:
-
unit is a left identity for bind:
let x: T; let f: (value: T) => Maybe<U>; just(x).onto(f) === f(x); -
unit is a right identity for bind:
let ma: Maybe<T>; ma.onto(just) === ma; -
bind is associative:
let ma: Maybe<T>; let f: (value: T) => Maybe<U>; let g: (value: U) => Maybe<V>; ma.onto(a => f(a).onto(g)) === ma.onto(f).onto(g)
|
If you have a |
null with Maybe<T>.onto() breaking the left-identity law.import { isNull } from '@perfective/common';
import { Just, just, nil } from '@perfective/common/maybe';
function decimal(value: number | null): Just<string> { (1)
if (isNull(value)) {
return just('0');
}
return just(value.toString(10));
}
just(3.14).onto(decimal) == decimal(3.14); (2)
nil().onto(decimal) != decimal(null); (3)
| 1 | Maybe<T>.onto() expects the function of type Unary<number, Maybe<string>>,
but the decimal function is of type Unary<number | null, Maybe<string>>,
so the argument type does not match. |
| 2 | Applying decimal to a present number behaves as expected. |
| 3 | When the value is absent, onto does not execute decimal at all,
so the result is not the same as applying decimal directly. |
If you have to use custom handling of null/undefined,
you should use the Maybe.into() method
that passed null and undefined as into the callback.
null and undefinedimport { isAbsent } from '@perfective/common';
import { Just, just, nothing, nil } from '@perfective/common/maybe';
function decimal(value: number | null | undefined): Just<string> {
if (isAbsent(value)) {
return just('0');
}
return just(value.toString(10));
}
just(3.14).onto(decimal) == decimal(3.14); // === just('3.14')
just(3.14).into(decimal) == decimal(3.14); // === just('3.14')
nothing().onto(decimal) == nothing(); // != decimal(undefined);
nothing().into(decimal) == decimal(undefined); // === just('0')
nil().onto(decimal) == nil(); // != decimal(null);
nil().into(decimal) == decimal(null); // === just('0')
For the (legacy) functions
(written prior to using Maybe)
that handle/return null/undefined,
you should use Maybe.map() or Maybe.lift() methods.
Functor
The Maybe<T> type is a functor that provides:
-
the
Maybe.to()method as afmapoperator.
It satisfies functor laws for defined non-null T:
-
Maybe.to()preserves identity morphisms:let id = (value: T) => value; let value: T; maybe(value).to(id) === maybe(id(value)); -
Maybe.to()preserves composition of morphisms:let f: (value: U) => V; let g: (value: T) => U; let value: T; maybe(value).to(v => f(g(v))) === maybe(value).to(g).to(f);
Number
The @perfective/common/number package declares types and functions to work with real numbers,
including the JavaScript
Number class.
Number
-
Nominal types:
-
PositiveNumber— a number that is greater than 0. -
NonNegativeNumber– a number that is greater than or equal to 0. -
NegativeNumber— a number that is less than 0. -
NonPositiveNumber— a number that is less than or equal to 0. -
Sign— indicator of the sign of the number. -1 for negative numbers. 1 for positive numbers.
-
-
Type guards:
-
isNumber<T>(value: number | T): value is number— returnstrueif a givenvalueis a number and not aNaN. -
isNotNumber<T>(value: number | T): value is T— returnstrueif a givenvalueis not a number or is aNaN. -
isNonNegativeNumber(value: number): value is NonNegativeNumber— returnstrueif a givenvalueis greater than or equal to 0. Returnsfalseif the value is less than 0 or isNaN.
-
-
Assertions:
-
assertIsNotNaN(value: number): asserts value is numberassertIsNotNaN(value: number, expected: string): asserts value is numberassertIsNotNaN(argument: string, value: number): asserts value is numberassertIsNotNaN(argument: string, value: number, expected: string): asserts value is number— asserts that the given number value is notNaN. ThrowsExceptionif the given number value isNaN. -
assertIsNonNegativeNumber(value: number): asserts value is NonNegativeNumberassertIsNonNegativeNumber(argument: string, value: number): asserts value is NonNegativeNumber— asserts that a givenvalueis a number and is greater than or equal to 0.Throws an
Exceptionif the value is less than 0 orNaN.
-
-
Functions:
-
negative(value: number): number— returns the negated value of a given number. If the given number is 0, returns 0. -
sign(value: number): Sign | null— returns 1 if given a positive number, -1 if given a negative number. Returnsnullif given 0 or -0. ThrowsExceptionif the given value isNaN.
-
-
Numbermethods:-
exponential(fraction: Digits): Unary<number, string>— creates a function that returns the input number in exponential notation rounded to a given number offractiondigits. -
fixed(fraction: Digits): Unary<number, string>— creates a function that returns the input number in fixed-point notation with a given number offractiondigits. -
precision(precision: Precision): Unary<number, string>— creates a function that returns the input number in fixed-point or exponential notation rounded to a givenprecision.In JavaScript, a
Numberonly keeps 17 decimal places of precision, whileDigitis an integer from0to20, andPrecisionis an integer from1to21. So passing digits or precision over 15 requires careful consideration and thorough testing.
-
-
Algebraic functions:
-
cubeRoot(value: number): number— returns the cube root of a given number. ThrowsExceptionif the given value isNaN. -
l2norm(…values: number[]): numberl2norm(values: number[]): number— returns the L2 norm (Euclidean norm) of a list of numbers. Throws
Exceptionif any of the given values isNaN. -
power(base: number, exponent: number): numberpower([base, exponent]: [number, number]): numberpower(base: number): (exponent: number) ⇒ number— returns the result of raising a base to a given exponent. Returns -1 or 1 if the base is -1 or 1 and exponent is Infinity (overrides the default
Math.pow()behavior to match IEEE 754). ThrowsExceptionif the base or exponent isNaN. -
powerOf(exponent: number): (base: number) ⇒ number— returns a function that raises a given base to the specified exponent. ThrowsExceptionif the exponent isNaN. -
squareRoot(value: PositiveNumber): number— returns the square root of a given non-negative number. ThrowsExceptionif the given value isNaNor a negative number.
-
-
Arithmetic functions:
-
sum(augend: number, addend: number): number— returns the result of addition of the given numbers. -
difference(minuend: number, subtrahend: number): number— returns the result of subtraction of a givensubtrahendfrom a givenminuend. -
product(multiplier: number, multiplicand: number): number— returns the result of multiplication of given numbers. -
quotient(dividend: number, divisor: number): number— returns the result of division of a givendividendby a givendivisor. -
remainder(dividend: number, divisor: number): number. — returns the remainder of division of a givendividendby a givendivisor. -
absolute(value: number): number— returns the absolute value of a given number.Throws
Exceptionif the given value isNaN.
-
-
Exponential functions:
-
exp(value: number): NonNegativeNumber— returns Euler’s numbereraised to the power of the given number. ThrowsExceptionif the given value isNaN. -
expm1(value: number): number— returns Euler’s numbereraised to the power of the given number minus 1. ThrowsExceptionif the given value isNaN.
-
-
Logarithmic functions:
-
log(value: number): number— returns the natural logarithm (base e) of a given non-negative number. ThrowsExceptionif the given value isNaNor less than zero. -
log10(value: number): number— returns the common (base 10) logarithm of a given non-negative number. ThrowsExceptionif the given value isNaNor less than zero. -
log1p(value: number): number— returns the natural logarithm (base e) of 1 plus a given number. ThrowsExceptionif the given value isNaNor less than-1. -
log2(value: number): number— returns the binary (base 2) logarithm of a given non-negative number. ThrowsExceptionif the given value isNaNor less than zero.
-
-
Rounding functions:
-
round(value: number): number— rounds a floating-point number to the nearest integer. ReturnsInfinityif the given value isInfinity. Returns-Infinityif the given value is-Infinity. ThrowsExceptionif the given value isNaN. -
roundedUp(value: number): number— returns the smallest integer greater than or equal to a given number. ReturnsInfinityif the given value isInfinity. Returns-Infinityif the given value is-Infinity. ThrowsExceptionif the given value isNaN. -
roundedDown(value: number): number— returns the largest integer less than or equal to a given number. ReturnsInfinityif the given value isInfinity. Returns-Infinityif the given value is-Infinity. ThrowsExceptionif the given value isNaN. -
roundedToFloat32(value: number): number— returns the nearest 32-bit single precision float representation of a given number. ThrowsExceptionif the given value isNaN. -
truncated(value: number): number— returns the integer part of a floating-point number. ThrowsExceptionif the given value isNaN.
-
-
Trigonometry types and functions:
-
Radiansis a nominal type for a radians value (number). -
arccos(cosine: number): Radians | null— returns the inverse cosine [0, π] of the given cosine value [-1, 1].Throws
Exceptionif the given cosine is less than-1, greater than1, or isNaN. -
arccosh(value: number): NonNegativeNumber | null— returns the inverse hyperbolic cosine [0, +∞) of a given number from [1, +∞).Throws
Exceptionif the given value is less than 1 or isNaN. -
arcsin(sine: number): Radians | null— returns the inverse sine [-π/2, π/2] of the given sine value [-1, 1].Throws
Exceptionif the given sine is less than-1, greater than1or isNaN. -
arcsinh(value: number): number— returns the inverse hyperbolic sine (-∞, +∞) of a given number from (-∞, +∞). ThrowsExceptionif the given value isNaN. -
arctan(value: number): Radians— returns the inverse tangent [-π/2, π/2] of a given value from (-∞, +∞).Throws
Exceptionif the given value isNaN. -
arctan2(y: number, x: number): Radiansarctan2([y, x]: [number, number]): Radians— returns the angle in radians [-π, π] between the positive x-axis and the ray from (0, 0) to the point (x, y).Throws
Exceptionif eitheryorxisNaN. -
arctanh(value: number): number— returns the inverse hyperbolic tangent (-∞, +∞) of a given number from (-1, 1).Throws
Exceptionif the given value is less than or equal to -1, greater than or equal to 1, or isNaN. -
cos(angle: Radians): number— returns the cosine [-1, 1] of a given angle in radians.Throws
Exceptionif the given angle isNaNorInfinity. -
cosh(value: number): number— returns the hyperbolic cosine [1, +∞) of a given number.Throws
Exceptionif the given value isNaN. -
sin(angle: Radians): number— returns the sine [-1, 1] of a given angle in radians.Throws
Exceptionif the given angle isNaNorInfinity. -
sinh(value: number): number— returns the hyperbolic sine (-∞, +∞) of a given number.Throws
Exceptionif the given value isNaN. -
tan(angle: number): number— returns the tangent (-∞, +∞) of a given angle in radians.Throws
Exceptionif the given angle isNaNorInfinity. -
tanh(value: number): number— returns the hyperbolic tangent (-1, 1) of a given number.Returns 1 if the given value is
Infinity.Returns -1 if the given value is
-Infinity.Throws
Exceptionif the given value isNaN.
-
-
Set functions:
-
maximum(values: readonly number[]): number | null— returns the largest of given numbers (ignoresNaN). If the givenvaluesarray is empty or contains onlyNaN, returnsnull. Use this function instead ofMath.max, as it returnsInfinityorNaNfor edge cases. -
minimum(values: readonly number[]): number | null— returns the smallest of given numbers (ignoresNaN). If the givenvaluesarray is empty or contains onlyNaN, returnsnull. Use this function instead ofMath.min, as it returnsInfinityorNaNfor edge cases.
-
Integer
-
Nominal types (aliases of
number):-
Integer— a positive natural number, zero, and negative integer number. -
SafeInteger— integers from-(2^53 - 1)to2^53 - 1, inclusive. -
PositiveInteger— an integer that is greater than or equal to 0. -
NonNegativeInteger— an integer that is greater than 0. -
NonPositiveInteger— an integer that is less than or equal to 0. -
NegativeInteger. — an integer that is less than 0.
-
-
Predicates:
-
isInteger(value: number): value is Integer— returnstrueif a given number is an integer. -
isSafeInteger(value: number): value is SafeInteger— returnstrueif a given number is from-(2^53 - 1)to2^53 - 1, inclusive. -
isNonNegativeInteger(value: number): value is NonNegativeInteger— returnstrueif a given number is an integer and is greater than or equal to 0. -
isPositiveInteger(value: number): value is PositiveInteger— returnstrueif a given number is an integer and is greater than 0. -
isNonPositiveInteger(value: number): value is NonPositiveInteger— returnstrueif a given number is an integer and is less than or equal to 0. -
isNegativeInteger(value: number): value is NegativeInteger— returnstrueif a given number is an integer and is less than 0.
-
Natural
-
Nominal types (aliases of
number):-
Natural— a non-negative integer, according to the ISO 80000-2.
-
-
Type Guards:
-
isNatural<T>(value: number | T): value is Natural. — returnstrueif a given number is a non-negative integer.
-
Infinity
-
Nominal types:
-
Infinity— either aPositiveInfinityorNegativeInfinity. -
PositiveInfinity— an alias for anumbersignifyingNumber.POSITIVE_INFINITY. -
NegativeInfinity— an alias for anumbersignifyingNumber.NEGATIVE_INFINITY. -
FiniteNumber— a number that is notInfinity, norNaN.
-
-
Type guards:
-
isInfinity(value: number): value is Infinity— returnstrueif the given value is anInfinity. -
isFinite(value: number): value is FiniteNumber— returnstrueif the given value is notInfinityand is notNaN.
-
-
Assertions:
-
assertIsFinite(value: number): asserts value is FiniteNumberassertIsFinite(name: string, value: number): asserts value is FiniteNumber— asserts that the given value is a finite number (not
NaNand notInfinity).Throws
Exceptionif the given value isNaNor positive/negativeInfinity.
-
Base (Radix)
The parseFloat(), parseInt(), and Number.prototype.toString() functions are combined
into polymorphic shortcuts for readability and avoiding NaN.
-
decimal(value: number): string— returns a string representing a specified number in decimal notation (base 10). -
decimal(value: string): number | null— returns anumberparsed from a given string in decimal notation (base 10). If the string cannot be parsed, returnsnull. -
binary(value: Integer): string— returns a string representing a specified integer in binary notation (base 2). -
binary(value: string): Integer | null— Returns an integernumberparsed from a given string in binary notation (base 2). If the string cannot be parsed, returnsnull. -
octal(value: Integer): string— returns a string representing a specified integer in octal notation (base 8). -
octal(value: string): Integer | null— returns an integernumberparsed from a given string in octal notation (base 8). If the string cannot be parsed, returnsnull. -
hexadecimal(value: Integer): string— returns a string representing a specified integer in hexadecimal notation (base 16). -
hexadecimal(value: string): Integer | null— returns an integernumberparsed from a given string in hexadecimal notation (base 16). If the string cannot be parsed, returnsnull.
|
When any of these functions is passed as a parameter to a generic function or method,
TypeScript does not recognize the polymorphic
|
Order
-
Predicates:
-
isEqualTo(value: number): Predicate<number>— creates a function that returnstrueif the input number is equal to a givenvalue. -
isNotEqualTo(value: number): Predicate<number>— creates a function that returnstrueif the input number is not equal to a givenvalue. -
isGreaterThan(value: number): Predicate<number>— creates a function that returnstrueif the input number is greater than a givenvalue. -
isGreaterThanOrEqualTo(value: number): Predicate<number>— creates a function that returnstrueif the input number is greater than or equal to a givenvalue. -
isLessThan(value: number): Predicate<number>— creates a function that returnstrueif the input number is less than a givenvalue. -
isLessThanOrEqualTo(value: number): Predicate<number>— creates a function that returnstrueif the input number is less than or equal to a givenvalue.
-
-
Sorting:
-
ascending(a: number, b: number): number— returns a negative number if the first argument is less than the second argument.Can be used as a callback for the
Array.prototype.sort()method to sort numbers in ascending order. -
descending(a: number, b: number): number— Returns a positive number if the first argument is greater than the second argument.Can be used as a callback for the
Array.prototype.sort()method to sort numbers in descending order.
-
Interval
-
Type:
-
Intervalrepresents a real interval range.
-
-
Constructors:
-
interval(min: number, max: number): Interval | null— creates anIntervalfrom givenminandmaxnumbers. Ifminis greater thanmax, returnsnull. -
intervalFromPair(pair: readonly [number, number]): Interval | null— creates anIntervalfrom a given pair of numbers, where the first number isminand the second ismax. Ifminis greater thanmax, returnsnull. -
intervalFromValues(values: number[]): Interval | null— creates anIntervalfrom the minimum and maximum numbers in a given array of numbers. If the given array is empty, returnsnull. -
intervalFromNullable(min: number | null, max: number | null): Interval | null— creates anIntervalfrom the givenminandmaxnumbers, which can be null. If theminisnull, the interval will have a minimum of -∞. If themaxisnull, the interval will have a maximum of +∞.
-
-
Predicates:
-
isInInterval(interval: Interval): Predicate<number>— creates a predicate that checks returnstrueif the input number is greater than or equal to the givenintervalminimum, or is less than or equal the givenintervalmaximum. -
isInOpenInterval(interval: Interval): Predicate<number>— creates a predicate that checks returnstrueif the input number is greater than the givenintervalminimum, or is less than the givenintervalmaximum. -
isInLeftOpenInterval(interval: Interval): Predicate<number>— creates a predicate that checks returnstrueif the input number is greater than the givenintervalminimum, or is less than or equal the givenintervalmaximum. -
isInRightOpenInterval(interval: Interval): Predicate<number>— creates a predicate that checks returnstrueif the input number is greater than or equal to the givenintervalminimum, or is less than the givenintervalmaximum.
-
Bitmasks
-
Types:
-
Flags<T extends number = number>— anenumobject with a list of available bitmask flags. -
Flag<T extends Flags>— a bitmask flag defined in a givenFlagsenum. -
Bitmask<T extends Flags | number = number>— a bitmask consisting of one or moreFlags.
-
-
Constructor:
-
bitmask<T extends Flags | number = number>(flags: Bitmask<T>[]): Bitmask— creates a bitmask with all given flags raised.
-
-
Predicates:
-
isFlagOn<T extends Flags | number>(bitmask: Bitmask<T>, flag: Bitmask<T>): boolean— returnstrueif a given flag is raised on a bitmask. -
hasFlagOn<T extends Flags | number>(flag: Bitmask<T>): Unary<Bitmask<T>, boolean>— creates a function that returnstrueif a givenflagis raised in the input bitmask.
-
-
Functions:
-
raisedFlags<T extends number>(type: object, bitmask: Bitmask<T>): Member<T>[]— returns flags that are raised on the given bitmask.
-
Object
The @perfective/common/object package provides functions to work with the standard JS
Object class.
-
Types:
-
ObjectWithDefined<T, K extends keyof T>— an object of typeTwith a defined value of propertyK. -
ObjectWithUndefined<T, K extends keyof T>— an object of typeTwith an undefined value of propertyK. -
ObjectWithNotNull<T, K extends keyof T>— an object of typeTwith a non-null value of propertyK. -
ObjectWithNull<T, K extends keyof T>— an object of typeTwith a null value of propertyK. -
ObjectWithPresent<T, K extends keyof T>— an object of typeTwith a present value of propertyK. -
ObjectWithAbsent<T, K extends keyof T>— an object of typeTwith an absent value of propertyK. -
RecursivePartial<T>— a generic type that recursively marks all properties of a given typeTas optional. -
Entry<K = string, V = unknown>— a key-value pair (array).
-
-
Constructors:
-
recordFromArray(array: string[]): Record<string, number>— creates an object from a given array with the array values as keys and their indexes as values. -
recordFromEntries(entries: Entry[]): Record<string, unknown>— creates an object from a given array of entries. An inverse forObject.entries(). -
pick<T, K extends keyof T>(record: NonNullable<T>, …properties: readonly K[]): Pick<T, K>— creates a copy of a givenrecordonly with givenproperties. -
recordWithPicked<T, K extends keyof T>(…properties: readonly K[]): Unary<NonNullable<T>, Pick<T, K>>— creates a function topick()givenpropertiesfrom its argument. -
omit<T, K extends keyof T>(record: NonNullable<T>, …properties: readonly K[]): Omit<T, K>— creates a copy of a givenrecordwithout givenproperties. -
recordWithOmitted<T, K extends keyof T>(…property: readonly K[]): Unary<NonNullable<T>, Omit<T, K>>— creates a function toomit()givenpropertiesfrom its argument. -
filter<T, K extends keyof T>(record: NonNullable<T>, condition: Predicate<T[K]>): Partial<T>— creates a copy of a givenrecordonly with properties that meet a givencondition. -
recordFiltered<T, K extends keyof T = keyof T>(condition: Predicate<T[K]>): Unary<NonNullable<T>, Partial<T>>— creates a function tofilter()properties that satisfy a givenconditionfrom its argument. -
assigned<T, V = Partial<T>>(value: T, …overrides: (V | Partial<T>)[]): T & V— creates a shallow copy of the given value with the given overrides. -
function recordWithAssigned<T, V = Partial<T>>(…overrides: (V | Partial<T>)[]): (value: T) ⇒ T & V— creates a function to assign givenoverridesto its argument.
-
-
Type guards:
-
hasDefinedProperty<T, K extends keyof T>(property: K, …properties: readonly K[]): (value: T) ⇒ value is ObjectWithDefined<T, K>— returns a type guard that returnstrueif its argument has a definedpropertyand all givenproperties. -
hasUndefinedProperty<T, K extends keyof T>(property: K, …properties: readonly K[]): (value: T) ⇒ value is ObjectWithUndefined<T, K>— returns a type guard that returnstrueif its argument has an undefinedpropertyand all givenproperties. -
hasNotNullProperty<T, K extends keyof T>(property: K, …properties: readonly K[]): (value: T) ⇒ value is ObjectWithNotNull<T, K>— returns a type guard that returnstrueif its argument has a non-nullpropertyand all givenproperties. -
hasNullProperty<T, K extends keyof T>(property: K, …properties: readonly K[]): (value: T) ⇒ value is ObjectWithNull<T, K>— returns a type guard that returnstrueif its argument has a nullpropertyand all givenproperties. -
hasPresentProperty<T, K extends keyof T>(property: K, …properties: readonly K[]): (value: T) ⇒ value is ObjectWithPresent<T, K>— returns a type guard that returnstrueif its argument has a presentpropertyand all givenproperties. -
hasAbsentProperty<T, K extends keyof T>(property: K, …properties: readonly K[]): (value: T) ⇒ value is ObjectWithAbsent<T, K>— returns a type guard that returnstrueif its argument has an absentpropertyand all givenproperties.
-
-
Predicates:
-
isObject<T>(value: T | null): boolean— returnstruewhen the value is not null and is not a primitive. -
isRecord<T>(value: T): boolean— returnstruewhen the value is an object created from theObjectclass (not anArray,Date, etc.). -
isEmpty<T>(value: T): boolean— returnstruewhen the value is falsy, an empty array or aRecordwithout properties. -
hasMethod(value: unknown, method: string): boolean— returnstruewhen a given value implements a givenmethod. -
hasNoMethod(value: unknown, method: string): boolean— returnstruewhen a given value implements a givenmethod.
-
-
Reducers:
-
toRecordFromEntries(record: Record<string, unknown>, value: Entry): Record<string, unknown>— a reducer to build a record from entries.
-
-
Property functions:
-
property<T, K extends keyof T>(property: K): Unary<T, T[K]>— creates a function that for a given value returns the value of a givenproperty. -
property<T, K extends keyof T>(property: K, condition: Predicate<T[K]>): Predicate<T>— creates a predicate that for a given value returnstrueif a givenpropertysatisfies a givencondition. -
by<T, K extends keyof T>(property: K, order: Compare<T[K]>): Compare<T>— returns a function to compare two objects by theirpropertywith a givenordercallback.
-
Input
One of the challenges in API development is declaring type of requests (inputs).
On the client side these types need to be as strict as possible
(e.g., all fields that are required must be marked as required).
On the server side the same type need to be treated as completely unknown, unvalidated data.
If the type is written for the client side, compiler will not be able to enforce any checks on the server side.
At the same time, server side cannot just use the plain unknown type,
as any access to properties will be prohibited (with strict compiler settings).
To resolve this issue, the Input<T> type is introduced.
It recursively adds unknown, null, and undefined to the type of every field or value.
That allows to enforce validation of input data,
while declaring the original type T can be declared in strict mode for the client side.
Validation functions and additional input types cover default JSON types:
object, array, string, number, boolean, and null.
-
Types:
-
Input<T> -
InputArray<T> -
InputObject<T> -
InputPrimitive<T>
-
-
Unit function:
-
input<T>(input: unknown): Input<T>— type cast toInput<T>.
-
-
Basic validation functions:
-
stringInput(input: Input<string>): string | undefined -
numberInput(input: Input<number>): number | undefined -
booleanInput(input: Input<boolean>): boolean | undefined -
arrayInput<T>(input: Input<T[]>): Input<T>[] | undefined— checks that theinputis an array and returns it as an array of unvalidated elements. -
objectInput<T>(input: Input<T>): InputObject<T> | undefined— checks that theinputis a non-null, non-array object, and returns it as an object with unvalidated properties. -
nullInput(input: Input<null>): null | undefined.
-
Maybe chain to validate inputsimport { panic } from '@perfective/common/error';
import { maybe } from '@perfective/common/maybe';
import { isNatural, Natural } from '@perfective/common/number';
import { Input, InputObject, numberInput, objectInput } from '@perfective/common/object';
interface ExampleParams {
id: number;
}
interface Example {
params: ExampleParams;
}
function userId(request: Input<Example>): Natural {
return maybe(request) (1)
.to<InputObject<Example>>(objectInput) (2)
.pick('params')
.to<InputObject<ExampleParams>>(objectInput)
.pick('id')
.to(numberInput) (3)
.otherwise(panic('User ID is not defined'))
.that(isNatural) (4)
.or(panic('User ID is invalid'));
}
| 1 | request may be undefined. |
| 2 | At the moment type transformations are not inferred correctly,
so explicit type need to provided for objectInput. |
| 3 | Last validation of the input structure. |
| 4 | Final validation of the input, specific for the function. |
|
A custom validation monad may be added later to allow "collecting" all validation errors and warnings. |
Enum
-
Types:
-
Enum<T extends number | string>— AnObjectwith string keys and string or number values as generated by the TypeScript for anenumdefinition. -
Member<T extends number | string>— key of anenum. — Defines a type of the keys of anEnum.
-
-
Functions:
-
members<T extends number | string, E extends Enum<T>>(value: E): Member<T>[]— returns a list of anenumkeys.
-
Promise
The @perfective/common/promise package provides functions to work with the
Promise class.
Reference
Types
-
Resolvable<T> = T | PromiseLike<T>— a value that can be passed intoPromise.resolve. -
Resolve<T> = (value: Resolvable<T>) ⇒ void— a function that can called to resolve aPromisevalue. -
Reject<E extends Error = Error> = (reason?: E) ⇒ void— a function called to reject aPromisewith an optionalreason.This type is stricter than the default type of the reject callback, as it requires an
Erroras areason. -
Executor<T, E extends Error = Error> = (resolve: Resolve<T>, reject: Reject<E>) ⇒ void— a callback passed into thePromiseconstructor. -
OnFulfilled<T, U = T> = (value: T) ⇒ Resolvable<U>— aonFulfilledcallback passed into thePromise.then()method. -
OnRejected<T = never> = (reason: unknown) ⇒ Resolvable<T>— aonRejectedpassed into thePromise.then()orPromise.catch()methods. -
Callback<T, E extends Error = Error> = (error: E | null | undefined, value: T) ⇒ void— an error-first callback.
Constructors
-
promise<T, E extends Error = Error>(executor: Executor<T, E>): Promise<T>— creates a newPromisewith a givenexecutorcallback. -
fulfilled<T>(value: Resolvable<T>): Promise<Awaited<T>>— creates a fulfilledPromise(a shortcut for thePromise.resolve()function).Using
Promise.resolve()directly causes the@typescript-eslint/unbound-methodlinting error and a TS compiler error:TS2322: Type 'unknown' is not assignable to type 'T'. -
rejected<T = never>(reason: Error): Promise<Awaited<T>>— creates a rejectedPromise(a shortcut for thePromise.rejected()function).Using
Promise.rejected()directly causes the@typescript-eslint/unbound-methodlinting error. -
settled<T>(): BiFold<Resolvable<T>, Error, Promise<Awaited<T>>>— creates aBiFoldpair of callbacks to wrap a value into aPromise. -
settlement<T, E extends Error = Error>(resolve: Resolve<T>, reject: Reject<E>): Callback<T, E>— create aCallbackthat uses givenresolveandrejectfunctions from anexecutorto settle aPromise. Usesettlementto promisify functions written in the error-first callback style.
Result
The @perfective/common/result package provides
a Result type implementation.
The Result type represents a result of a function that can be a Success or a Failure (an Error).
It provides a mechanism for "checked exceptions"
and allows to avoid try-catch blocks and unchecked JavaScript errors.
Using Result with Promise
You can use Result<T> to handle synchronous parts of Promise chains.
import { error, throws } from '@perfective/common/error';
import { Unary } from '@perfective/common/function';
import { failure, promisedResult, Result, settledResult, success } from '@perfective/common/result';
import { isEmpty } from '@perfective/common/string';
interface Request {
method: string;
url: string;
}
function validInput(id: string): string {
if (isEmpty(id)) {
return throws(error('Input id is empty'));
}
return id;
}
function apiRequest(method: string): Unary<string, Request> {
return (id: string): Request => ({
method,
url: `/entity/${id}`,
});
}
async function apiResponse(request: Request): string {
if (request.method === 'HEAD') {
return '501 Not Implemented';
}
return '200 OK';
}
async function entityById(inputId: Promise<string>): Promise<string> {
return inputId
.then(validInput)
.then(apiRequest('HEAD')) (1)
.then(apiResponse);
}
async function entityById(inputId: Promise<string>): Promise<string> {
return promisedResult(inputId) (2)
.then(headApiRequest) (3)
.then(settledResult) (4)
.then(apiResponse);
}
async function headApiRequest(inputId: Result<string>): Result<Request> {
return inputId.onto(validId).to(apiRequest('HEAD'));
}
function validId(id: string): Result<string> {
if (isEmpty(id)) {
return failure(error('Input id is empty'));
}
return success(id);
}
| 1 | Even if the inputId is a Promise,
and the apiResponse is an asynchronous function,
the validInput and apiRequest functions are synchronous. |
| 2 | Use promisedResult to wrap Promise resolved value or rejection into a Result. |
| 3 | The result is transformed using a synchronous headApiRequest function. |
| 4 | Use settledResult to unpack a Result into a settled Promise.
In this case, if the value is a Success, a fulfilled Promise is returned.
But if the value is a Failure,
then a Promise is rejected. |
Reference
Types
-
BiMapResult<T, U> = BiMap<T, U, Error, Error>— a pair of callbacks for theResult.to()bifunctor. -
BiFoldResult<T, U> = Bifold<T, Error, U>— a pair of callbacks for theResult.into()method argument.
Functions
-
success<T>(value: T): Success<T>— creates aSuccessobject from a givenvalue. It is a unit function for theResultmonad.An
Errortype can also be used as aSuccessvalue.For example, when an API returns an error code and this error code is parsed and matched to a specific
Errorsubtype. -
successFrom<T, U>(map: (value: T) ⇒ U): Unary<T, Success<U>>— creates a function to transform avaluewith a givenmapcallback and return the result as aSuccess. -
failure<T>(error: Error): Failure<T>— creates aFailureobject from a givenerror.Throws a
TypeError, if the givenerrorvalue is not anErrorobject. -
failureFrom<T>(map: (value: T) ⇒ Error): Unary<T, Failure<T>>— creates a function to transform avalueinto anErrorwith a givenmapcallback and return the result as aFailure. -
rejection<T = never>(reason: unknown): Failure<T>— creates aFailurefrom anunknownreason. PassrejectionintoPromise.catch()orPromise.then()as anonRejectedcallback to wrap areasoninto aFailure. -
result<T>(value: T | Error): Result<T>— creates aFailureif a given value is anError, otherwise creates aSuccess.TypeScript compiler does not exclude an
Errortype from theTautomatically.If you pass a
valueof typeT | Errorto theresult(), the return type will beSuccess<T | Error>.You have to cast the type parameter manually (e.g.,
result<string>(…)) to get the return type asResult<T>. -
resultOf<T>(callback: Nullary<T>): Result<T>— calls a givencallbackin atry-catchblock. If thecallbackthrows an error, catches the error and returns it as aFailure. Otherwise, returns the result of thecallbackas aSuccess.Use the
resultOfto wrap up unsafe functions into aResult. -
resultFrom<T, U>(map: (value: T) ⇒ U): Unary<T, Result<U>>— creates a function to try transforming avaluewith a givenmapcallback and return it as aSuccess. If themapcallback throws, returns aFailure. -
promisedResult<T>(promise: Promise<T>): Promise<Result<T>>— wraps aPromisevalue into aResult. -
settledResult<T>(result: Result<T>): Promise<T>— creates a settledPromisefrom a givenResult. -
successWith<T, U>(map: Unary<T, U>): BiMapResult<T, U>— creates aBiMapResultpair with a givenmapcallback as the first element and an identity function as the second element. -
failureWith<T>(map: Unary<Error, Error>): BiMapResult<T, T>— creates aBiMapResultpair with a givenmapcallback as the second element and an identity function as the first element.
Type guards
-
isResult<T, U>(value: Result<T> | U): value is Result<T>— returnstrueif a givenvalueis aResult.-
isNotResult<T, U>(value: Result<T> | U): value is U— returnstrueif a givenvalueis not aResult.
-
-
isSuccess<T, U>(value: Success<T> | U): value is Success<T>— returnstrueif a givenvalueis aSuccess.-
isNotSuccess<T, U>(value: Success<T> | U): value is U— returnstrueif a givenvalueis not aSuccess.
-
-
isFailure<T, U>(value: Failure<T> | U): value is Failure<T>— returnstrueif a givenvalueis aFailure.-
isNotFailure<T, U>(value: Failure<T> | U): value is U— returnstrueif a givenvalueis not aFailure.
-
Result.onto()
-
Result.onto<U>(flatMap: (value: T) ⇒ Result<U>): Result<U>:-
for a
Success, applies a givenflatMapcallback to theSuccess.valueand returns the result; -
for a
Failure, ignores theflatMapcallback and returns the sameFailure.
-
-
Lifts:
-
onto<T, U>(value: Unary<T, Result<U>>): Unary<Result<T>, Result<U>>— creates a function to apply a givenvaluecallback to theResult.onto()method and return the result of thevalue. -
onto<T, U>(value: Unary<T, Failure<U>>): Unary<Result<T>, Failure<U>>— creates a function to apply a givenvaluecallback to theResult.onto()method and return the result of thevalue(aFailure).
-
import { error, typeError } from '@perfective/common/error';
import { Unary } from '@perfective/common/function';
import { Result, failure, success } from '@perfective/common/result';
import { isEmpty } from '@perfective/common/string';
interface Request {
method: string;
url: string;
}
interface Response {
status: string;
}
function validInput(id: string): Result<string> {
if (isEmpty(id)) {
return failure(typeError('Input id is empty'));
}
return success(id);
}
function apiRequest(method: string): Unary<string, Result<Request>> {
return (id: string): Result<Request> => success({
method,
url: `/entity/${id}`,
});
}
function apiResponse(request: Request): Result<Response> {
if (request.method === 'HEAD') {
return failure(error('Not implemented'));
}
return success({
status: '200 OK',
});
}
validInput('abc')
.onto(apiRequest('GET'))
.onto(apiResponse)
.value == { status: '200 OK' }; (1)
validInput('abc')
.onto(apiRequest('HEAD'))
.onto(apiResponse)
.value == error('Not implemented'); (2)
validInput('')
.onto(apiRequest('HEAD'))
.onto(apiResponse)
.value == typeError('Input id is empty'); (3)
| 1 | When we have a valid id and "send" a GET request,
then the whole chain succeeds. |
| 2 | When we have a valid id but "send" a HEAD request,
the apiResponse fails with an Error. |
| 3 | When we have an invalid id,
neither the apiRequest nor apiResponse callbacks are called.
So even as a |
Result.to()
-
Result.to<U>(map: Unary<T, U>): Result<U>:-
For a
Success, applies a givenmapcallback to theSuccess.valueand returns the result; -
For a
Failure, ignores themapcallback and returns the sameFailure.
-
-
Result.to<U>(mapValue: Unary<T, U>, mapError: Unary<Error, Error>): Result<U>-
For a
Success, applies a givenmapValuecallback to theSuccess.valueand returns the result as aSuccess; -
For a
Failure, applies a givenmapErrorcallback to theFailure.valueand returns the result as sFailure.This method can be used to track occurred failures occurred in a previous step by chaining them together using the
mapError.
-
-
Result.to<U>(maps: BiMapResult<T, U>): Result<U>:-
For a
Success, applies the first callback of a givenmapspair to theSuccess.valueand returns its result wrapped as aSuccess. -
For a
Failure, applied the second callback of a givenmapspair to theFailure.valueand returns its result wrapped as aFailure.This method allows to use a pair of
mapValueandmapErrorfunctions created dynamically.You can also use it with the
failureWithfunction to only transform aFailure.value.
-
-
Lifts:
-
to<T, U>(value: Unary<T, U>, error?: Unary<Error, Error>): Unary<Result<T>, Result<U>>— creates a function to apply givenvalueanderrorcallbacks to theResult.to()method and return the result. -
to<T, U>(maps: BiMapResult<T, U>): Unary<Result<T>, Result<U>>— creates a function to apply a givenmapscallbacks pair to theResult.to()method and return the result.
-
import { error, typeError } from '@perfective/common/error';
import { Unary } from '@perfective/common/function';
import { Result, failure, success } from '@perfective/common/result';
import { isEmpty } from '@perfective/common/string';
interface Request {
method: string;
url: string;
}
interface Response {
status: string;
url: string;
}
function validInput(id: string): Result<string> {
if (isEmpty(id)) {
return failure(typeError('Input id is empty'));
}
return success(id);
}
function apiRequest(method: string): Unary<string, Request> {
return (id: string): Request => ({
method,
url: `/entity/${id}`,
});
}
function apiResponse(request: Request): Response {
return {
status: '200 OK',
url: request.url,
};
}
validInput('abc')
.to(apiRequest('GET'))
.to(apiResponse) (1)
.value == { status: '200 OK' }; (2)
validInput('')
.to(apiRequest('GET'))
.to(apiResponse)
.value == typeError('Input id is empty'); (3)
| 1 | Both apiRequest and apiResponse transform a given value into a new one.
Result.to wraps them into the next Success. |
| 2 | When we have a valid id,
then the whole chain succeeds. |
| 3 | When we have an invalid id,
neither apiRequest nor apiResponse callbacks are called.
So the result is the TypeError returned by the validInput. |
Result.to with the failureWith() function to only transform a Failure.value.import { chained, typeError } from '@perfective/common/error';
import { failure, failureWith, Result, success } from '@perfective/common/result';
import { isEmpty } from '@perfective/common/string';
function validInput(id: string): Result<string> {
if (isEmpty(id)) {
return failure(typeError('Input id is empty'));
}
return success(id);
}
function entityByIdRequest(id: string): Result<Request> {
return validInput(id)
.to(failureWith(chained('Entity ID {{id}} is invalid' { (1)
id,
})))
.to(apiRequest('GET'));
}
| 1 | You can also combine both callbacks into one Result.to(apiRequest(…), chained(…)) call. |
|
The following calls are equivalent
|
Result.into()
-
Result.into<U>(fold: BiFoldResult<T, U>): U:-
For a
Success, applies the first callback of thefoldpair to theSuccess.valueand returns the result. -
For a
Failure, applies the second callback of thefoldpair to theFailure.valueand returns the result.Result.into(fold)allows to pass a pair of reduce callbacks dynamically as one argument.
-
-
Result.into<U>(reduceValue: Unary<T, U>, reduceError: Unary<Error, U>): U:-
For a
Successapplies a givenreduceValueto theSuccess.value, -
For a
Failureapplies a givenreduceErrorto theFailure.value(Error).
Result.into(reduceValue, reduceError)separates handling of theSuccess.valueandFailure.value. It is especially useful when theSuccess.valueis anError. As in this case, theResult.into(reduce)call may not be able to distinguish between aSuccess.valueErrorand aFailure.valueError. -
-
Lifts:
-
into<T, U>(value: Unary<T, U>, error: Unary<Error, U>): Unary<Result<T>, U>— creates a function to apply givenvalueanderrorcallbacks to theResult.into()method and return the result. -
into<T, U>(fold: BiFoldResult<T, U>): Unary<Result<T>, U>— creates a function to apply a givenfoldcallbacks pair to theResult.into()method and return the result.
-
Result.into() method to switch to a Promise.import { typeError } from '@perfective/common/error';
import { Unary } from '@perfective/common/function';
import { rejected } from '@perfective/common/promise';
import { failure, Result, success } from '@perfective/common/result';
import { isEmpty } from '@perfective/common/string';
interface Request {
method: string;
url: string;
}
function validInput(id: string): Result<string> {
if (isEmpty(id)) {
return failure(typeError('Input id is empty'));
}
return success(id);
}
function apiRequest(method: string): Unary<string, Request> {
return (id: string): Request => ({
method,
url: `/entity/${id}`,
});
}
async function apiResponse(request: Request): Promise<string> {
if (request.method === 'HEAD') {
return '501 Not Implemented';
}
return '200 OK';
}
async function entityById(id: string): Promise<string> {
return validInput('') (1)
.otherwise('abc') (2)
.to(apiRequest('HEAD')) (3)
.into(apiResponse) (4)
.catch(() => '503 Service Unavailable'); (5)
}
| 1 | The id input is not valid. |
| 2 | Fallback to abc as a valid id. |
| 3 | The Result.otherwise() always returns a Success,
so the whole chain now is strictly a Success. |
| 4 | When we have a Request,
we use Result.into() to switch into the apiResponse Promise. |
| 5 | Now we have a Promise chain and can continue computation. |
Result.that()
-
Result.that(filter: Predicate<T>, error: Value<Error>): Result<T>-
For a
Success, if thevaluesatisfies a givenfilter, returns itself. Otherwise, returns aFailurewith a givenerror. -
For a
Failure, ignores both arguments and returns itself.
-
-
Result.that(filter: Predicate<T>, message: Value<string>): Result<T>-
For a
Success, if thevaluesatisfies a givenfilter, returns itself. Otherwise, returns aFailurewith anExceptioncreated with a givenmessage, a{{value}}token created from theSuccess.value, and theSuccess.valuepassed into theExceptionContext. -
For a
Failure, ignores both arguments and returns itself.
-
-
Lifts:
-
that<T>(filter: Predicate<T>, error: Value<Error>): Unary<Result<T>, Result<T>>— creates a function to apply givenfilterpredicate anderrorto theResult.that()method and return the result. -
that<T>(filter: Predicate<T>, message: Value<string>): Unary<Result<T>, Result<T>>— creates a function to apply givenfilterpredicate andmessageto theResult.that()method and return the result.
-
Result.that() to build validation chains.import { typeError } from '@perfective/common/error';
import { isGreaterThan, isNumber } from '@perfective/common/number';
import { Result, success } from '@perfective/common/result';
import { isNotEmpty } from '@perfective/common/string';
function validId(id: string): Result<number> { (1)
return success(id)
.that(isNotEmpty, typeError('Input id is empty')) (2)
.to(Number)
.that(isNumber, 'Failed to parse input {{value}} as a number') (3)
.that(isGreaterThan(0), 'Input id must be greater than 0'); (4)
}
| 1 | If the input can be parsed as a number and is greater than zero,
the function returns Success with a parsed number value. |
| 2 | For an empty string, the function returns a Failure with a TypeError message Input id is empty. |
| 3 | For an input that cannot be parsed as a number, like Zero,
the function returns a Failure with an Exception an a tokenized message
(e.g. Failed to parse input 'Zero' as a number) |
| 4 | For an input that is not greater than 0,
the function returns a Failure with an Exception with message Input id must be greater than 0. |
Result.which()
-
Result.which<U extends T>(typeGuard: TypeGuard<T, U>, error: Value<Error>): Result<U>-
For a
Success, if thevaluesatisfies a giventypeGuard, returns itself with the type narrowed down by thetypeGuard. Otherwise, returns aFailurewith a givenerror. -
For a
Failure, returns itself.
-
-
Result.which<U extends T>(typeGuard: TypeGuard<T, U>, message: Value<string>): Result<U>-
For a
Success, if thevaluesatisfies a giventypeGuard, returns itself with the type narrowed down by thetypeGuard. Otherwise, returns aFailurewith anExceptioncreated with a givenmessage, a{{value}}token created from theSuccess.value, and theSuccess.valuepassed into theExceptionContext.
-
-
Lifts:
-
which<T, U extends T>(typeGuard: TypeGuard<T, U>, error: Value<Error>): Unary<Result<T>, Result<U>>— creates a function to apply giventypeGuardanderrorto theResult.which()method and return the result. -
which<T, U extends T>(typeGuard: TypeGuard<T, U>, message: Value<string>): Unary<Result<T>, Result<U>>— creates a function to apply giventypeGuardand errormessageto theResult.which()method and return the result.
-
Result.which() to build validation chains.import { isNotNull } from '@perfective/common';
import { decimal } from '@perfective/common/number';
import { Result, success } from '@perfective/common/result';
function validId(id: string): Result<number> {
return success(id)
.to(decimal) // == Result<number | null>
.which(isNotNull, 'Failed to parse id as a number'); (1)
}
| 1 | The decimal() function parses a string and returns number or null (if parsing failed).
Result.which() allows to narrow the type with a type guard (isNotNull),
so the type becomes Result<number>. |
Result.when()
-
Result.when(condition: Proposition, error: Value<Error>): Result<T>-
For a
Success, if theconditionis true, returns itself. Otherwise, returns aFailurewith a givenerror. -
For a
Failure, returns itself.
-
-
Result.when(condition: Proposition, message: Value<string>): Result<T>-
For a
Success, if theconditionis true, returns itself. Otherwise, returns aFailurewith a givenmessage. -
For a
Failure, returns itself.
-
-
Lifts:
-
when<T>(condition: Proposition, error: Value<Error>): Unary<Result<T>, Result<T>>— creates a function to apply givenconditionanderrorto theResult.when()method and return the result. -
when<T>(condition: Proposition, message: Value<string>): Unary<Result<T>, Result<T>>— creates a function to apply givenconditionandmessageto theResult.when()method and return the result.
-
Result.when() to guard based on an external condition.import { Result } from '@perfective/common/result';
interface Book {
isbn: string;
title: string;
}
interface UserService {
hasPermission: (permission: string) => boolean;
}
interface BookStore {
byIsbn: (isbn: string) => Result<Book>;
}
class BooksService {
public constructor(
private readonly user: UserService,
private readonly books: BookStore,
) {}
public bookByIsbn(isbn: string): Result<Book> { (1)
return this.books.byIsbn(isbn)
.when(() => this.user.hasPermission('read book'), 'Access denied'); (2)
}
}
| 1 | The booksByIsbn() method composes UserService and BookService to load books
only when an active user has permissions to read them. |
| 2 | If the hasPermissions() method returns false,
then the bookByIsbn() method returns a Failure with "Access denied" Exception. |
Result.otherwise()
-
Result.otherwise(recovery: Recovery<T>): Success<T>-
For a
Success, returns itself. -
For a
Failure, applies itsvalue(Error) to a givenrecoverycallback and returns the result wrapped into aSuccess.
-
-
Lift:
-
otherwise<T>(recovery: Recovery<T>): Unary<Result<T>, Success<T>>— creates a function to apply a givenrecoverycallback to theResult.otherwise()method and return the result.
-
Result.otherwise() to recover from an error and continue the computation chainimport { NonNegativeInteger, PositiveInteger } from '@perfective/common/number';
import { Result } from '@perfective/common/result';
interface User {
id: NonNegativeInteger;
username: string;
}
interface UserStore {
byId: (id: PositiveInteger) => Result<User>;
}
interface Log {
error: (error: Error) => void;
}
function fallback<T>(log: Log, value: T): (error: Error) => T {
return (error: Error): T => {
log.error(error);
return value;
};
}
function anonymousUser(): User {
return {
id: 0,
username: 'anonymous',
};
}
class UserService {
public constructor(
private readonly users: UserStore,
private readonly log: Log,
) {}
public usernameById(id: number): string {
return this.users.byId(id) (1)
.otherwise(fallback(this.log, anonymousUser())) (2)
.to(user => user.username)
.value;
}
}
| 1 | UserStore.byId() returns a Result<User>, so it may return a Failure. |
| 2 | Log the failure and return a fallback, so the chain can be completed. |
Result.or()
-
Result.or(recovery: Recovery<T>): T-
For a
Success, returns its ownvalue. -
For a
Failure, applies itsvalue(Error) to a givenrecoverycallback and returns the result.
-
-
Lift:
-
or<T>(recovery: Recovery<T>): Unary<Result<T>, T>— creates a function to apply a givenrecoverycallback to theResult.or()method and return the result.
-
Result.or() to recover from an error and return the result of computationimport { isNotNull } from '@perfective/common';
import { decimal, isGreaterThan, isInteger, NonNegativeInteger, PositiveInteger } from '@perfective/common/number';
import { Result, success } from '@perfective/common/result';
interface User {
id: NonNegativeInteger;
username: string;
}
interface UserStore {
byId: (id: PositiveInteger) => Result<User>;
}
interface Log {
error: (error: Error) => void;
}
function fallback<T>(log: Log, value: T): (error: Error) => T {
return (error: Error): T => {
log.error(error);
return value;
};
}
function validId(id: string): Result<PositiveInteger> { (1)
return success(id)
.to(decimal) // == Result<number | null>
.which(isNotNull, 'Failed to parse id as a number')
.that(isInteger, 'User ID must be an integer')
.that(isGreaterThan(0), 'User ID must be positive');
}
function anonymousUser(): User {
return {
id: 0,
username: 'anonymous',
};
}
class UserService {
public constructor(
private readonly users: UserStore,
private readonly log: Log,
) {}
public userById(id: string): User {
return validId(id)
.onto(id => this.users.byId(id)) (2)
.or(fallback(this.log, anonymousUser())); (3)
}
}
| 1 | The validId() function may return a handful of different failures. |
| 2 | The UserStore.byId() method returns a Result, so it may also return a failure. |
| 3 | In case of a failure we log the error and return an anonymous user object. |
Result.through()
-
Result.through(valueProcedure: UnaryVoid<T>, errorProcedure?: UnaryVoid<Error>): this:-
For a
Success, passes thevaluethrough a givenvalueProcedureand returns itself. -
For a
Failure, passes thevaluethrough a givenerrorProcedureand returns itself.
-
-
Result.through(procedures: BiVoidResult<T>): this:-
For a
Success, passes thevaluethrough the first procedure in theprocedurespair and returns itself. -
For a
Failure, passes thevaluethrough the second procedure in theprocedurespair and returns itself.
-
-
Lifts:
-
through<T>(value: UnaryVoid<T>, error: UnaryVoid<Error>): Unary<Result<T>, Result<T>>— creates a function to apply givenvalueanderrorcallbacks to theResult.through()method and return the givenResult. -
through<T>(procedures: BiVoidResult<T>): Unary<Result<T>, Result<T>>— creates a function to apply a givenprocedurescallbacks pair to theResult.through()method and return the givenResult.
-
import { typeError } from '@perfective/common/error';
import { empty } from '@perfective/common/function';
import { failure, Result, success } from '@perfective/common/result';
import { isEmpty } from '@perfective/common/string';
function validInput(id: string): Result<string> {
if (isEmpty(id)) {
return failure(typeError('Input id is empty'));
}
return success(id);
}
function entityByIdRequest(id: string): Result<Request> {
return validInput(id)
.to(apiRequest('GET'))
.through(empty, console.error); (1)
}
| 1 | When we have a Success, we only pass a no-op empty function.
But if we have a Failure, we log an error.
Either way, the Result is the same. |
Type classes
Monad
The Result<T> type is a monad that provides:
-
the
Result.onto()method as a bind (>>=) operator; -
the
success()constructor as a unit (return) function.
It satisfies the monad laws:
-
unit is a left identity for bind:
let x: T; let f: (value: T) => Result<T>; success(x).onto(f) === f(x); -
unit is a right identity for bind:
let ma: Result<T>; ma.onto(success) === ma; -
bind is associative:
let ma: Result<T>; let f: (value: T) => Success<U>; let g: (value: U) => Success<V>; ma.onto(a => f(a).onto(g)) === ma.onto(f).onto(g);
Functor
The Result<T> type is a functor that provides:
-
the
Result.to()method as a fmap function.
It satisfies the functor laws:
-
Result.to()preserves identity morphisms:let id = (value: T) => value; let value: T; let error: Error; success(value).to(id) === success(id(value)); failure(error).to(id) === failure(id(error)); -
Result.to()preserves composition of morphisms:let f: (value: U) => V; let g: (value: T) => U; let value: T; let error: Error; success(value).to(v => f(g(v))) === success(value).to(g).to(f); failure(error).to(v => f(g(v))) === failure(error).to(g).to(f); (1)1 Failure.to()ignores the input and always returns itself.
Bifunctor
The Result<T> type is a bifunctor that provides:
-
the
Result.to(maps)method as the bimap function. -
the
successWith()function as the second function. -
the
failureWIth()function as the first function.
Which ensures that:
-
Result.to(maps)preserves identity morphismslet id = (value: T) => value; let value: T; let error: Error; success(value).to([id, id]) === success(id(value)); failure(error).to([id, id]) === failure(id(error)); -
Result.to(successWith(mapValue))preserves identity morphisms -
Result.to(failureWith(mapError))preserves identity morphisms -
Applying the
bimapfunction is the same as applying thefirstandsecondfunctions.let f: (value: Error) => Error; let s: (value: T) => U; let value: T; let error: Error; success(value).to([s, f]) === success(value).to(successWith(s)).(failureWith(f)); failure(error).to([s, f]) === failure(error).to(successWith(s)).(failureWith(f));
String
The @perfective/common/string package works with the standard JS
String type.
It provides the following functions and additional types.
-
Type guards:
-
isString<T>(value: T | string): value is string— returnstrueif a given value is astring. -
isNotString<T>(value: T | string): value is T— returnstrueif a given value is not astring.
-
-
Properties:
-
length(value: string): number— returns the length of a given string.
-
-
Predicates:
-
isEmpty(value: string): boolean— returnstrueif a given string is empty. -
isNotEmpty(value: string): boolean— returnstrueif a given string is not empty.
-
-
Operators:
-
lines(value: string): string[]— splits a given string into an array of string based on the line separator (\n,\r\n, and\r). -
lowerCase(value: string): string— converts a given string to lower case. -
upperCase(value: string): string— converts a given string to upper case. -
trim(value: string): string— removes whitespace from both ends of a given string.
-
-
Curried functions:
-
charAt(index: number): Unary<string, string>— creates a function that returns a UTF-16 code unit at a given zero-basedindexin the input string. -
concat(…strings: string[]): Unary<string, string>— creates a function that returns a string built from the input string and concatenated with givenstrings. -
concatTo(value: string): Unary<string | string[], string>— creates a function that returns a string built from the input string(s) concatenated to a givenstring. -
endsWith(search: string, endPosition?: number): Unary<string, boolean>— creates a function that returnstrueif a givensearchstring is found at a givenendPositionindex of the input string.If
endPositionis omitted, the input string length is used. -
includes(search: string, position: number = 0): Unary<string, boolean>— creates a function that returnstrueif a givensearchstring is found in the input string starting at a givenpositionindex. -
indexOf(search: string, from: number = 0): Unary<string, number | -1>— creates a function that returns the index of the first occurrence of a givensearchstring in the input string, starting at a givenfromindex; or returns-1if the givensearchstring is not found. -
lastIndexOf(search: string, from?: number): Unary<string, number | -1>— creates a function that returns the index of the first occurrence of a givensearchstring in the input string, starting at a givenfromindex; or returns-1if the givensearchstring is not found. -
padEnd(length: number, fill?: string): Unary<string, string>— creates a function that pads the of end the input string with a givenfillstring up to the targetlength. -
padStart(length: number, fill?: string): Unary<string, string>— creates a function that pads the of start the input string with a givenfillstring up to the targetlength. -
repeat(count: number): Unary<string, string>— creates a function that creates a string consisting of a givencountof copies of the input string. -
replace(search: string | RegExp, replacement: string): Unary<string, string>— creates a function that replaces givensearchsubstrings in the input string with a givenreplacement. -
replaceWith(search: string | RegExp, replacement: Replacement): Unary<string, string>— creates a function that replaces givensearchsubstrings in the input string with a result of thereplacementfunction (invoked on every match). -
search(search: RegExp): Unary<string, number | -1>— creates a function that returns the index of the first occurrence of a given regular expression in the input string; or returns-1if the given expression is not found. -
slice(start: number, end?: number): Unary<string, string>— creates a function that returns a section of the input string from a givenstartindex to the end of the string, or to a givenendindex (exclusive). -
split(separator: string | RegExp, limit?: number): Unary<string, string[]>— creates a function that creates an ordered list of substrings by splitting the input string using a givenseparatorand up to an optionallimit. -
startsWith(search: string, from: number = 0): Unary<string, boolean>— creates a function that returnstrueif the input string begins with a givensearchsubstring at a givenfromindex.
-
-
Utf16CodeUnit: — an integer between0and65535(0xFFFF) representing a UTF-16 code unit.-
charCodeAt(index: number): Unary<string, Utf16CodeUnit>— creates a function that returns a UTF-16 code unit value at a given zero-basedindexin the input string.
-
-
CodePoint: — an integer between0and0x10FFFF(inclusive) representing a Unicode code point.-
codePointAt(position: number): Unary<string, CodePoint | undefined>— creates a function that returns a code point value of the character at a givenindexin the input string; or returnsundefinedif the givenindexis out of range.
-
-
UnicodeNormalizationForm:-
normalize(form: UnicodeNormalizationForm = 'NFC'): Unary<string, string>— creates a function that returns a string containing the Unicode Normalization Form of the input string for a given normalizationform.
-
Format
-
Format— represents a template with tokens that can be turned into a string.-
format(template: string, tokens: Tokens | unknown[] = {}): Format— creates a {@link Format} record with the giventemplateandtokens. -
formatted(input: Format): string— replacesFormat.tokensin theFormat.templateand returns the resulting string.Each token is wrapped in the double curly braces. For example, a template with a token
{{foo}}will be replaced by the string value of the tokenfoo.
-
-
Tokens: — a mapping between a token and its string value.-
tokens(tokens: unknown[] | Tokens): Tokens— createsTokensrecord from a given array of positional tokens, where each token is an index of each value in the given array.If given a
Tokensobject returns the given object.
-
Roadmap
-
Implement with the
@perfective/common/localepackage: -
Implement with the
@perfective/common/regexppackage: -
Implement with a polyfill: