npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

monocle-ts

v2.3.13

Published

A porting of scala monocle library to TypeScript

Downloads

1,524,770

Readme

build status dependency status npm downloads

Motivation

(Adapted from monocle site)

Modifying immutable nested object in JavaScript is verbose which makes code difficult to understand and reason about.

Let's have a look at some examples:

interface Street {
  num: number
  name: string
}
interface Address {
  city: string
  street: Street
}
interface Company {
  name: string
  address: Address
}
interface Employee {
  name: string
  company: Company
}

Let’s say we have an employee and we need to upper case the first character of his company street name. Here is how we could write it in vanilla JavaScript

const employee: Employee = {
  name: 'john',
  company: {
    name: 'awesome inc',
    address: {
      city: 'london',
      street: {
        num: 23,
        name: 'high street'
      }
    }
  }
}

const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1)

const employeeCapitalized = {
  ...employee,
  company: {
    ...employee.company,
    address: {
      ...employee.company.address,
      street: {
        ...employee.company.address.street,
        name: capitalize(employee.company.address.street.name)
      }
    }
  }
}

As we can see copy is not convenient to update nested objects because we need to repeat ourselves. Let's see what could we do with monocle-ts

import { Lens } from 'monocle-ts'

const company = Lens.fromProp<Employee>()('company')
const address = Lens.fromProp<Company>()('address')
const street = Lens.fromProp<Address>()('street')
const name = Lens.fromProp<Street>()('name')

compose takes two Lenses, one from A to B and another one from B to C and creates a third Lens from A to C. Therefore, after composing company, address, street and name, we obtain a Lens from Employee to string (the street name). Now we can use this Lens issued from the composition to modify the street name using the function capitalize

const capitalizeName = company.compose(address).compose(street).compose(name).modify(capitalize)

assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized)

You can use the fromPath API to avoid some boilerplate

import { Lens } from 'monocle-ts'

const name = Lens.fromPath<Employee>()(['company', 'address', 'street', 'name'])

const capitalizeName = name.modify(capitalize)

assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized) // true

Here modify lift a function string => string to a function Employee => Employee. It works but it would be clearer if we could zoom into the first character of a string with a Lens. However, we cannot write such a Lens because Lenses require the field they are directed at to be mandatory. In our case the first character of a string is optional as a string can be empty. So we need another abstraction that would be a sort of partial Lens, in monocle-ts it is called an Optional.

import { Optional } from 'monocle-ts'
import { some, none } from 'fp-ts/Option'

const firstLetterOptional = new Optional<string, string>(
  (s) => (s.length > 0 ? some(s[0]) : none),
  (a) => (s) => (s.length > 0 ? a + s.substring(1) : s)
)

const firstLetter = company.compose(address).compose(street).compose(name).asOptional().compose(firstLetterOptional)

assert.deepStrictEqual(firstLetter.modify((s) => s.toUpperCase())(employee), employeeCapitalized)

Similarly to compose for lenses, compose for optionals takes two Optionals, one from A to B and another from B to C and creates a third Optional from A to C. All Lenses can be seen as Optionals where the optional element to zoom into is always present, hence composing an Optional and a Lens always produces an Optional.

TypeScript compatibility

The stable version is tested against TypeScript 3.5.2, but should run with TypeScript 2.8.0+ too

| monocle-ts version | required typescript version | | -------------------- | ----------------------------- | | 2.0.x+ | 3.5+ | | 1.x+ | 2.8.0+ |

Note. If you are running < [email protected] you have to polyfill unknown.

You can use unknown-ts as a polyfill.

Documentation

Experimental modules (version 2.3+)

Experimental modules (*) are published in order to get early feedback from the community.

The experimental modules are independent and backward-incompatible with stable ones.

(*) A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.

From [email protected]+ you can use the following experimental modules:

  • Iso
  • Lens
  • Prism
  • Optional
  • Traversal
  • At
  • Ix

which implement the same features contained in index.ts but are pipe-based instead of class-based.

Here's the same examples with the new API

interface Street {
  num: number
  name: string
}
interface Address {
  city: string
  street: Street
}
interface Company {
  name: string
  address: Address
}
interface Employee {
  name: string
  company: Company
}

const employee: Employee = {
  name: 'john',
  company: {
    name: 'awesome inc',
    address: {
      city: 'london',
      street: {
        num: 23,
        name: 'high street'
      }
    }
  }
}

const capitalize = (s: string): string => s.substring(0, 1).toUpperCase() + s.substring(1)

const employeeCapitalized = {
  ...employee,
  company: {
    ...employee.company,
    address: {
      ...employee.company.address,
      street: {
        ...employee.company.address.street,
        name: capitalize(employee.company.address.street.name)
      }
    }
  }
}

import * as assert from 'assert'
import * as L from 'monocle-ts/Lens'
import { pipe } from 'fp-ts/function'

const capitalizeName = pipe(
  L.id<Employee>(),
  L.prop('company'),
  L.prop('address'),
  L.prop('street'),
  L.prop('name'),
  L.modify(capitalize)
)

assert.deepStrictEqual(capitalizeName(employee), employeeCapitalized)

import * as O from 'monocle-ts/Optional'
import { some, none } from 'fp-ts/Option'

const firstLetterOptional: O.Optional<string, string> = {
  getOption: (s) => (s.length > 0 ? some(s[0]) : none),
  set: (a) => (s) => (s.length > 0 ? a + s.substring(1) : s)
}

const firstLetter = pipe(
  L.id<Employee>(),
  L.prop('company'),
  L.prop('address'),
  L.prop('street'),
  L.prop('name'),
  L.composeOptional(firstLetterOptional)
)

assert.deepStrictEqual(
  pipe(
    firstLetter,
    O.modify((s) => s.toUpperCase())
  )(employee),
  employeeCapitalized
)