piconuro
v1.0.1
Published
Pure functional reactive-store pattern for minimalists
Downloads
7
Readme
pure | 📦
code style | standard
piconuro
A functional approach to the reactive-store pattern delivering indiscriminate minimalism.
Nuro let's you build reactive pathways using functions.
It's a part of picostack created to develop famework-agnostic app-kernels/blockends that can be easily unit-tested and run in both node and browser.
Aside from having a smaller API it offers similar workflows as svelte/stores or react/useState but without dependencies.
The Contract
- A neuron is a function that takes a callback as input and returns an
unsubscribe
function. - The callback is invoked synchroneously once during subscribe
- The callback is invoked every time the neuron fires.
- After
unsubscribe
is called, the callback is nolonger invoked.
Use
$ npm install piconuro
import { init } = require('piconuro')
const $n = init('Hello')
// Subscribe
const unsubscribe = $n(value => console.log(value))
// Unsubscribe
unsubscribe()
With react:
Add this custom hook to your app:
import { useState, useEffect } from 'react'
import { settle, get } from 'piconuro'
// CustomHook
export function useNuro ($n) {
const [value, set] = useState(get($n))
// ensures unsub on unmount
useEffect(() => settle($n)(v => set(v)), [set])
return value
}
With svelte:
Add this neuron to readable-store adapter to your app:
// N(e)uro -> svelte adapter
export function svlt (neuron, dbg) {
return readable(null, set =>
!dbg
? neuron(set)
: nfo(neuron, dbg)(set)
)
}
With Vanilla html/js
$n(value => document.getElementById('value-div').innerHTML = value)
API
We prepend all neurons with a
$
-sign to avoid confusing them with values.
$n
is an imaginary neuron
If you're having issues and need to inspect your neural path
don't fret, just insert an nfo()
neuron in your pathway.
write (value) // => [$n, setter]
A writable neuron, easiest way to start a new path.
const [$name, setName] = writable('')
setName('bobby')
init (value, $n)
Init is an immutable neuron that fires the initial value once. Use it to create placeholders or build new pathways.
Example:
const $age = init(28)
const $postType = init('image')
const $friends = init([])
If optional $n
was passed, then init will fire a second time
with the value of $n
resolves
Example of a neuron that fires true
, and after
1 second delay notifies all existing and future subscribers with false
:
const $loading = init(
true,
when(new Promise(resolve =>
setTimeout(() => resolve(false), 1000)
))
)
mute ($n, fn)
Short for MUTatE
, mutates input values
using return value of provided fn
-function.
Fires on immediate or async result.
If fn
is an async function or you return a promise.
Then the output will fire when the value resolves.
Does not fire placeholders, prepend with init() if you need an immediate sync value.
Sync example:
[$x, setX] = write(0)
const $squared = mute($x, x => x * x)
setX(2) // $squared fires: 4
setX(8) // $squared fires: 64
Async example that fetch comments for a post whenever $postId
changes:
[$postId, setPostId] => write(77)
$comments => init(
[], // Empty array as placeholder
mute($postId, async id => {
const resp = await fetch(`https://dinosaurTech/api/posts/${id}`)
return JSON.parse(resp.data)
})
)
// Connect $comments to console.log
$comments(value => console.log('Comments: ', value))
setPostId(32) // causes mute to fire a second time with a different set.
combine (...neurons)
A neuron that combines the output of multiple neurons into a single output. The first output is held until all neurons have fired once.
There are two ways of using it.
Passing a list of neurons:
const $n = combine($dogAge, $numberofCats, $year)
$n(console.log) // Outputs an array
// => [13, 2, 2022]
Or passing a map of neurons:
const $n = combine({
age: $dogAge,
cats: $numberofCats,
year: $year
})
$n(console.log) // Outputs an object
// => { age: 13, cats: 2, year: 2022 }
memo ($n)
One to many neuron (opposite of combine) Memo is similar to an EventEmitter that keeps a list of subscribers/connections and remembers the last fired value so new subscribers do not re-trigger the entire pathway.
Use memo to tradeoff computation for memory if you have multiple dynamic subscribers.
gate ($n, shallow = false)
Gate dirty-checks values that passes through it, preventing the path from firing same value twice.
Example:
const [$x, setX] = write(0)
const $n = gate($x) // $n is gated version of $x
$n(console.log) // pipe $n to console.log
setX(2) // logs '2'
setX(3) // logs '3'
setX(3) // Nothing fired
setX(0) // logs '0'
nfo ($n, name)
This neuron logs all values passes through it. It's a very useful tool to inspect your path showing when subscribers connect and disconnect.
const { write, gate, mute, nfo } = require('.')
// or: import { write, gate, mute, nfo } from 'piconuro'
const [$birthday, setBirthday] = write(new Date())
const $age = nfo(
mute($birthday, dob => new Date().getYear() - dob.getYear()),
'Age'
)
const $ageCheck = gate(
mute($age, age => age > 13)
)
const $n = nfo($ageCheck, 'Check')
const unsub = $n(v => console.log('Final Output', v)) // connect dummy
setBirthday(new Date('1984-03-24'))
setBirthday(new Date('1999-09-10'))
setBirthday(new Date('2018-07-01'))
unsub() // disconnect dummy
when (promise)
Experimental promise to async neuron converter.
fires once when promise resolves or symbol ERROR
if promise rejects.
settle (neuron, debounceMs = 10, risingEdge = false)
Buffers a signal and outputs last value
⚠️WARNING⚠️ Use with care, this neuron introduces unchecked asyncronity into your path causing racing conditions along the way. Only use is for buffering final outputs to silly frameworks such as react that render with a built-in rising-edge buffer. 🤦
Helpers
get (neuron) // => value
Gets the synchroneous value of a neuron
const $n = init('Hello')
const v = get($n)
console.log(v) // => 'Hello'
next (neuron, skip = 1, inspect = false) // => Promise
Async version of get() that skip
-s amount of values before resolving.
Imagine a neuron value stream to be an array:
['a', 'b', 'c']
setting skip
to 0 will return 'a', set it to 2 to get 'c'
const value = await next($n)
until (neuron, condition, timeout = -1) // => Promise
Sibling of next(), an async utility getter that resolves value when 'condition'-function returns truthy.
const hiFive = await until($clock, time => t > 5)
isSync (neuron, ms = 100) // => Promise
Utility method that tests a neuron for synchronity. returns true if and only if the neuron fired once immediately throws error if the neuron did not fire within the grace period.
Designed to make unit-testing easier
import { $users } from './blockend.js'
testOk(await isSync($users), 'Users output has a placeholder')
* iter (neuron, nValues = 5)
Converts a neuron into an async iterator:
- {neuron} Neuron to generate from
- {nValues} Number of values to generate, 1 will yield 1 value, setting it to -1 will cause an eternal loop.
$clock = ... // imaginary clock neuron that fires once every second.
for await (const time of iter(clock)) {
console.log('The time is:', time)
}
Donations
| __ \ Help Wanted! | | | | | |
| | | | ___ ___ ___ _ __ | |_| | __ _| |__ ___ ___ ___
| | | |/ _ \/ __/ _ \ '_ \| __| | / _` | '_ \/ __| / __|/ _ \
| |__| | __/ (_| __/ | | | |_| |___| (_| | |_) \__ \_\__ \ __/
|_____/ \___|\___\___|_| |_|\__|______\__,_|_.__/|___(_)___/\___|
If you're reading this it means that the docs are missing or in a bad state.
Writing and maintaining friendly and useful documentation takes
effort and time.
__How_to_Help____________________________________.
| |
| - Open an issue if you have questions! |
| - Star this repo if you found it interesting |
| - Fork off & help document <3 |
| - Say Hi! :) https://discord.gg/K5XjmZx |
|.________________________________________________|
Changelog
0.1.0 first release
Contributing
By making a pull request, you agree to release your modifications under the license stated in the next section.
Only changesets by human contributors will be accepted.
License
2022 Tony Ivanov