Options
All
  • Public
  • Public/Protected
  • All
Menu

Module @crikey/stores-base

@crikey/stores-base

Types and functions for creating Svelte compatible stores. @crikey/stores-base stores further extend the svelte/store contract to allow for additional features and extensibility.

See @crikey/stores-base for full documentation.

API

Store creation functions:

Utility functions:

Type guards:

Trigger functions:

Installation

# pnpm
$ pnpm add @crikey/stores-base

# npm
$ npm add @crikey/stores-base

# yarn
$ yarn add @crikey/stores-base

Usage

This package is predominantly intended for internal use.

See individual APIs for strict usage instructions or browse the unit tests and usage from other packages in the mono repository.

Differences with Svelte stores

Definable trigger semantics

Svelte stores use a greedy change detection system to, whereby complex types are always considered to have changed.

e.g.

import { writable } from 'svelte/store';

const value = {};
const store = writable(value);
store.subscribe(value => console.log('changed'));
store.set(value);

// > changed
// > changed

@crikey/stores-base stores allow for user defined trigger functions. This trigger function is called for each Writable.set and Writable.update call, allowing for user defined comparisons between the old value and the new value to determine if subscribers should be notified.

e.g.

From ./packages/stores-base/examples/writable.test.ts#189~198


const value = {};
const store = writable(trigger_strict_not_equal, value); // only trigger if old_value !== new_value
store.subscribe(_value => console.log('changed'));
store.set(value);

// > changed

Asynchronous update as well as set

@crikey stores extend the readable, writable, and derive signatures allowing calculations to asynchronously update as well as set their values.

e.g.

From ./packages/stores-base/examples/derive.test.ts#102~131

    const store_a = writable(trigger_strict_not_equal, 1);

const auto_increment = derive(
trigger_strict_not_equal,
store_a,
(a, { update }) => {
const intervalId = setInterval(
() => { update(value => value + a); },
1000
);

return () => {
clearTimeout(intervalId);
}
},
0
);

auto_increment.subscribe(value => console.log('store value:', value));

await new Promise(resolve => {
setTimeout(resolve, 3800);
});

// > store value: 0
// > store value: 1
// > store value: 2
// > store value: 3

Subscriber execution order

In order to ensure reliable and predictable execution order for subscribers, stores utilize an internal action queue. Whenever a store is changed, its active subscriptions are pushed onto a queue and executed in order. If more changes result in more subscriptions being pushed onto the queue, they are added to the end of the current queue and everything continues to be executed in FIFO order.

Svelte does not expose this queue and thus extensions are not able to maintain a pure FIFO order when mixed.

As a natural result, when mixing svelte stores and @crikey/stores, execution order will not be strictly FIFO.

Unlimited dependencies

To avoid erroneous recalculations, derive store types keep track of which inputs are being recalculated (see Premature evaluation below). @crikey/stores-base determines the most efficient approach to this problem based on the number of inputs required.

svelte store implementation details use a fixed tracking system allowing for a maximum of 32 inputs. Additional inputs beyond this number will begin to behave incorrectly.

Note that this is an implementation detail and as such is likely to be improved at some point.

Premature evaluation

Ensuring a derived store value is evaluated against up-to-date inputs is non-trivial.

From the below examples, svelte and @crikey are comparable except for (e) where svelte stores may erroneously calculate a derived value based off of atrophied inputs.

Some examples:

a) Simple single dependency

  • As soon as a changes, d is recalculated.
    %%{init:{"theme":"dark"}}%% graph TD a --> d
    %%{init:{"theme":"default"}}%% graph TD a --> d
    graph TD
      a --> d

b) Simple dual dependency

  • As soon as a or b changes, d is recalculated.
    %%{init:{"theme":"dark"}}%% graph TD a --> d b --> d
    %%{init:{"theme":"default"}}%% graph TD a --> d b --> d
    graph TD
      a --> d
      b --> d

c) Simple chained dependency

  • As soon as a changes, b is recalculated.
  • As soon as b or c changes, d is recalculated.
    %%{init:{"theme":"dark"}}%% graph TD a --> b --> d c --> d
    %%{init:{"theme":"default"}}%% graph TD a --> b --> d c --> d
    graph TD
      a --> b --> d
            c --> d

