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

affection

v0.0.1

Published

Declarative side-effects

Downloads

13

Readme

Affection

Declarative side-effects

npm install affection

npm Build Status Greenkeeper badge

Affection is a library for describing side-effects as plain data and providing composition utilities. This project aims to improve on similar libraries by not using generators.

Generators make testing difficult in that:

  • They can have internal state.
  • Each segment of the function cannot be tested in isolation.
  • Each segment of the function can only be reach after the segments before it.
  • Generators are awkward. Conversing with a generator with next() isn't as simple as function calling.
  • Composition of generators is harder than functions inherently.

So Affection is all about functions, with the goals:

  • Improve testability through the use of pure functions.
  • Improve code reuse through la-a-carte composition of side-effects.

Let's see how we do.

Examples

This first example does not use any composition.

import { run, call, callMethod } from 'affection'

const getJSON = url => [
  call(fetch, url),
  resp => [callMethod(resp, 'json')]
]

async function main () {
  const payload = await run(getJSON('http://example.com'))
  console.log(payload)
}

This second example does the same as the first. Here we are using the composition utilities.

import { step, runStep, batchSteps, call, callMethod } from 'affection'

const fetchUrl = url => call(fetch, [url])
const readJSON = resp => callMethod(resp, 'json')
const getJSON = batchSteps([fetchUrl, readJSON].map(step))

async function main () {
  const payload = await runStep(getJSON, 'http://example.com')
  console.log(payload)
}

Documentation

The package contains the following:

Effects

See defaultHandle for adding more.

Execution
Composition

call

call(func: function, args: Array<any>, context: any): Effect

Describes a function call of func.apply(context, args).

callMethod

callMethod(obj: any, method: String, args: Array<any>): Effect

Describes a method call of obj[method].apply(obj, args)

all

all(effects: Array<Effect>): Effect

Describes combining effects. Like Promise.all.

race

race(effects: Array<Effect>): Effect

Describes racing effects. Like Promise.race.

itself

itself(value: any): Effect

Describes a value. This is an identity function for Effects.

defaultHandle

defaultHandle(effect: Effect, handle: function): any

Performs the action described by a particular effect. defaultHandle provides the handling for the effects included in Affection. To add more, create a new handle that wraps defaultHandle and pass that to run.

For example, say we want to add a timeout effect:

import { defaultHandle } from 'affection'

export function timeout (duration) {
  return { type: 'timeout', duration }
}

export function myHandle (effect, handle) {
  if (effect.type === 'timeout') {
    return new Promise(resolve => setTimeout(resolve, effect.duration))
  }
  return defaultHandle(effect, handle)
}

// Later...

async function main () {
  await run([timeout(1000)], myHandler)
  // Will have waited a second
}

run

run(plan: [Effect, function?], handle: function = defaultHandle): any

Executes a plan. A plan is an array where the first element is an Effect to be handled using handle and the second element is a function to call with the result of the Effect. If the function is not provided, execution terminates and the result is returned.

step

step(makeEffect: any -> Effect): Step

Creates a step. A step is a means of encapsulating an effect without needing a plan (as described by run).

This is hard to understand without an understanding of how run works. The run function is recursively executing plans until there is nothing more to do. A step is a way of saying, "Execute this effect; we might be done, might not." There could be 5 more effects to run or it's the end result; the step doesn't need to know.

This is for code reuse: effects should be decoupled from their consumers.

mapStep

mapStep(step: Step, transform: function): Step

Creates a new step which will return the result of transform called with the input to the step makeEffect and the result of the Effect.

This is good for passing along context without mucking up simple steps. For example, we are building a dictionary of the most used word for each country. We want to retain the country we are querying about in the result.

const getMostUsedWordInCountry = country => call(MyAPI, country)
const countryWordStep = step(getMostUsedWordInCountry)
const getCountryWord = mapStep(countryWordStep, (result, country) => ({ country, word: result }))

runStep(getCountryWord, 'Canada').then(result => {
  console.log(result)
  // => { country: 'Canada', word: 'Sorry' }
})

batchSteps

batchSteps(steps: Array<Step>): Step

Creates a new step which will call each step passing the result of first step to the next and so on.

runStep

runStep(step: Step, input: any, handle: function = defaultHandle): any

Executes a step with a given input. Uses run so handle works in the same way.