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

chainify-api

v0.0.3

Published

A small utility library to convert synchronous imperative API into functional chainable API

Downloads

6

Readme

chainify.js [WIP]

A simple utility library that converts old-style imperative API into functional/chainable style API. A functional/chainable style goes well with the latest features of EcmaScript.

If you are forced to work with an imperative API and crave a functional/chainable style, this is the library for you. It creates a very light-weight wrapper (less than 150 lines unminified, has no dependencies) over an imperative API.

Sample usage on Three.js's imperative API.

Original imperative API
var raycaster = new THREE.Raycaster()
raycaster.setFromCamera( mouse, camera )
var intersects = raycaster.intersectObjects( scene.children )

for ( var i = 0; i < intersects.length; i++ ) {
	intersects[ i ].object.material.color.set( 0xff0000 )
}
Chainified API
const { Raycaster } = chainify(THREE)	// do this once at the top of the file

new Raycaster()
	.setFromCamera( mouse, camera )
	.intersectObjects( scene.children )
	.forEach(i => i.object.material.color.set( 0xff0000 ))

Sample usage on DOM's imperative API.

Original imperative API
window.addEventListener( 'mousemove', updateMouse, false )
window.requestAnimationFrame(render)
Chainified API
chainify(window)
  .addEventListener("mousemove", updateMouse, false)
  .renderAnimationFrame(render)

Use cases - Features supported

1. Converting unchainable member functions into chainable

As an example, consider the THREE.WebGLRenderer constructor provide by the library Three.js, used to create a WebGL-based renderer. This is how the original constrcutor is meant to be used.

Original imperative API
var renderer = new THREE.WebGLRenderer()

renderer.setClearColor(new Color(0xEEEEEE, 1.0))
renderer.setSize( window.innerWidth, window.innerHeight )
renderer.shadowMap.enabled = true

renderer.render(scene, camera)

var domEl = renderer.domElement

If you pass THREE to chainify, you can use the resulting WebGLRenderer constructor like shown below.

Chainified API
const { WebGLRenderer } = chainify(THREE) 	// do this once at the top of the file

const domEl = new WebGLRenderer()
		    .setClearColor(new Color(0xEEEEEE, 1.0))
		    .setSize(window.innerWidth, window.innerHeight)
		    .manage(r => r.shadowMap.enabled = true)
		    .render(scene, camera)
		    .domElement

The original setClearColor method returns undefined, so it is unchainable. But the setClearColor method in the chainified code example reutrns an instance of WebGLRenderer, so now setSize method can be directly chained to it. If setClearColor had returned a value of type number or boolean, the chain could not be continued.

Note that if setClearColor had returns an object, array, map or string, then ONLY that object's, array's, map's or string's methods could come next in chain. But since, setClearColor returns undefined, chainify ensures that it return the instance of WebGLRenderer, so WebGLRenderer's setSize method could be used in the chain.

As another example below, new TextureLoader() returns a TextureLoader instance, but since .load("media/pano.jpg") returns a Texture instance, the next function in the chain has to be a member function of Texture (and not TextureLoader).

Now updateMatirx() returns null, so the manage function receives the last context (return value of load viz. Texture instance). Thus, t argument of manage is an instance of Texture.

const { TextureLoader } = chainify(THREE)

background = new TextureLoader()
	      .load("media/pano.jpg")
	      .updateMatrix()
	      .manage(t => {
		t.wrapS = RepeatWrapping
		t.repeat.x = -1
	      })

2. manage function

Chainifying an object adds manage function to it. manage can be used to operate on the object without breaking the function chain.

In the previous two code examples, manage is used

  1. to enable the shadowMap on the renderer in the middle of the chain
  2. update texture properties at the end of the chain.

So, manage itself can be further chained if required because manage always returns its context (this).

3. map function

Chainifying an object adds map function to it. While manage always returns its context (object on which it was called), map returns the value returned by the function passed as argument to map. Thus, while manage maintains the context of the chain, map can be used to change the context of the chain without breaking it.

An example using map is given below.

Original imperative API
const video = document.getElementById("video")
video.pause()
const metadata = { width: video.videoWidth, height: video.videoHeight }
Chainified API
const cdocument = chainify(document) // do this once at the top of the file, then use cdocument everywhere

const metadata = cdocument.getElementById("video")
			  .pause()
			  .map(v => ({ width: v.videoWidth, height: v.videoheight }))

Nore that .pause() could be placed in the chain exactly because it returns undefined. Had it returned anything other than undefined or null, then the map would get the return value of pause() (and not the video DOM node) as v.

TODO

4. Array-like objects returned by one function are treated as Arrays in the next function in the chain

5. Add support for conditional chaining (functional replacement for imperative if-else)