d) Diamond dependency

  • As soon as a changes, b and c are recalculated.
  • As soon as b or c changes, d is recalculated.
%%{init:{"theme":"dark"}}%% graph TD a --> b --> d a --> c --> d
%%{init:{"theme":"default"}}%% graph TD a --> b --> d a --> c --> d
graph TD
    a --> b --> d
    a --> c --> d

e) Diamond+ dependency

  • As soon as a changes, b, c, and d are recalculated.
  • As soon as b or c changes, d is recalculated.

svelte: A change to a may result in d being recalculated multiple times, sometimes using partially atrophied data from its dependencies.

@crikey: A change to a will at most result in d being recalculated once, after all its dependencies have been resolved.

%%{init:{"theme":"dark"}}%% graph TD a --> d a --> b --> d a --> c --> d
%%{init:{"theme":"default"}}%% graph TD a --> d a --> b --> d a --> c --> d
graph TD
    a       --> d
    a --> b --> d
    a --> c --> d

Infinite recursion checks

Subscribing to a store from within its start function triggers a RecursionError rather returning the initial_value

Error handling

Uncaught errors in subscribers, start functions or derivation functions can now be handled via @{link set_store_runner}


Index

Type aliases

Action: () => void

Type declaration

    • (): void
    • Generic action.

      Returns void

ComplexSet<T>: Set<T> & { invalidate: Invalidate; revalidate: Revalidate; set: Set<T>; update: Update<T> }

Type parameters

  • T

DeriveFnAsyncComplex<S, T>: (values: StoresValues<S>, set: ComplexSet<T>) => Unsubscriber | void

Type parameters

Type declaration

DeriveFnSync<S, T>: (values: StoresValues<S>) => T

Type parameters

Type declaration

    • Synchronous callback for deriving a value from resolved input stores

      Parameters

      Returns T

Invalidate: Action

Callback to inform that a value is undergoing change. Helps solve the diamond dependency problem

ReadFnAsyncComplex<I, O>: (values: I, set: ComplexSet<O>) => Unsubscriber | void

Type parameters

  • I

  • O

Type declaration

ReadFnSync<I, O>: (values: I) => O

Type parameters

  • I

  • O

Type declaration

    • (values: I): O
    • Synchronous callback for deriving a value from resolved input value

      Parameters

      • values: I

      Returns O

Revalidate: Action

Callback to inform that the value was reevaluated to the same value

Set<T>: (this: void, value: T) => void

Type parameters

  • T

Type declaration

    • (this: void, value: T): void
    • Signature of the Writable.set function

      Parameters

      • this: void
      • value: T

      Returns void

StartNotifier<T>: (set: ComplexSet<T>) => StopNotifier | void

Type parameters

  • T

Type declaration

StopNotifier: Action

Callback to inform that the last subscriber has unsubscribed

Stores: Readable<unknown> | [Readable<unknown>, ...Readable<unknown>[]] | Readable<unknown>[]

One or more Readables.

StoresValues<T>: T extends Readable<infer U> ? U : { [ K in keyof T]: T[K] extends Readable<infer U> ? U : never }

One or more values from Readable stores.

Type parameters

  • T

Subscribe<T>: SubscribeBasic<T> | SubscribeFull<T>

Subscription signature

Type parameters

  • T

SubscribeBasic<T>: (this: void, run: Subscriber<T>) => Unsubscriber

Type parameters

  • T

Type declaration

SubscribeFull<T>: (this: void, run: Subscriber<T>, invalidate?: Invalidate, revalidate?: Revalidate) => Unsubscriber

Type parameters

  • T

Type declaration

Subscriber<T>: (value: T) => void

Type parameters

  • T

Type declaration

    • (value: T): void
    • Callback to inform of a value updates.

      Parameters

      • value: T

      Returns void

TransformedStore<O>: Writable<O> & { derived$: Readable<O | undefined>; smart$: Readable<O | undefined> }

A Writable store which contains the most recent of either:

  • An input store transformed via a matching read transform
  • A value set directly via set or update

Additionally contains two other stores: {@link derived$} {@link smart$}

Type parameters

  • O

Trigger<T>: (initial: boolean, new_value: T, old_value?: T) => boolean

