Skip to content

Alx-l/spacelift

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

50 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

space-lift "Lift your values into space for infinite possibilities"

Rich Array/Object wrapper, Option, Result monads

Design goals

  • 100% immutable, no magic, no overwhelming polymorphism or dynamic operators
  • Fun to use
  • Correctness and proper 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%
  • Use an OO style as it's the most convenient one without an |> operator directly in the language (fingers crossed)

How to use

Here's everything that can be imported from space-lift:

import lift, { Option, Some, None, Result, Ok, Err, update, deepUpdate, DELETE, range, Set, memoize, is, fromArrayLike, tuple } from 'space-lift'

lift is a generic function that can wrap an Array or Object and 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' // For bundlers who work more efficiently with ECMAScript modules

import 'space-lift/commonjs/all' // To use the legacy commonjs modules

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'
// or import _ 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' }
]

// This will result in an Array sorted by first name, then by last name
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
}

// Using reference equality on every arg
const memoizedMultiply = memoize(multiply)


const myObj = { id: 10 }

// Using memo keys
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) // some === Some(33)
const none = Option(null) // none === None

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) // Some(null | undefined) wouldn't compile.
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)
])
// some === Some([10, 20, 5])

const none = Option.all([
  Option(10),
  None,
  Option(5),
  null
])
// none === None

Option.isOption

Returns whether the passed instance in an Option, and refines its type

import { Option, Some } from 'space-lift'
Option.isOption(Some(33)) // true

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)
// some === Some(66)

flatMap

Maps the value contained in this Some to a new Option, else returns None.

const some = Option(33).flatMap(_ => Option(44))
// some === Some(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)
// some === Some(33)

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, // None
  count => count * 10 // Some
)

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))
// some === Some(33)

Misc

get

Some instances return their value, whereas None always return undefined. This method never throws.

const value = Some(33).get()
// value === 33

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)

// value === 33

forEach

Applies the given procedure to the option's value, if it is non empty.

Option(33).forEach(x => console.log(x))

exists

Returns whether this option is a Some with a value satisfying the predicate.

Option(30).exists(n => n > 10) // true

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)) // true

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')
]) // Err('nooo')

isOk

Returns whether this is an instance of Ok

import { Result, Ok, Err } from 'space-lift'

Ok(10).isOk() // true

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) // Ok(20)
Err(10).map(x => x * 2) // Err(10)

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) // Ok(10)
Err(10).mapError(x => x * 2) // Err(20)

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(20)
Ok(10).flatMap(x => Err(x * 2)) // Err(20)

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
) // 20

About

Functional utils for Arrays, Objects

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 88.2%
  • JavaScript 11.8%