# chainify-api

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

Latest version **0.0.3** (published 2019-08-27) · ISC license · 0 weekly downloads

## Install

```sh
npm install chainify-api
pnpm add chainify-api
yarn add chainify-api
bun add chainify-api
```

## Health

**Score 15/100 (F)** — status: abandoned.

Positive: no vulnerabilities.

Warnings: low downloads; no types; no esm support; pre 1.0.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.0.3 |
| Published | 2019-08-27 |
| First published | 2019-08-09 |
| Weekly downloads | 0 |
| License | ISC |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Unpacked size | 46.7 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Author | gshah |
| Maintainers | gshah30 |

## Links

- npm: https://www.npmjs.com/package/chainify-api
- Repository: https://github.com/gshah30/chainify.js
- Homepage: https://github.com/gshah30/chainify.js#readme
- Issues: https://github.com/gshah30/chainify.js/issues
- npm.io page: https://npm.io/package/chainify-api

## Recent versions

- 0.0.3 (latest) — 2019-08-27
- 0.0.2 — 2019-08-21
- 1.0.0 — 2019-08-09

## 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
```javascript
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
```javascript
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
```javascript
window.addEventListener( 'mousemove', updateMouse, false )
window.requestAnimationFrame(render)
```

##### Chainified API
```javascript
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
```javascript
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
```javascript
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_.

```javascript
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
```javascript
const video = document.getElementById("video")
video.pause()
const metadata = { width: video.videoWidth, height: video.videoHeight }
```
##### Chainified API
```javascript
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)

---
_Source: https://npm.io/package/chainify-api · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
