space-lift
"Lift your values into space for infinite possibilities"
Rich Array, Object, Map, Set wrapper
Design goals
- 100% immutable, no magic, no overwhelming polymorphism or dynamic operators
- Fun to use
- Correctness and first-class typescript typings
- Tiny and performant
- Small set of functions, configurable with lambdas
- Cover 95% of frontend data transformation needs without becoming a bloated lib just to cover the remaining 5%
How to use
Here's everything that can be imported from space-lift
:
import { lift, update, range, is, createUnion, createEnum, identity, noop } from 'space-lift'
lift
is the main attraction and is used to wrap an Array, Object, Map or Set to give it extra functionalities
update
, deepUpdate
, DELETE
come from immupdate
Option
, Some
, None
are used to work with optional values
Result
, Ok
, Err
are used to work with computation that may fail
range
is a factory function for Arrays of numbers
Set
is a factory function for objects acting as Sets
is
is a helper used to determine if an instance is of a particular type (e.g is.array([]) === true
)
fromArrayLike
converts an Array like object (FileList, HTMLCollection, etc) into a standard Array
tuple
creates a properly typed tuple
By default, the library provides no operators to the Wrapped Arrays/Objects at all. You get to choose what to import.
The fastest way is to install everything in a single import (probably in your main file):
import 'space-lift/es/all'
import 'space-lift/commonjs/all'
But you can also choose exactly what to import:
import 'space-lift/es/array/map'
import 'space-lift/es/object/mapValues'
Note: When using typescript, don't forget to enable (at least) these two flags for better type-safety: noImplicitAny
, strictNullChecks
Examples
Update an object inside an Array
import lift, { update } from 'space-lift'
const people = [
{ id: 1, name: 'jon' },
{ id: 2, name: 'sarah' },
{ id: 3, name: 'nina' }
]
const updatedPeople = lift(people)
.findIndex(p => p.id === 2)
.map(index => lift(people).updateAt(index, person => update(person, { name: 'Nick' })))
.getOrElse(people)
Sort on two fields
import lift from 'space-lift'
const people = [
{ first: 'jon', last: 'haggis' },
{ first: 'sarah', last: 'john' },
{ first: 'nina', last: 'pedro' }
]
const sortedPeople = lift(people)
.sort({ by: p => p.last })
.sort({ by: p => p.first })
.value()
Auto unwrap
Most of the time, you will have to call .value()
to read your value back (or .get()
for options, although it is recommended to use map
/getOrElse
/etc instead)
Because it's distracting to write .value()
more than once per chain, some operators will automatically unwrap values returned from their iterators (like Promise->then).
These operators are:
Option.map
Array.map
Array.flatMap
Array.updateAt
transform
API
Array
TODO: Detail and examples
Object
TODO: Detail and examples
Function
memoize
import { memoize } from 'space-lift'
function multiply(a: number, b: number) {
return a * b
}
const memoizedMultiply = memoize(multiply)
const myObj = { id: 10 }
const memoized = memoize(
(a: number, b: typeof myObj): {} => ({ x: a, y: b }),
{ key: (a, b) => `${a}_${b.id}` }
)
Option
Creating an Option
Option(x)
Creates an Option from a value.
If the value is null or undefined, it will create a None, else a Some.
const some = Option(33)
const none = Option(null)
If you already know the value is defined for sure (not nullable) or not, you can create a Some
or None
directly:
const some = Some(33)
const none = None
Option.all([...optionsOrValues])
Creates a new Option holding the tuple of all the values contained in the passed array if they were all Some or non null/undefined values,
else returns None
const some = Option.all([
Option(10),
20,
Option(5)
])
const none = Option.all([
Option(10),
None,
Option(5),
null
])
Option.isOption
Returns whether the passed instance in an Option, and refines its type
import { Option, Some } from 'space-lift'
Option.isOption(Some(33))
None
The Option constant representing no value.
import { None } from 'space-lift'
Transforming an Option
map
Maps the value contained in this Some, else returns None.
Depending on the map function return value, a Some could be tranformed into a None, as a Some is guaranteed to never contain a null or undefined value.
const some = Option(33).map(x => x * 2)
flatMap
Maps the value contained in this Some to a new Option, else returns None.
const some = Option(33).flatMap(_ => Option(44))
filter
If this Option is a Some and the predicate returns true, keep that Some.
In all other cases, return None.
const some = Option(33).filter(x => x > 32)
fold
Applies the first function if this is a None, else applies the second function.
Note: Since this method creates 2 functions everytime it runs, don't use in tight loops; use isDefined() instead.
const count = Option(10).fold(
() => 100,
count => count * 10
)
toArray
Transforms this option into an Array or either 1 or 0 element.
orElse
Returns this Option unless it's a None, in which case the provided alternative is returned.
const some = Option(null).orElse(() => Option(33))
Misc
get
Some
instances return their value, whereas None
always return undefined
.
This method never throws.
const value = Some(33).get()
isDefined
Returns whether this Option has a defined value (i.e, it's a Some(value))
Note: this refines the type of the Option to be a Some so it's guaranteed its value is not null/undefined.
getOrElse
Returns this Option's value if it's a Some, else return the provided alternative
const value = Option(undefined).getOrElse(33)
forEach
Applies the given procedure to the option's value, if it is non empty.
Option(33).forEach(x => console.log(x))
contains
Returns whether this option is a Some that contain a specific value, using ===
Option(30).contains(30)
exists
Returns whether this option is a Some with a value satisfying the predicate.
Option(30).exists(n => n > 10)
Result
A Result
is the result of a computation that may fail. An Ok
represents a successful computation, while an Err
represent the error case.
Importing Result
Here's everything that can be imported to use Results:
import { Result, Ok, Err } from 'space-lift'
const ok = Ok(10)
const err = Err('oops')
Result.isResult
Returns whether this instance is a Result (either an Ok or a Err) and refines its type
import { Result, Ok } from 'space-lift'
Result.isResult(Ok(10))
Result.all
Creates a new Ok Result holding the tuple of all the values contained in the passed array if they were all Ok,
else returns the first encountered Err.
import { Result, Ok, Err } from 'space-lift'
const result = Result.all([
Ok(20),
Err('nooo'),
Ok(200),
Err('oops')
])
isOk
Returns whether this is an instance of Ok
import { Result, Ok, Err } from 'space-lift'
Ok(10).isOk()
map
Maps the value contained in this Result if it's an Ok, else propagates the Error.
import { Result, Ok, Err } from 'space-lift'
Ok(10).map(x => x * 2)
Err(10).map(x => x * 2)
mapError
Maps the Error contained in this Result if it's an Err, else propagates the Ok.
import { Result, Ok, Err } from 'space-lift'
Ok(10).mapError(x => x * 2)
Err(10).mapError(x => x * 2)
flatMap
Maps the value contained in this Result with another Result if it's an Ok, else propagates the Error.
Note: It is allowed to return a Result with a different Error type.
import { Result, Ok, Err } from 'space-lift'
Ok(10).flatMap(x => Ok(x * 2))
Ok(10).flatMap(x => Err(x * 2))
fold
Applies the first function if this is an Err, else applies the second function.
Note: Don't use in tight loops; use isOk() instead.
import { Result, Ok, Err } from 'space-lift'
Ok(10).fold(
err => console.error(err),
num => num * 2
)
createEnum
Creates a type safe string enumeration from a list of strings, providing:
the list of all possible values, an object with all enum keys and the derived type of the enum in a single declaration.
import { createEnum } from 'space-lift/es/enum'
const enumeration = createEnum('green', 'orange', 'red')
type StopLightColor = typeof enumeration.T
enumeration.values
const color = enumeration.enum
const redish: StopLightColor = 'red'
const greenish: StopLightColor = color.green
const orange: 'orange' = color.orange
orange
createUnion
Creates a type-safe union, providing: derived types, factories and type-guards in a single declaration.
import { createUnion, empty } from 'space-lift/es/union'
const union = createUnion({
green: empty,
orange: empty,
red: empty,
broken: (cause: string) => ({ cause })
})
const is = union.is
const stopLight = union.factories
type StopLight = typeof union.T
const orange: StopLight = stopLight.orange()
type Green = typeof stopLight.green.T
const green: Green = stopLight.green()
const broken = stopLight.broken('oops')
stopLight.orange()
stopLight.broken('oops')
if (is('broken')(broken)) {
broken.cause
}
if (is('broken')(green)) {
}