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

atomi

v0.2.4

Published

JS Reactivity Framework

Downloads

337

Readme

atomi

A reactivity framework.

Installation

npm i atomi

Example

import { atom, reactive } from "atomi"

const [counter, setCounter, setCounterFn] = atom(0)

reactive(() => {
  console.log(`counter is: ${counter()}`)
})

await setCounter(1) // Logs `counter is: 1`
await setCounterFn(count => count + 1) // Logs `counter is: 2`

References

Hooks

This is the part that majority of users are going to interact with.

reactive

import { reactive } from "atomi"

Used to create reactive functions. Reactive functions are such functions that execute automatically when atoms inside them change their value

| Argument | Type | Desc | |---|:---|:---| | callback | [async] Function | Called when atoms inside change Accepts a single argument scope | | ignoreAsync | Boolean | By default is false. If true is passed return will be always scope even if callback resovles into Promise |

Returns scope or Promise that resolves into scope when callback is async or returns Promise

reactive(scope => {
  const value = dependantAtom()
  scope.stop()
})

atom

  • src/hooks.mjs#82
  • Accept Any
  • Return [() => Any, (value) => Promise, (Function) => Promise]
import { atom } from "atomi"

Atoms are used to store information. When they are accessed from inside reactive functions, atom registers function's scope as dependency. Any time atom is updated, it's functions of dependant scopes are executed.

| Argument | Type | Desc | |---|:---|:---| | Default Value | Any | Intial value of atom |

Returns Array of getter, setter and callback setter functions

  • getter function that returns current value of the atom and registers current scope as dependecy if current scope exists
  • setter function accepts a value that will become new value of the atom. Returns a Promise that fulfills when all dependant scopes of the atom are executed
  • callback setter accepts a callback function that accepts 2 arguments and that returns new value for the atom or NONE. Returns a Promise that fulfills when all dependant scopes of the atom are executed

callback setter

| Argument | Type | Desc | |---|:---|:---| | Current Value | Any | The current value of atom | | NONE | Symbol | Unique symbol. If NONE is returned the atom retains it's value and reactive updates will not follow |

Returns Any or NONE

const [count, setCount, setCountFn] = atom(0)
reactive(function logCount() {
  // getter function
  console.log(count())
})
// setter function
await setCount(2)
// callback setter function
await setCountFn((current, NONE) => current + 1) // Sets count to 3 and triggers logCount to execute
await setCountFn((current, NONE) => NONE) // Count stays at 3 and logCount is not triggered.
// NONE is useful when the complex logic produces the same value as current
// and hence we do not want to update anything because it says the same.

new atom

import { atom } from "atomi"

| Argument | Type | Desc | |---|:---|:---| | Default Value | Any | Intial value of atom |

Returns RectiveVar with default value

const count = new atom(0)
reactive(function logCount() {
  console.log(count.get())
})
await count.set(1) // sets atom count value to 1 and triggers logCount

nonreactive

import { nonreactive } from "atomi"

Used as wrapper around an atom to access its value, but not register current scope as its dependency

| Argument | Type | Desc | |---|:---|:---| | Callback | Function | Getter function or atomic function |

Returns the return value of the callback function

const [count, setCount] = atom(0)
const [increment, setIncrement] = atom(1)
reactive(() => {
  console.log(`count is ${count() + nonreactive(increment)}`)
})

await setCount(current => 1) // triggers the reactive function and logs `count is 2`
await setIncrement(2) // does not trigger the reactive function
await setCount(current => 2) // triggers the reactive function and logs `count is 4`

guard

import { guard } from "atomi"

Used as wrapper around an atom or a function to access its value, but triggers value changes. Acceps a comparison function as a second argument.

| Argument | Type | Desc | |---|:---|:---| | Callback | Function | Getter function or atomic function | | Comparator | Function | Comparator function; accepts 2 arguments, new and old return value of the callback function. Will trigger the dependencies and store the new value when Comparator returns true |

Returns the return value of the callback function

const [number, setNumber] = atom(0)
reactive(() => {
  console.log(`number is ${guard(number)}`)
})

await setNumber(1) // triggers the reactive function and logs `count is 1`
await setNumber(1) // does not trigger the reactive function
await setNumber(2) // triggers the reactive function and logs `count is 2`

Example using custom comparator function:

const [number, setNumber] = atom(0)
reactive(() => {
  console.log(`number is ${guard(number, (a, b) => parseInt(a) !== parseInt(b))}`)
})

await setNumber(0.5) // does not trigger the reactive function, because parseInt(0.5) is still 0
await setNumber(1.1) // triggers the reactive function and logs `count is 1.1`
await setNumber(1.5) // does not trigger the reactive function, because parseInt(1.5) is still 1