Type parameters

  • T

Type declaration

    • (initial: boolean, new_value: T, old_value?: T): boolean
    • Callback used to determine if a change signal should be emitted

      Parameters

      • initial: boolean
      • new_value: T
      • Optional old_value: T

      Returns boolean

Unsubscriber: Action

Unsubscribes from value updates.

Update<T>: { (this: void, updater: UpdaterAsync<T>): void; (this: void, updater: UpdaterSync<T>): void }

Type parameters

  • T

Type declaration

UpdaterAsync<T>: (value: T, set: Set<T>) => void

Type parameters

  • T

Type declaration

    • (value: T, set: Set<T>): void
    • Callback to update a value.

      Parameters

      • value: T
      • set: Set<T>

      Returns void

UpdaterSync<T>: (value: T) => T

Type parameters

  • T

Type declaration

    • (value: T): T
    • Callback to update a value.

      Parameters

      • value: T

      Returns T

WriteFnAsync<I, O>: (values: I, set: ComplexSet<O>) => void

Type parameters

  • I

  • O

Type declaration

    • Asynchronous callback for deriving a value from resolved input value

      Parameters

      Returns void

WriteFnSync<I, O>: (values: I) => O

Type parameters

  • I

  • O

Type declaration

    • (values: I): O
    • Synchronous callback for deriving a value from resolved input value

      Parameters

      • values: I

      Returns O

