cells, a new functional & reactive programming library compatible with Svelte

Henri
4 min readJan 26, 2024

--

At OKcontract, we write web applications that both rely on a lot of async data and perform complex client-side computations. We also use Svelte and we saw an opportunity to build a computation library that is almost compatible with the Svelte store interface.

A few problems

In addition to being used for all the core computations of our SDK, it can also solve smaller problems. For instance,

$: value = complexComputation(…);

can be triggered multiple times, leading to app stuttering.

let title: string;
$: getTitle().then((label) => (title = label));

Stores solve this but they do not manage asynchronicity nor errors properly. Lastly, one annoying thing is to manage initial undefined values, as we ideally want to wait until values are defined before running any kind of computation with them.

Functional & Reactive

Functional Reactive Programming is a solution to these problems precisely defined in the original research paper. Going beyond the semantics described in the original paper, some libraries are functional and reactive, even if they don’t follow the original semantics and definitions. In that segment, popular libraries like RxJS rely of a high number of primitives (aka operators). For React, some libraries such as MobX or Recoil introduce global states and mapped functions from that state, but they’re strongly tied to React and still a bit rigid.

We designed cells drawing from the way spreadsheets work, providing an alternative with a reduced number of primitives. The core concept of cells is well, a cell. A cell can be of two types:

  • it’s either a ValueCell that holds values that can be manually updated,
  • or a MapCell that holds a compute function (or formula) that is applied to other referenced cells whenever they change. The compute function must be functional without side effects.
ValueCell vs MapCell

cells is currently limited to a single type of event: ValueCell updates, but this will be extended in the future.

Installation

You can scaffold a new project easily by using npm create vite@latest or bun create vite, choosing Svelte (and TypeScript if you want).

Then install the cells package using npm or your favorite manager:

npm i --save @okcontract/cells

Short tutorial with Svelte

Let’s create a simple Svelte application with cells. Our application will be showing a simple component, Dog.svelte:

We’re going there… with very few lines of code!

First, in your main App.svelte, create a Sheet that contains cells:

<script lang=”ts”>
import { Sheet } from “@okcontract/cells”;
const sheet = new Sheet();

import Dog from “./lib/Dog.svelte”;
</script>

<main>
<h1>Svelte + cells</h1>
<div class=”card”>
<Dog {sheet} />
</div>
</main>

In most cases, you should have a single Sheet in your app, that you can put in Svelte context, but here we’re passing it manually since only have one component.

Then, in your Dog.svelte component import it:

import type { Sheet } from “@okcontract/cells”;
export let sheet: Sheet;

We can now define cells: We’ll use three cells:

  • resp is the HTTP response of the dog image API we’re using. We’re using fetch so these calls are async. It’s a ValueCell.
  • url is the dog image URL, extracted from the response. It is an async function too, because of the built-in JSON decoding. It’s a MapCell.
  • finally, len is the very basic URL length (synchronous this time). It’s a MapCell too.

We just define these cells easily, without caring about asynchronicity or potential errors:

const resp = sheet.new(fetchDog());
const url = resp.map(getDogURL);
const len = url.map((txt) => txt.length);

with the following functions:

const dogURL = “https://dog.ceo/api/breeds/image/random";
const fetchDog = () => fetch(dogURL);
const getDogURL = async (r: Response): Promise<string> =>
(await r.json()).message;

Cells will be automatically whenever an asynchronous computation arise. They are compatible with the $ sugar syntax defined by Svelte, so you can use cells directly in your Svelte templates.

{#if $resp instanceof Error || $url instanceof Error}
<div>Error: {$resp}</div>
{:else}
<p>URL length: {$len} bytes</p>
<img src={$url} alt=”Dog” width=”400" />
{/if}

User actions just need to update any ValueCell, such as resp in our example:

<button on:click={() => resp.set(fetchDog())}>Refresh</button>

Error management

Errors are automatically managed: The cell that triggers the error and its dependencies contains errors directly, without throwing them.

To simulate errors, we can define a failed response function:

const failDog = (): Promise<Response> =>
new Promise((_, rejects) => {
rejects(“no internet”);
});

And simply define:

<button on:click={() => resp.set(failDog())}>Fail</button>

More Features

In this very introductory article, we leave aside useful features such as:

  • Proxies: Subset of the graph that can be easily deleted at once. We can also easily wait for all cells in a proxy to be computed.
  • Pointers: Often, we want cells that just point to another one. That exists too!
  • Utilities and combinators: Tools that make your life easier when dealing with cells containing array of cells, etc.

Try it

There is Svelte REPL with this tutorial app: Try it!

You’ll see there’s a few bonus features… But we left several ones out: Can you easily save one of the dogs? Or something else?

Conclusion

There is much more to cells that what we covered in this first tutorial, especially as we will release several components and libraries that make a good use of it soon. Overall, we already have written tens of thousands of lines of code with cells for OKcontract… and it’s been a joy to build with!

A major bonus of our approach is to design pure JavaScript libraries and use Svelte as dumbly as possible for the presentation layer, we’ll also write about this next.

--

--