vyce

A tiny store you can use to build tidy relationships.

import { store, computed } from 'vyce';

const authors = store([
    { name: 'haruki', age: 25 },
    { name: 'james', age: 32 },
    { name: 'agatha', age: 62 }
]);

const youngAuthors = computed(() =>
    authors().filter(author => author.age < 40)
);

authors(prev => [
    ...prev,
    { name: 'david', age: 28 },
    { name: 'lovecraft', age: 57 }
]);

youngAuthors();
// [
//  { name: 'haruki', age: 25 },
//  { name: 'james', age: 32 },
//  { name: 'david', age: 28 }
// ]

Install

Node

npm install vyce

Deno

import { store, computed } from 'https://deno.land/x/vyce/index.js';

Browser

<script src="https://unpkg.com/vyce/dist/vyce.min.js"></script>

In the browser context, the default export name is vyce.

Browser (ESM)

<script type="module">
  import { store, computed } from 'https://unpkg.com/vyce/dist/vyce.js';
</script>

Try on Flems.io.

Usage

See index.d.ts for type definitions.

By default, stores created with vyce use a built-in deep clone function adapted from klona. The default function is capable of cloning objects with JSON-valid data types. You may opt to use another deep clone utility across all new stores by initially calling store.setClone. See below for an example using klona/full.

import { store } from 'vyce';
import { klona } from 'klona/full';

store.setClone(klona);

// all new stores will now use `klona/full` internally
const state = store({ name: 'denam' });

API

store(value?)

import { store } from 'vyce';

const state = store({ name: 'denam' });

// call your store without arguments to get its value
state(); // `{ name: 'denam' }`

// pass an argument to set its value
state({ age: 18 });

// or pass a function to set a value based on the previous value
state(prev => ({ ...prev, name: 'catiua' }));

state(); // `{ age: 18, name: 'catiua' }`

store.sub

import { store } from 'vyce';

const state = store(10);
const unsub = state.sub(value => console.log(value)); // logs `10`

state(20); // logs `20`
unsub();
state(30); // does not log anything

Setting a store will only update its value and run subscribers if the new value is different than the old value. Internally, this is determined with the !== operator. Also note: by default, the subscriber function is called once upon subscribing. Pass a falsey value as a second argument to store.sub to disable the initial call.

import { store } from 'vyce';

const state = store(10);
const unsub = state.sub(value => console.log(value), false); // does not log

state(20); // logs `20`

store.end

Calling end will detach all subscribers from a store.

import { store, computed } from 'vyce';

const foo = store(10);
const bar = store(20);

const rum = computed(() => foo() + bar()); // 30
const ham = computed(() => rum() + bar()); // 50
const logger = rum.sub(console.log); // logs `30`

rum.end(); // breaks all listeners (ham, logger)

foo(20); // foo updates rum, but since ham is no longer listening to rum, it remains at 50
ham(); // 50

computed

As demonstrated above, you can use computed to create stores derived from parent stores. Dependencies are tracked automatically. Creation of circular dependencies will throw an error.

import { store, computed } from 'vyce';

const a = store(10);
const b = computed(() => a() + 10);
const c = computed(() => a(b())); // throws `Circular Dependency` Error

Credits

Inspired by flyd, klona, and trkl.