Core

This part describes the core functionality that the framework is built upon. Majority of users should not interact with it directly unless working on a library that extends core functionalities.

ReactiveVar

A class that is used by atoms internally to store value.

NOTE: it is recommended to use atoms in your work, unless you are working on a library or you like the syntax of it better. If you prefer to use ReactiveVar directly for OOP style programming, you still should use new atom instead

import { ReactiveVar } from "atomi"

#get

Returns current value of the ReactiveVar and registers current scope as a dependency is current scope exists

const counter = new atom(0)
reactive(() => {
  const count = counter.get()
  ...
})

#set

Sets value of the ReactiveVar

const counter = new atom(0)
counter.set(2) // counter.get will return 2 instead

Scope

A class that manages reactive functions

import { Scope } from "atomi"

| Argument | Type | Desc | |---|:---|:---| | Callback | Function | The function that we with to execute when inside update |

reactive passes scope to the callback fucntion and returns the same scope

const scope = reactive(scope => {
  console.log(scope.firstRun) // logs: true
})

scope.die()

#depend

Is used by ReactiveVar.get to register a dependency

die

Intended to be used like #stop but permanently

#execute

Is used by Tracker when to execute reactive function when Scope was triggered by any ReactiveVars previously

#firstRun

A getter property of Scope. Returns true if the dependant function has not been called more then once.

reactive(scope => {
  console.log(scope.firstRun) // logs: true
})

#resume

Is used to restore connection between dependant ReactiveVars and the Scope. Should be called to restore reactive function execution after Scope.stop had been called.

const [count, setCount] = atom(0)
const scope = reactive(scope => {
  console.log(`count is ${count()}`)
})
await setCount(1) // logs `count is 1`
scope.stop()
await setCount(2) // does not log anything
scope.resume()
await setCount(3) // logs `count is 3`

#stop

Is used when we want to temporary suspend reactive function execution event when its dependencies are updated.

const [count, setCount] = atom(0)
const scope = reactive(scope => {
  console.log(`count is ${count()}`)
})
await setCount(1) // logs `count is 1`
scope.stop()
await setCount(2) // does not log anything
scope.resume()
await setCount(3) // logs `count is 3`

#trigger

Is used by ReactiveVar when setting a new value.

Returns Promise that fullfils when reactive function of the scope is finished executing

#triggeredBy

Is a property of the scope. Is usefull when debugging to know what ReactiveVar triggered the scope update when scope depends on multiple ReactiveVars

Tracker

Intended to be used as a global object to track and register ReactiveVar updates and execute reactive functions

import { Tracker } from "atomi"

scheduleJob

Schedules scope's reactive function to execute when callback queue is empty. Is used by Scope.trigger to schedule function execution when dependant ReactiveVars update.

Usefull functions

