2.0.0 ā€¢ Published 3 years ago

performative-ts v2.0.0

Weekly downloads
3
License
MIT
Repository
-
Last release
3 years ago

Performative-ts

Composable, minimalist algebraic effects inspired, side-effect management library

npm install --save performative-ts
yarn add performative-ts

Use cases

šŸ‘‰ Providing a theme:

import { perform, withHandler } from 'performative-ts'

const getThemeEff = 'getThemeEff'

function Button({ label }) {
  const theme = perform(getThemeEff)

  return `
    <button style="color: ${theme.primaryColor}">
      ${label}
    </button>
  `
}

function App() {
  return Button({ label: 'Hello' })
}

console.log(
  withHandler(
    {
      [getThemeEff]() {
        return { primaryColor: 'paleblue' }
      },
    },
    () => App(),
  ),
)

šŸ‘‰ Add a logger:

import { perform, withHandler } from 'performative-ts'

// --- library

const logEff = 'logEff'

function log(info) {
  perform(logEff, info)
}

function withLogger(logFunction, wrappedFunc) {
  return withHandler({ [logEff]: logFunction }, wrappedFunc)
}

// --- userland

function Button({ label }) {
  log('rendering Button')

  return `
    <button style="color: ${theme.primaryColor}">
      ${label}
    </button>
  `
}

function App() {
  log('rendering App')

  return withLogger(
    (info) => log(`[App]: ${info}`),
    () => Button({ label: 'Hello' }),
  )
}

withLogger((info) => console.log(info), App)
// rendering Button
// [App]: rendering App

See examples for more

Best practices

šŸ‘‰ Rename your functions to add meaning:

import { perform, withHandler } from 'performative-ts'

const getThemeEff = 'getThemeEff'

export function useTheme() {
  return perform(getThemeEff)
}

export function provideTheme(theme, Component) {
  return withHandler({ [getThemeEff]: () => theme }, Component)
}

šŸ‘‰ Use a symbol to prevent collisions

const getThemeEff = Symbol('getThemeEff')

šŸ‘‰ Compose your handlers

import { compose } from 'your-favorite-lib'
import { bindHandler, perform } from 'performative-ts'

function sayName(name) {
  console.log(`Hello ${name || perform('getName')}`)
}

const composed = compose(
  bindHandler(['getName', () => 'John Snow']),
  bindHandler(['getName', () => perform('getName').toUpperCase()]),
)(sayName)

composed('Arya') // Hello Arya
composed() // Hello JOHN SNOW

šŸ‘‰ Capture current frame for later execution

import { captureFrame, bindFrame, withFrame } from 'performative-ts/frame'

const frame = captureFrame()

// later
const boundListener = bindFrame(frame, listener)
button.addEventListener('click', boundListener)

setTimeout(() => {
  withFrame(frame, () => computation())
}, 1000)

šŸ‘‰ Use with typescript

import { perform, withHandler } from 'performative-ts'
import type { EffectName } from 'performative-ts'

export type Theme = {
  primaryColor: string
}

const getThemeEff: EffectName<() => Theme> = Symbol('getThemeEff')

export function useTheme(): Theme {
  return perform(getThemeEff)
}

export function provideTheme<C extends Function>(theme: Theme, Component: C): C {
  // for better typings with typescript, the library allow to provide handlers as tuples
  return withHandler([getThemeEff, () => theme], Component)
}

API reference

perform

perform(effectName, ...args): effectResult or throws

Performs a effect and return its result. Throws if the corresponding effect handler is not declared during its execution.

performAndFailSilently(effectName, ...args): effectResult | undefined

Performs a effect safely and return its result. Returns undefined if the corresponding effect handler is not declared during its execution.

withHandler

withHandler(handler, computation): computationResult

Calls a computation (a function taking no arguments) with the provided effect handler.

withHandler(...handlerTuples, computation): computationResult

Calls a computation (a function taking no arguments) with the provided effect handler functions.

bindHandler

bindHandler(handler, func): func

Binds an effect handler to the provided function.

bindHandler(...handlerTuples, func): func

Binds effect handler functions to the provided function.

curried bindHandler

bindHandler(handler): func => func

Returns a function that will bind the given effect handler to another function.

bindHandler(...handlerTuples): func => func

Returns a function that will bind the given effect handler functions to another function.

frames

captureFrame(): currentFrame

Captures current execution frame

withFrame(frame, computation): computationResult

Calls a computation (a function taking no arguments) with the provided execution frame.

bindFrame(frame, func): func

Binds an execution frame to the provided function.

Types

EffectName

An unique effect identifier. Can be a string, number or symbol.

Handler

An object consisting of EffectNames as keys and EffectHandlerFunctions as values.

HandlerTuple

A tuple consisting of EffectName as first element and EffectHandlerFunction as second element.

Frame

An object keeping the reference to its own effect handlers and to its parent frame.