Create Store Functions

  • Create a simple store which always returns the same value upon subscription

    Example:

    From ./packages/stores-base/examples/constant.test.ts#8~14

        const store = constant(10);

    console.log('store value:', get(store));

    // > store value: undefined

    Type parameters

    • T

    Parameters

    • value: T

      the constant value of the store

    Returns Readable<T>

  • Derives a store from one or more other stores. The store value is calculated on demand and recalculated whenever any of the store dependencies change.

    For simple synchronous usage, see the alternate signature.

    Values may be updated asynchronously:

    Example:

    From ./packages/stores-base/examples/derive.test.ts#102~131

        const store_a = writable(trigger_strict_not_equal, 1);

    const auto_increment = derive(
    trigger_strict_not_equal,
    store_a,
    (a, { update }) => {
    const intervalId = setInterval(
    () => { update(value => value + a); },
    1000
    );

    return () => {
    clearTimeout(intervalId);
    }
    },
    0
    );

    auto_increment.subscribe(value => console.log('store value:', value));

    await new Promise(resolve => {
    setTimeout(resolve, 3800);
    });

    // > store value: 0
    // > store value: 1
    // > store value: 2
    // > store value: 3

    Type parameters

    Parameters

    • trigger: Trigger<T>

      callback used to determine if subscribers should be called

    • stores: S

      input stores

    • fn: DeriveFnAsyncComplex<S, undefined | T>

      callback that aggregates the store values which are passed in as the first argument

    Returns Readable<T | undefined>

  • Derives a store from one or more other stores. The store value is calculated on demand and recalculated whenever any of the store dependencies change.

    For simple synchronous usage, see the alternate signature.

    Values may be updated asynchronously:

    Example:

    From ./packages/stores-base/examples/derive.test.ts#102~131

        const store_a = writable(trigger_strict_not_equal, 1);

    const auto_increment = derive(
    trigger_strict_not_equal,
    store_a,
    (a, { update }) => {
    const intervalId = setInterval(
    () => { update(value => value + a); },
    1000
    );

    return () => {
    clearTimeout(intervalId);
    }
    },
    0
    );

    auto_increment.subscribe(value => console.log('store value:', value));

    await new Promise(resolve => {
    setTimeout(resolve, 3800);
    });

    // > store value: 0
    // > store value: 1
    // > store value: 2
    // > store value: 3

    Type parameters

    Parameters

    • trigger: Trigger<T>

      callback used to determine if subscribers should be called

    • stores: S

      input stores

    • fn: DeriveFnAsyncComplex<S, T>

      callback that aggregates the store values which are passed in as the first argument

    • initial_value: T

      initial value

    Returns Readable<T>

  • Derives a store from one or more other stores. The store value is calculated on demand and recalculated whenever any of the store dependencies change.

    In the simplest version, derive takes a single store, and the callback returns a derived value:

    Example:

    From ./packages/stores-base/examples/derive.test.ts#11~26

        const store_a = writable(trigger_strict_not_equal, 1);

    const doubled = derive(
    trigger_strict_not_equal,
    store_a,
    a => a * 2
    );

    doubled.subscribe(value => console.log('store value:', value));

    store_a.set(2);

    // > store value: 2
    // > store value: 4

    derive may also take a tuple or array of inputs a derive a value from those:

    Example:

    From ./packages/stores-base/examples/derive.test.ts#37~53

        const store_a = writable(trigger_strict_not_equal, 1);
    const store_b = writable(trigger_strict_not_equal, 100);

    const summed = derive(
    trigger_strict_not_equal,
    [store_a, store_b],
    ([a, b]) => a + b
    );

    summed.subscribe(value => console.log('store value:', value));

    store_a.set(2);

    // > store value: 101
    // > store value: 102

    Alternate signatures provide a means for deriving the value asynchronously.

    Type parameters

    Parameters

    • trigger: Trigger<T>

      callback used to determine if subscribers should be called

    • stores: S

      input stores

    • fn: DeriveFnSync<S, T>

      callback that aggregates the store values

    Returns Readable<T>

  • Creates a readable store with the value of undefined.

    This signature provides little benefit other than mirroring the signature for its counterpart, readable

    Example:

    From ./packages/stores-base/examples/readable.test.ts#8~14

        const store = readable(trigger_strict_not_equal);

    store.subscribe(value => { console.log('store value:', value) });

    // > store value: undefined

    Explicitly defining the type of store via readable<Type> will result in a store of type Readable<Type | undefined> to allow for the default value. If this is undesired, an alternate default value/type can be provided.

    Type parameters

    • T = undefined

    Parameters

    • trigger: Trigger<T>

      callback used to determine if subscribers should be called

    Returns Readable<T | undefined>

  • Creates a readable store with an initial value of value.

    Readable stores provide no external methods for changing the store value, but their value can be changed via the implementation of start. See writable for detailed usage of the start argument.

    Example:

    From ./packages/stores-base/examples/readable.test.ts#25~50

        const time = readable<Date | null>(trigger_strict_not_equal, null, (set) => {
    set(new Date());

    const intervalId = setTimeout(() => {
    set(new Date());
    }, 1000);

    return () => {
    clearInterval(intervalId);
    }
    });

    const unsubscribe = time.subscribe(value => { console.log('time is:', value) });

    // wait 1 second
    await new Promise(resolve => {
    setTimeout(resolve, 1000);
    });

    unsubscribe();

    // > time is: ...
    // > time is: ...

    Type parameters

    • T

    Parameters

    • trigger: Trigger<T>

      callback used to determine if subscribers should be called

    • value: T

      initial store value

    • Optional start: StartNotifier<T>

      callback which is signaled whenever the number of subscribers changes from 0 to 1

    Returns Readable<T>

  • Create a writable store with an initial value of undefined.

    Writable stores allow the store value to be set and updated by external code via Writable.set and Writable.update.

    Example:

    From ./packages/stores-base/examples/writable.test.ts#8~30


    // create a writable store
    const store = writable<number>(trigger_strict_not_equal);

    // log each store value
    store.subscribe(value => console.log(value))

    // set
    store.set(1);

    // update
    store.update(value => value === undefined ? 0 : value + 1);

    // set
    store.set(undefined);

    // > undefined
    // > 1
    // > 2
    // > undefined

    Explicitly defining the type of store via writable<Type> will result in a store of type Writable<Type | undefined> to allow for the default value. If this is undesired, an alternate default value/type can be provided.

    Type parameters

    • T = undefined

    Parameters

    • trigger: Trigger<undefined | T>

      callback used to determine if subscribers should be called

    Returns Writable<T | undefined>

  • Create a writable store with an initial value of value.

    Writable stores allow the store value to be set and updated by external code via Writable.set and Writable.update.

    Example:

    From ./packages/stores-base/examples/writable.test.ts#43~61


    // create a writable store
    const store = writable(trigger_strict_not_equal, 42);

    // log each store value
    store.subscribe(value => console.log(value))

    // set
    store.set(1);

    // update
    store.update(value => value + 1);

    // > 42
    // > 1
    // > 2

    If start is provided, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). Thus, start is called whenever the writable store 'starts up'. start may optionally return a function which will be called when the last subscriber unsubscribes.

    Example:

    From ./packages/stores-base/examples/writable.test.ts#73~98


    // create a writable store
    const store = writable(trigger_strict_not_equal, 42, () => {
    console.log('got a subscriber');
    return () => console.log('no more subscribers');
    });

    // log each store value
    const unsubscribe = store.subscribe(value => console.log(value))

    // set
    store.set(1);

    // update
    store.update(value => value + 1);

    unsubscribe();

    // > got a subscriber
    // > 42
    // > 1
    // > 2
    // > no more subscribers

    start is passed 4 functions - set, update, invalidate, and validate.

    start: set

    Set the current value of the store (and thus marking the store value as valid).

    Example:

    From ./packages/stores-base/examples/writable.test.ts#112~139


    // create a writable store which updates asynchronously
    const store = writable(trigger_strict_not_equal, false, (set) => {
    const id = setTimeout(
    () => { set(true) },
    0
    );

    return () => {
    clearTimeout(id);
    };
    });

    // log each store value
    const unsubscribe = store.subscribe(value => console.log('store value:', value))

    // give time for an update
    await new Promise(resolve => {
    setTimeout(resolve, 0);
    });

    unsubscribe();

    // > store value: false
    // > store value: true

    start: update

    Update the current value of the store (and thus marking the store value as valid).

    Example:

    From ./packages/stores-base/examples/writable.test.ts#150~177


    // create a writable store which updates asynchronously
    const store = writable(trigger_strict_not_equal, 5, ({ update }) => {
    const id = setTimeout(
    () => { update(value => value * 1000) },
    0
    );

    return () => {
    clearTimeout(id);
    };
    });

    // log each store value
    const unsubscribe = store.subscribe(value => console.log('store value:', value))

    // give time for an update
    await new Promise(resolve => {
    setTimeout(resolve, 0);
    });

    unsubscribe();

    // > store value: 5
    // > store value: 5000

    start: invalidate

    Mark the store (and any dependencies) as dirty. Only necessary when creating advanced stores such as derive.

    start: validate

    Mark the store (and any dependencies) as valid. Only necessary when creating advanced stores such as derive.

    invalidate/validate

    Usage of invalidate and validate is only necessary when creating advanced stores such as derive which are dependent on other stores but should only be recalculated once all dependent stores are in a valid state.

    Type parameters

    • T

    Parameters

    • trigger: Trigger<T>

      callback used to determine if subscribers should be called

    • value: T

      initial store value

    • Optional start: StartNotifier<T>

      callback called whenever the number of subscribers changes from 0 to 1

    Returns Writable<T>