This sections will focus exclusively on the functions to enhance experience with using atoms Specifically on the (callback setter)[#callback-setter) part of it. All of the following functions shine when used in combination with the callback setter

Numbers

add

Creates and adder function

import { add } from "atomi"
const [count,, setCount] = atom(0)

setCount(add(2)) // sets 2 as count value
setCount(add(5)) // sets 7 as count value

sub

Creates and subtractor function

import { sub } from "atomi"
const [count,, setCount] = atom(10)

setCount(sub(2)) // sets 8 as count value
setCount(sub(5)) // sets 3 as count value

inc

Is an edge case of add function that uses 1 as its argument

import { inc } from "atomi"
const [count,, setCount] = atom(0)

setCount(inc) // sets 1 as count value
setCount(inc) // sets 2 as count value

dec

Is an edge case of sub function that uses 1 as its argument

import { sub } from "atomi"
const [count,, setCount] = atom(10)

setCount(sub) // sets 9 as count value
setCount(sub) // sets 8 as count value

power

Creates power function

import { pow } from "atomi"
const [count,, setCount] = atom(2)

setCount(pow(2)) // sets 4 as count value
setCount(pow(3)) // sets 64 as count value

Objects

assign

Creates assigner function

import { assign } from "atomi"
const [person,, setPerson] = atom({ name: "tim" })

setPerson(assing({ age: 22 })) // sets person to be { name: "tim", age: 22 }
setPerson(assing({ age: 23 })) // sets person to be { name: "tim", age: 23 }

Booleans

not

Should be self explanatory

import { not } from "atomi"
const [state,, setState] = atom(true)

setState(not) // sets state to false
setState(not) // sets state to true

id

Good old identity function

import { id } from "atomi"
const [array,, setArray] = atom([1,0,2,0])

setArray(filter(id)) // filters out all falsy values leaving [1, 2]

is

Good old identity function

import { is } from "atomi"
const [array,, setArray] = atom([1,0,1,2,0])

setArray(filter(is(1))) // filters out all values that are not 1 leaving [1, 1]

lesser

Creates function that check if value is lesser then provided

import { lesser } from "atomi"
const [array,, setArray] = atom([1,0,1,2,0])

setArray(filter(lesser(2))) // filters all values that less then 2 [1,0,1,0]

greater

Creates function that check if value is lesser then provided

import { greater } from "atomi"
const [array,, setArray] = atom([1,0,1,2,0])

setArray(filter(greater(1))) // filters all values that are greater then 1 leaving [2]

negative

Is an edge case of lesser function that uses 1 as its argument

import { negative } from "atomi"
const [array,, setArray] = atom([-1,0,1,2,0])

setArray(filter(negative)) // filters out all values that are not negative leaving [-1]

positive

Is an edge case of greater function that uses 1 as its argument

import { positive } from "atomi"
const [array,, setArray] = atom([-1,0,1,2,0])

setArray(filter(positive)) // filters out all values that are not negative leaving [1,2]

Arrays

map

Creates a mapper

import { map } from "atomi"
const [array,, setArray] = atom([1,3,2])

setArray(map(x => x + 1)) // increases all array elements by 1
setArray(map(inc)) // increases all array elements by 1 using inc

filter

Creates a filterer

import { filter } from "atomi"
const [array,, setArray] = atom([1,3,2])

setArray(filter(x => x > 1)) // keeps only elemens that are larger then 1

prepend

Creates a prepender

import { prepend } from "atomi"
const [array,, setArray] = atom([1,3,2])

setArray(prepend(0, 0.5)) // adds 0 and 0.5 at the beginning. Not array is [0,0.5,1,3,2]

append

Creates a appender

import { append } from "atomi"
const [array,, setArray] = atom([1,3,2])

setArray(append(0)) // add 0 at the end. Not array is [1,3,2,0]

insert

Creates an inserter

import { insert } from "atomi"
const [array,, setArray] = atom([1,3,2])

setArray(insert(1, 4, 5)) // inserted [4, 5] at position 1. Now array stores [1, 4, 5, 3, 2]

assignWhere

Creates a function that will update specific element in the array

import { assignWhere } from "atomi"
const [array,, setArray] = atom([1,3,2])

setArray(assignWhere(is(3)), inc) // where element is equal to 3 it will increases it by 1. Now array stores [1,4,2]

sort

Creates a function that will sort an array based on the sorter provided when passed to a callback setter

import { sort } from "atomi"
const [array,, setArray] = atom([11,1,3,2])

setArray(sort()) // using default js sorter. Now array stores [1, 11, 2, 3]

asc

Precoded sorter to sort by ascending order

import { asc } from "atomi"
const [array,, setArray] = atom([11,1,3,2])

setArray(sort(asc)) // using default js sorter. Now array stores [1, 2, 3, 11]

desc

Precoded sorter to sort by descending order

import { desc } from "atomi"
const [array,, setArray] = atom([11,1,3,2])

setArray(sort(desc)) // using default js sorter. Now array stores [11, 3, 2, 1]

Optimizing

omap

Creates a function reacting to changes in an atom containing an array keeping track of previous changes, calling a map callback only when the element mutates.

import { omap } from "atomi"
const [array,, setArray] = atom([11, 1, 2, 3])

const result = omap(array, x => x + 1)
console.log(result()) // [12, 2, 3, 4]

await setArray(arr => { arr[0] = 0; return arr; })
// NOTE: map fn `x => x + 1` was called only once with 0;
console.log(result()); // [1, 2, 3, 4]

await setArray(arr => { arr[2] = 3; arr[3] = 2; return arr; })
// NOTE: map fn `x => x + 1` was not called at all because elements just swapped places,
//       but result has been updated
console.log(result()); // [1, 2, 4, 3]

omapEnumerated

Creates a function reacting to changes in an atom containing an array keeping track of previous changes, calling a map callback only when the element mutates or changes its potisition in the array.

import { omapEnumerated } from "atomi"
const [array,, setArray] = atom([0, 1, 2, 3])

const result = omapEnumerated(array, (x, index) => x + index)
console.log(result()) // [0, 2, 4, 6]

await setArray(arr => { arr[3] = 4; return arr; })
// NOTE: map fn `x => x + 1` was called only once with 4;
console.log(result()); // [0, 2, 4, 7]

await setArray(arr => { arr[2] = 3; arr[3] = 2; return arr; })
// NOTE: map fn `x => x + 1` will be called twice for 3 and 2 with respective indexes
console.log(result()); // [0, 2, 5, 5]