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

fluente

v4.0.0

Published

Make fluent objects like a boss!

Downloads

22

Readme

fluente

npm version JavaScript Style Guide Dependencies Status Actions Status Coverage Status

Make fluent objects like a boss!

Features

  • Zero dependencies: small footprint.
  • Protected state
  • Selective mutability: choose between immutable objects or a classy object.
  • Undo/Redo out of the box
  • TypeScript support

Installation

npm install --save fluente

fluente(options)

Returns the build object.

  • options <Object>
    • state <Object> Initial state.
    • [constants] <Object> Constant values.
    • [getters] <Object> Value getters.
    • [fluents] <Object> Fluent methods.
    • [mappers] <Object> Map (normal) methods.
    • [mutable] <Boolean> See mutability.
    • [historySize] <Number> See undo and redo.
    • [produce] <Function> See state manipulation.
  • Returns: <Object>

Quickstart

The easiest way to create a fluent API is by returning the instance itself inside a class method.

class Calculator {
  constructor (value = 0) {
    this.value = value
  }

  add (value) {
    this.value += value
    return this // Fluent API
  }

  valueOf () {
    return this.value
  }
}

const calculator = new Calculator(0)

const value = calculator
  .add(2)
  .add(4)
  .add(8)
  .add(16)
  .valueOf()

console.log(value) // 30

Fluente provides a different way to declare fluent objects.

const fluente = require('fluente')

const symCalculator = Symbol('my_calculator')

function valueOf (state) {
  return state.value
}

function add (state, value) {
  return {
    value: valueOf(state) + value
  }
}

function subtract (state, value) {
  return {
    value: valueOf(state) - value
  }
}

function multiply (state, value) {
  return {
    value: valueOf(state) * value
  }
}

function divide (state, value) {
  return {
    value: valueOf(state) / value
  }
}

function createCalculator (value = 0) {
  return fluente({
    // Enable state history (undo/redo)
    historySize: 8,
    // Initial state
    state: {
      value
    },
    // Object constants
    constants: {
      [symCalculator]: true
    },
    // Value getters
    getters: {
      value: valueOf,
      integer: state => Number.isInteger(valueOf(state))
    },
    // Fluent methods
    fluents: {
      add,
      subtract,
      multiply,
      divide
    },
    // Map (normal) methods
    mappers: {
      valueOf
    }
  })
}

const zero = createCalculator(0)

// Read constant value
if (zero[symCalculator] !== true) {
  throw new Error('Expected calculator')
}

// Read value getters
console.log(zero.value, zero.integer) // 0 true

// Use fluent method
const pi = zero.add(Math.PI)

console.log(pi.value, pi.integer) // 3.141592653589793 false

const value = zero
  .add(2)
  .subtract(8)
  .multiply(-1)
  .divide(2)
  .undo(2) // Undo 2 mutations: divide(2) and multiply(-1)
  .redo(1) // Redo 1 mutation: multiply(-1)
  .valueOf() // Use map method

console.log(value) // 6

Like a constructor, Fluente returns an object. You can still declare methods, constants, and getters, but with a different signature. Functions do not use the this keyword to access the state. Instead, It's always passed as the first argument. Another key difference is how fluent methods mutate the state: by returning an object that contains the changed values.

These rules abstract the methods (and getters) definition and the state mutation, unlocking some cool features like selective mutability, state protection, and undo/redo.

Mutability

By default, all objects build by Fluente are immutable (all fluent methods will return a new object containing the updated state). You can retrieve mutable objects using the mutable option.

const fluente = require('fluente')

function createCalculator (initialValue = 0) {
  return fluente({
    mutable: true, // Request mutable object
    state: {
      value: initialValue
    },
    fluents: {
      add (state, value) {
        return {
          value: state.value + value
        }
      }
    },
    mappers: {
      valueOf (state) {
        return state.value
      }
    }
  })
}

const calculator = createCalculator(0)
calculator.add(40)
calculator.add(2)
console.log(calculator.valueOf()) // 42

Undo and Redo

An isolated state enables easy undo-redo implementation. The current state represents the present moment. Applying a mutation move the present into the past, and set a new present state. Undoing a mutation will restore a state from the past, and move the present state into the future. Redoing a mutation will do the opposite. A more detailed example is available in this Redux article.

Fluente automatically injects undo-redo functions. Both functions optionally accept the number of mutations to apply, defaulting to 1 mutation. Infinity is accepted, and means "redo/undo all the mutations available".

The historySize option controls the max number of mutations remembered by Fluente. Set the historySize option to a positive number to enable this feature.

State manipulation

By default, all fluent functions need to return an updated state, even partially, to let Fluente know what is changed. Plus state needs to be treated as immutable. Those rules are necessary to ensure correct state isolation. The subtract function in the next example is updating the state's value property by returning an object containing that property.

function subtract (state, value) {
  return {
    value: state.value - value
  }
}

However, other state manipulation systems are viable through a custom Producer. A Producer is a function that accepts both state object and map function. Its purpose is to handle any map mutations applied against the state and then return a new and fully updated state.

const fluente = require('fluente')

function mySimpleProducer (oldState, mapper) {
  // Clone current state
  const newState = Object.assign({}, oldState)
  // Run state mapper (may override some root properties now)
  mapper(newState)
  // Return updated state
  return newState
}

function createCalculator (initialValue = 0) {
  return fluente({
    produce: mySimpleProducer, // Use a custom producer
    state: {
      value: initialValue
    },
    fluents: {
      add (state, value) {
        state.value += value // Direct state manipulation
      }
    },
    mappers: {
      valueOf (state) {
        return state.value
      }
    }
  })
}

The easiest and safest way to support direct state manipulation is to use Immer's produce function.

const fluente = require('fluente')
const { produce } = require('immer')

function createCalculator (initialValue = 0) {
  return fluente({
    produce, // Use Immer to handle state changes
    state: {
      value: initialValue
    },
    fluents: {
      add (state, value) {
        state.value += value // Direct state manipulation
      }
    },
    mappers: {
      valueOf (state) {
        return state.value
      }
    },
  })
}

Immutable.js is also supported.

const fluente = require('fluente')
const { Map } = require('immutable')

function valueOf (state) {
  return state.get('value')
}

function add (state, value) {
  return state.set('value', valueOf(state) + value)
}

function createCalculator (initialValue = 0) {
  return fluente({
    // Direct mapping (fluent mappers need to return the updated state)
    produce: (state, mapper) => mapper(state),
    // Initial state (init with Immutable.js)
    state: Map({
      value: initialValue
    }),
    fluents: {
      add
    },
    mappers: {
      valueOf
    }
  })
}