Other Functions

  • Creates a new TransformedStore store by applying transform functions on both read and write.

    Type parameters

    • I

    • O

    Parameters

    • trigger: Trigger<undefined | O>

      callback used to determine if subscribers should be called

    • store$: Writable<I>

      input store

    • read: ReadFnAsyncComplex<I, undefined | O>

      callback used to transform values from the input store into values for the output store

    • write: WriteFnAsync<undefined | O, I>

      callback used setting the value of the output store. result is applied to the input store.

    Returns TransformedStore<O | undefined>

  • Creates a new TransformedStore store by applying transform functions on both read and write.

    Type parameters

    • I

    • O

    Parameters

    • trigger: Trigger<undefined | O>

      callback used to determine if subscribers should be called

    • store$: Writable<I>

      input store

    • read: ReadFnAsyncComplex<I, undefined | O>

      callback used to transform values from the input store into values for the output store

    • write: WriteFnSync<undefined | O, I>

      callback used setting the value of the output store. result is applied to the input store.

    Returns TransformedStore<O | undefined>

  • Creates a new TransformedStore store by applying transform functions on both read and write.

    Type parameters

    • I

    • O

    Parameters

    • trigger: Trigger<O>

      callback used to determine if subscribers should be called

    • store$: Writable<I>

      input store

    • read: ReadFnAsyncComplex<I, O>

      callback used to transform values from the input store into values for the output store

    • write: WriteFnAsync<O, I>

      callback used setting the value of the output store. result is applied to the input store.

    • initial_value: O

      Initial value of the resulting store

    Returns TransformedStore<O>

  • Creates a new TransformedStore store by applying transform functions on both read and write.

    Type parameters

    • I

    • O

    Parameters

    • trigger: Trigger<O>

      callback used to determine if subscribers should be called

    • store$: Writable<I>

      input store

    • read: ReadFnAsyncComplex<I, O>

      callback used to transform values from the input store into values for the output store

    • write: WriteFnSync<O, I>

      callback used setting the value of the output store. result is applied to the input store.

    • initial_value: O

      Initial value of the resulting store

    Returns TransformedStore<O>

  • Creates a new TransformedStore store by applying transform functions on both read and write.

    Type parameters

    • I

    • O

    Parameters

    • trigger: Trigger<O>

      callback used to determine if subscribers should be called

    • store$: Writable<I>

      input store

    • read: ReadFnSync<I, O>

      callback used to transform values from the input store into values for the output store

    • write: WriteFnAsync<O, I>

      callback used setting the value of the output store. result is applied to the input store.

    Returns TransformedStore<O>

  • Creates a new TransformedStore store by applying transform functions on both read and write.

    Type parameters

    • I

    • O

    Parameters

    • trigger: Trigger<O>

      callback used to determine if subscribers should be called

    • store$: Writable<I>

      input store

    • read: ReadFnSync<I, O>

      callback used to transform values from the input store into values for the output store

    • write: WriteFnSync<O, I>

      callback used setting the value of the output store. result is applied to the input store.

    Returns TransformedStore<O>

  • trigger_safe_not_equal<T>(_initial: boolean, a: T, b?: T): boolean
  • Emulate svelte style store triggers. Notably,

    • always trigger when the new value is an object or a function
    • dont trigger if the new value is NaN and the old value is NaN

    Type parameters

    • T

    Parameters

    • _initial: boolean

      ignored

    • a: T

      new value

    • Optional b: T

      current value

    Returns boolean

