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

@okcontract/cells

v0.3.3

Published

Simplified reactive functional programming for the web

Downloads

23

Readme

Cells: Simplified reactive functional programming for the web

CI Coverage Status size

Functional & Reactive programming (FRP) simplifies handling complex, dynamic data flows: It is particularly useful in scenarios with asynchronous data sources, such as user interfaces or real-time data feeds.

cells is a simplified Functional & Reactive (but not strictly FRP) library inspired by spreadsheets.

cells are either values or functions

cells focuses on automation as it transparently manages:

  • async calls
  • undefined values
  • errors
  • pointers

Although it is a fully independent library, cells can be a powerful drop-in replacement for Svelte stores as it implements the Svelte store interface. There is also a React hook and a demo with Vue 3.

Walkthrough

import { Sheet } from "@okcontract/cells";

// create a new Sheet, typically one per application
const sheet = new Sheet();

// that's a cell
const cellA = sheet.new(1);

// no need to wait for anything, values and computations will
// be called automatically when they're ready
const cellB = sheet.new(getValueAfterDelay(2, 100));

// that's a _mapped_ cell, that is recomputed automatically when
// either dependent cells are updated
const sum = sheet.map([cellA, cellB], (a, b) => a + b);

// you can map a single cell directly
// and feel free to define mapped cells before their values are available
const prod = cellB.map((v) => 2 * v);

// we await _only_ when we need results
expect(await prod.get()).toBe(4);

// you can update _value_ cells directly (no await)
cellA.set(3);
expect(await sum.get()).toBe(5);

// map computations can be async too
const ok = someCell.map(async (v) => {
  const errors = await validate(v);
  return errors.length === 0;
});

Note that:

  • .get() never returns undefined and cells semantics waits until cell values are defined. If you need to immediately return a value, use null instead.
  • At any time, the actual cell value is accessible through cell.value (possibly undefined) but it's advisable to avoid relying on a value that could be updated at any moment.

Subscriptions

You can define subscribers functions to be called whenever a cell value change.

const cellA = sheet.new(1);

// create subscriptions on any cell
const unsubscribe = cellA.subscribe((v) => {
  console.log({ v });
});

// later call `unsubscribe` to cancel the subscription
unsubscribe();

When using cells in Svelte files, you can use the $cell sugar to subscribe to a cell value for display. Note that there is a initial undefined value when using that sugar as the reactive subscription are initially output by the Svelte compiler as let $cell;.

Unlike Svelte, subscriptions are called in transactional batches, i.e. cells wait until all updates are propagated in the Sheet to dispatch notifications once to subscribers. This prevents stuttering.

Memory management and proxies

Cells are not garbage collected automatically, since the best practice is to keep a long-running Sheet for each application.

To delete a cell, use:

sheet.delete(cell);

To simplify these operations, you can define a sub-graph of cells in a Proxy that can be deleted at once.

import { SheetProxy } from "@okcontract/cells";

// create a proxy (for instance in a Svelte component)
const proxy = new SheetProxy(sheet);

// use the proxy to create new cells...
const cell = proxy.new(1);
// ...and mapped cells
const mappedCell = proxy.map([...cells], (...args)=>{...})

// delete the proxy when you're done
proxy.destroy();

There are added benefits of proxies, including a single call to wait for all cells in a Proxy to be computed.

await proxy.working.wait();

Pointers

Sometimes, you will need to return cells inside .map compute functions. cells manages this automatically with pointers.

const cellA = proxy.new(...);
const cellB = proxy.new(...);

// pointerCell will either be cellA or cellB
const pointerCell = proxy.map([cellA, cellB], (a, b) =>
  condition ? cellA : cellB
);

// breathe normally
await pointerCell.get();
pointerCell.map(...);

Cell updates

It's easy to update cells depending on their previous value:

const counter = proxy.new(0);
// ...
counter.update((prev) => prev + 1);

Note that update functions should return new arrays and objects, for instance using spread operators [...prev, new] and {...prev, field: new}.

If you need more complex imperative updates, we suggest you to use immer:

import { produce } from "immer";

const patch = (prev) => {
  // complex updates...
};
cell.update((prev) => produce(prev, patch));

Design & Philosophy

The design and philosophy of cells comes from frameworks such as RxJS, MobX, and Recoil. Our main objective is to enhance developer experience by simplifying the complexity typically associated with state management and reactive programming.

We aim for ease of use and correction, so chasing down any bug is our top priority.

A non-goal is high-performance (we're not a tensor library!): cells is slower than any direct implementation and should not be used for computationally intensive tasks. However, cells optimizes the global computation within a program, potentially leading to more optimal execution than can be achieved through imperative computations from manually organized functions or modules.

About

cells is built at OKcontract and is released under the MIT license.

Contributors are welcome, feel free to submit PRs directly for small changes. You can also reach out in our Discord or contact us on Twitter in advance for larger contributions.

This work is supported in part by a RFG grant from Optimism.