Package Exports
- @binarymuse/ts-stdlib
- @binarymuse/ts-stdlib/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@binarymuse/ts-stdlib) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
ts-stdlib
@binarymuse/ts-stdlib is a set of classes, utilities, and types to make working with TypeScript a little bit nicer. These concepts can be found in many different languages, although many of the implementations here are inspired by Rust.
The library includes:
Wrapper Types:
Option<T>- a type that represents a value (Some<T>) or the absence of one (None)Result<T, E>- a type that represents a successful result (Ok<T>) or an error (Err<E>)Rc<T>- a reference counted resource
Container Types:
Deque<T>- a double-ended queue, implemented with a doubly-linked list
Installation
npm install @binarymuse/ts-stdlib
# or
pnpm add @binarymuse/ts-stdlib
# or
yarn add @binarymuse/ts-stdlibOption<T>
import { Option, Some, None } from "@binarymuse/ts-stdlib"For a longer guide on using Option<T>, see guides/option.md.
An Option<T> has two variants:
Some<T>, representing the existance of the inner valueNone, representing the absence of an inner value
Creating an option
Some(value: T): Option<T>- create aSomefrom a value; note that callingSome(undefined)orSome(null)will returnNoneNone- reference to the singletonNonevalue
Querying the inner value
Option<T>.isSome(): booleanReturns
trueif the option isSome,falseotherwiseOption<T>.isSomeAnd(fn: (value: T) => boolean): booleanReturns
trueif the option isSomeand callingfnwith the inner value returnstrue,falseotherwiseOption<T>.isNone(): booleanReturns
trueif the option isNone,falseotherwiseOption<T>.isNoneOr(fn: (value: T) => boolean): booleanReturns true if the option is
Noneor callingfnwith the inner value returnstrue,falseotherwiseOption<T>.unwrap(): TReturns the underlying value if the option is
Some, otherwise throws an exceptionOption<T>.expect(msg: string): TReturns the underlying value if the option is
Some, otherwise throws an exception with a custom messageOption<T>.unwrapOr(default: T): TReturns the underlying value if the option is
Some, otherwise returnsdefaultOption<T>.unwrapOrElse(fn: () => T): TReturns the underlying value if the option is
Some, otherwise callsfnand returns its return valueOption<T>.filter(fn: (T) => boolean): Option<T>Returns
Noneif the option isNone, otherwise callsfnwith the inner value and returns:Some<T>with the original wrapped value iffnreturns trueNoneiffnreturns false
Option<T>.match<U>(cases: { some: (value: T) => U; none: () => U }): UReturns the result of calling
cases.some()with the inner value if the option isSome, otherwise returns the result of callingcases.none()Option<T>.equals(other: Option<T>): booleanReturns true if both options are
Someand their inner values are equal using the JavaScript==operator, or if both options areNone. As a special case, if both options areSomeand their inner values are alsoSomeor another library type (likeResult), their inner values are compared withequals().Option<T>.strictEquals(other: Option<T>): booleanReturns true if both options are
Someand their inner values are equal using the JavaScript===operator, or if both options areNone. As a special case, if both options areSomeand their inner values are alsoSomeor another library type (likeResult), their inner values are compared withstrictEquals().
Transforming options
Option<T>.map<U>(fn: (value: T) => U): Option<U>Returns an
Option<U>by mapping the inner value of the source option withfnOption<T>.mapOr<U>(defaultValue: U, mapFn: (value: T) => U): Option<U>Returns an option wrapping the provided
defaultValueif the option isNone, or callsmapFnwith the inner value and returns a new option wrapping its return valuemapOrElse: <U>(defaultFn: () => U, mapFn: (value: T) => U): Option<U>Returns an option wrapping the return value of
defaultFnif the option isNone, or callsmapFnwith the inner value and returns a new option wrapping its return valueOption<T>.and<U>(other: Option<U>): Option<U>Returns
Noneif the source option isNone, otherwise returnsotherOption<T>.andThen<U>(fn: (value: T) => Option<U>): Option<U>Returns
Noneif the source option isNone, otherwise callsfnwith the inner value and returns the result.Option<T>.or(other: Option<T>): Option<T>Returns the source option if it is
Some, otherwise returnsotherOption<T>.orElse(fn: () => Option<T>): Option<T>Returns the source option if it is
Some, otherwise callsfnand returns the resultOption<T>.xor(other: Option<T>) => Option<T>Returns the source option or
otherif exactly one of them isSome, otherwise returnsNoneOption<T>.flatten(): Option<T>Converts from
Option<Option<T>>toOption<T>. Only one level of nesting is removed.
Result<T, E>
import { Result, Ok, Err } from "@binarymuse/ts-stdlib"For a longer guide on using Result<T, E>, see guides/result.md.
A Result<T, E> has two variants:
Ok<T>, representing a successful result containing a value of typeTErr<E>, representing an error containing a value of typeE
Creating a result
Ok<T, E = never>(value: T): Result<T, E>- create anOkresult containing a success valueErr<E, T = never>(error: E): Result<T, E>- create anErrresult containing an error value
Since TypeScript can't infer the type of E (in the case of Ok) or the type of T (in the case of Err), it can be useful to explicitly define these types when creating the Result:
const result1: Result<ValueType, ErrorType> = Ok(value);
const result2: Result<ValueType, ErrorType> = Err(error);Querying the result
Result<T, E>.isOk(): booleanReturns
trueif the result isOk,falseotherwiseResult<T, E>.isOkAnd(fn: (value: T) => boolean): booleanReturns
trueif the result isOkand callingfnwith the inner value returnstrue,falseotherwiseResult<T, E>.isErr(): booleanReturns
trueif the result isErr,falseotherwiseResult<T, E>.isErrAnd(fn: (error: E) => boolean): booleanReturns
trueif the result isErrand callingfnwith the error value returnstrue,falseotherwiseResult<T, E>.unwrap(): TReturns the underlying value if the result is
Ok, otherwise throws an exceptionResult<T, E>.unwrapOr(defaultValue: T): TReturns the underlying value if the result is
Ok, otherwise returnsdefaultValueResult<T, E>.unwrapOrElse(fn: () => T): TReturns the underlying value if the result is
Ok, otherwise callsfnand returns its return valueResult<T, E>.unwrapErr(): EReturns the error value if the result is
Err, otherwise throws an exceptionResult<T, E>.expect(msg: string): TReturns the underlying value if the result is
Ok, otherwise throws an exception with the provided messageResult<T, E>.expectErr(msg: string): EReturns the error value if the result is
Err, otherwise throws an exception with the provided messageResult<T, E>.equals(other: Result<T, E>): booleanReturns true if both results are
Okand their inner values are equal using the JavaScript==operator, or if both results areErrand their error values are equal using the JavaScript==operator. As a special case, if both results contain inner values that are alsoResultorOptiontypes, their inner values are compared withequals().Result<T, E>.strictEquals(other: Result<T, E>): booleanReturns true if both results are
Okand their inner values are equal using the JavaScript===operator, or if both results areErrand their error values are equal using the JavaScript===operator. As a special case, if both results contain inner values that are alsoResultorOptiontypes, their inner values are compared withstrictEquals().
Transforming results
Result<T, E>.map<U>(fn: (value: T) => U): Result<U, E>Returns a
Result<U, E>by mapping the success value of the source result withfnResult<T, E>.mapOr<U>(defaultValue: U, fn: (value: T) => U): Result<U, E>Returns a result wrapping the provided
defaultValueif the source result isErr, or callsfnwith the source result's success value and returns a new result wrapping its return valueResult<T, E>.mapOrElse<U>(defaultFn: () => U, mapFn: (value: T) => U): Result<U, E>Returns a result wrapping the return value of
defaultFnif the source result isErr, or callsmapFnwith the source result's success value and returns a new result wrapping its return valueResult<T, E>.mapErr<U>(fn: (error: E) => U): Result<T, U>Returns a
Result<T, U>by mapping the error value of the source result withfnResult<T, E>.and<U>(other: Result<U, E>): Result<U, E>Returns
otherif the source result isOk, otherwise returns the source result's errorResult<T, E>.andThen<U>(fn: (value: T) => Result<U, E>): Result<U, E>Returns the error if the source result is
Err, otherwise callsfnwith the success value and returns the resultResult<T, E>.or<U>(other: Result<U, E>): Result<U, E>Returns the source result if it is
Ok, otherwise returnsotherResult<T, E>.orElse<U>(fn: () => Result<U, E>): Result<U, E>Returns the source result if it is
Ok, otherwise callsfnand returns the resultResult<T, E>.flatten(): Result<T, E>Converts from
Result<Result<T, E>, E>toResult<T, E>. Only one level of nesting is removed.
Inspecting results
Result<T, E>.inspect(fn: (value: T) => void): Result<T, E>Calls
fnwith the success value if the result isOk, otherwise does nothing. Returns the original result.Result<T, E>.inspectErr(fn: (error: E) => void): Result<T, E>Calls
fnwith the error value if the result isErr, otherwise does nothing. Returns the original result.
Converting to options
Result<T, E>.ok(): Option<T>Converts the
Result<T, E>into anOption<T>, mappingOk(v)toSome(v)andErr(_)toNoneResult<T, E>.err(): Option<E>Converts the
Result<T, E>into anOption<E>, mappingOk(_)toNoneandErr(e)toSome(e)
Rc<T>
import { Rc, Weak } from "@binarymuse/ts-stdlib"For a longer guide on using Rc<T>, see guides/rc.md.
An Rc<T> is a reference-counted object. Since JavaScript uses garbage collection and doesn't have destructors, this type isn't as useful as it is in languages like Rust. However, there are situations where they can be useful.
Creating an Rc
To create an Rc<T>, use the Rc(resource: T, cleanup: (res: T) => void) function. This will return an Rc<T>, which has access to all the same methods and properties as the original resource. Internally, this is managed using Proxies, so your JavaScript environment must support them in order to use Rc.
Since an Rc in JavaScript is only useful in situations where the underlying resource has some cleanup method that needs to be called before it is garbage collected, the second argument to Rc() is a function that takes the wrapped value and does any appropriate cleanup.
To create a copy of a reference, incrementing the internal counter, you can use Rc.clone(rc). To clean up an Rc, potentially invoking the cleanup function (if it's the last reference of that particular resource), call Rc.dispose(rc). If you don't call Rc.dispose() for every Rc created, the underlying resource will never be cleaned up.
Weak references
At times, it's useful to create references to a resource that can be used to access the resource, but that won't keep the resource alive if all the other references are disposed. This is known as a "weak" reference (whereas Rc is a "strong" reference). Create a weak reference from a strong one by passing it to Rc.weak(rc) or Rc.intoWeak(rc).
To be useful, a weak reference must be able to be turned back into a strong reference. You can do this with Rc.upgrade(weak), getting an Option<Rc<T>>. However, since weak references don't keep a resource from being cleaned up, it's possible that the resource is already disposed (due to all the strong references already being disposed). In this case, Rc.upgrade(weak) will return None.
API
Since an Rc exposes all the methods and properties of its wrapped resource, all the methods are static functions on Rc itself.
Rc<T extends object>(resource: T, cleanup: (res: T) => void): Rc<T>Creates a new strong reference-counted resource wrapping
resource. When the last strong reference is disposed, the cleanup function is run.Rc.clone<T>(rc: Rc<T>): Rc<T>Creates a copy of the
Rc, incrementing its internal reference count by 1. Both the original and newly-returnedRcmust bedispose()d before the resource will be disposed.Rc.dispose<T>(rc: Rc<T> | Weak<T>)Disposes of an
Rc, decrementing its internal reference count by 1, and cleaning up the underlying resource if the counter reaches 0.Weakreferences won't keep the underlying resource alive (seeRc.weak()), so they don't strictly need to be disposed, but they can still be passed toRc.dispose().After an
Rcor aWeakhas beendispose()d, it can no longer be used, and trying to access any of the exposed methods or properties from the wrapped resource will throw an error. Passing theRcto theRc.*methods (exceptinspect()) will also throw.Rc.weak<T>(rc: Rc<T>): Weak<T>Creates a
Weak<T>, known as a weak reference, from the originalRc, leaving the original intact. A weak reference won't keep the underlying resource from being disposed; that is, if all the strong references (Rc<T>s) are disposed, the resource will be cleaned up, even if there are stillWeak<T>references to the resource that haven't been disposed.A
Weak<T>can be "upgraded" to a strongRc<T>via theRc.upgrade()function.Rc.intoWeak<T>(rc: Rc<T>): Weak<T>Returns a new weak reference from the strong reference, just like
weak(), but it additionally disposes of the original, strong reference, which can no longer be used — effectively "turning" the strong reference into a weak one.If the reference being converted into a weak reference is the last strong reference for a resource, the resource will be immediately disposed, since the newly-created weak reference doesn't keep the resource alive.
Rc.upgrade<T>(weak: Weak<T>): Option<Rc<T>>Upgrading a
Weak<T>attempts to create a newRc<T>with the same underlying resource. If the resource has been cleaned up, due to all the strong references being disposed already, the function will returnNone. If the resource is still alive, it will returnSome<Rc<T>>.Rc.inspect<T>(rc: Rc<T> | Weak<T>): RcInfoReturns an object with information about the
Rc, useful for debugging.RcInfohas the following properties:id- the internal ID of theRcrefCount- the number of strong references to the resource left un-disosedweakCount- the number of weak references to the resource left un-disposeddisposed-trueif this specificRcorWeakinstance has been disposed,falseotherwiseinnerDisposed-trueif the wrapped resource has been disposed,falseotherwise
Deque<T>
import { Deque } from "@binarymuse/ts-stdlib"A Deque<T> is a double-ended queue that allows efficient insertion and removal at both ends. It's implemented as a doubly-linked list, making it ideal for situations where you need to:
- Add or remove elements from either end in O(1) time
- Maintain a queue that can grow in either direction
- Implement a work queue that can have items prioritized (added to front) or queued normally (added to back)
API
new Deque<T>()Creates a new empty deque
Deque.from<T>(iter: Iterable<T>): Deque<T>Creates a deque from the items in an
IterableDeque<T>.pushFront(item: T): voidAdds an item to the front of the deque
Deque<T>.pushBack(item: T): voidAdds an item to the back of the deque
Deque<T>.popFront(): Option<T>Removes and returns the item at the front of the deque, or
Noneif the deque is emptyDeque<T>.popBack(): Option<T>Removes and returns the item at the back of the deque, or
Noneif the deque is emptyDeque<T>.peekFront(): Option<T>Returns the item at the front of the deque without removing it, or
Noneif the deque is emptyDeque<T>.peekBack(): Option<T>Returns the item at the back of the deque without removing it, or
Noneif the deque is empty
The deque also implements the iterator protocol, allowing you to iterate through all items from front to back:
const deque = new Deque<number>();
deque.pushBack(1);
deque.pushBack(2);
deque.pushFront(0);
for (const item of deque) {
console.log(item); // Prints: 0, 1, 2
}