Pending Functions

  • create_pending(length: number): Pending
  • Create an efficient system for determining if any of an array of items are pending.

    Used by derived store types to ensure that the derived value is only recalculated once all dependent stores are in a clean state.

    Example:

    From ./packages/stores-base/examples/pending.test.ts#8~21

        const pending = create_pending(3);

    // pending state starts clean
    console.log(pending.is_dirty()); // false

    // any invalid items make the pending state dirty
    pending.invalidate(1);
    console.log(pending.is_dirty()); // true

    // validating all invalid items makes the pending state clean
    pending.validate(1);
    console.log(pending.is_dirty()); // false

    Parameters

    • length: number

      maximum number of pending items to keep track of

    Returns Pending

    a pending state tracker which is currently clean

Predefined Triggers Functions

  • trigger_always<T>(_initial: boolean, _new_value: T, _old_value?: T): boolean
  • trigger_strict_not_equal<T>(_initial: boolean, new_value: T, old_value?: T): boolean

Utility Functions

  • Return the current value of the provided store.

    Works by subscribing and immediately unsubscribing from the given store. This is neither efficient nor reactive and should generally be avoided.

    Example:

    From ./packages/stores-base/examples/get.test.ts#8~12

        const store = writable(trigger_strict_not_equal, 42);

    console.log(get(store)); // > 42

    Type parameters

    • T

    Parameters

    • store: Readable<T>

      store to get the value from

    Returns T

    the current store value

  • noop(): void
  • Create a read-only version of a given store by restricting the available methods

    Example:

    From ./packages/stores-base/examples/read-only.test.ts#8~15

        const rw_store = writable(trigger_strict_not_equal, 42);

    const ro_store = read_only(rw_store);

    console.log(Object.getOwnPropertyNames(rw_store)); // > [ 'set', 'update', 'subscribe' ]
    console.log(Object.getOwnPropertyNames(ro_store)); // > [ 'subscribe' ]

    Type parameters

    • T

    Parameters

    • store: Readable<T>

      the store to be restricted

    Returns Readable<T>

Generated using TypeDoc