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.
# pnpm
$ pnpm add @crikey/stores-base
# npm
$ npm add @crikey/stores-base
# yarn
$ yarn add @crikey/stores-base
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.
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
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
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.
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.
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
a
changes, d
is recalculated.graph TD
a --> d
b) Simple dual dependency
a
or b
changes, d
is recalculated.graph TD
a --> d
b --> d
c) Simple chained dependency
a
changes, b
is recalculated.b
or c
changes, d
is recalculated.graph TD
a --> b --> d
c --> d
d) Diamond dependency
a
changes, b
and c
are recalculated.b
or c
changes, d
is recalculated.graph TD
a --> b --> d
a --> c --> d
e) Diamond+ dependency
a
changes, b
, c
, and d
are recalculated.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.
graph TD
a --> d
a --> b --> d
a --> c --> d
Subscribing to a store from within its start function triggers a RecursionError rather returning the initial_value
Uncaught errors in subscribers, start functions or derivation functions can now be handled via @{link set_store_runner}
Asynchronous callback for deriving a value from resolved input stores
Synchronous callback for deriving a value from resolved input stores
Callback to inform that a value is undergoing change. Helps solve the diamond dependency problem
Asynchronous callback for deriving a value from resolved input value
Synchronous callback for deriving a value from resolved input value
Callback to inform that the value was reevaluated to the same value
Signature of the Writable.set function
Callback to inform that there is now a subscriber
Callback to inform that the last subscriber has unsubscribed
One or more Readable
s.
One or more values from Readable
stores.
Subscription signature
Basic subscription signature used by terminal subscriptions
Full subscription signature used by derived store types
Callback to inform of a value updates.
A Writable store which contains the most recent of either:
read
transformset
or update
Additionally contains two other stores: {@link derived$} {@link smart$}
Callback used to determine if a change signal should be emitted
Unsubscribes from value updates.
Signature of the Writable.update function
Signature of the Writable.update function
Callback to update a value.
Callback to update a value.
Asynchronous callback for deriving a value from resolved input value
Synchronous callback for deriving a value from resolved input value
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
the constant value of the store
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
callback used to determine if subscribers should be called
input stores
callback that aggregates the store values which are passed in as the first argument
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
callback used to determine if subscribers should be called
input stores
callback that aggregates the store values which are passed in as the first argument
initial value
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.
callback used to determine if subscribers should be called
input stores
callback that aggregates the store values
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.
callback used to determine if subscribers should be called
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: ...
callback used to determine if subscribers should be called
initial store value
callback which is signaled whenever the number of subscribers changes from 0 to 1
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.
callback used to determine if subscribers should be called
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.
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.
callback used to determine if subscribers should be called
initial store value
callback called whenever the number of subscribers changes from 0 to 1
Creates a new TransformedStore store by applying transform functions on both read and write.
callback used to determine if subscribers should be called
input store
callback used to transform values from the input store into values for the output store
callback used setting the value of the output store. result is applied to the input store.
Creates a new TransformedStore store by applying transform functions on both read and write.
callback used to determine if subscribers should be called
input store
callback used to transform values from the input store into values for the output store
callback used setting the value of the output store. result is applied to the input store.
Creates a new TransformedStore store by applying transform functions on both read and write.
callback used to determine if subscribers should be called
input store
callback used to transform values from the input store into values for the output store
callback used setting the value of the output store. result is applied to the input store.
Initial value of the resulting store
Creates a new TransformedStore store by applying transform functions on both read and write.
callback used to determine if subscribers should be called
input store
callback used to transform values from the input store into values for the output store
callback used setting the value of the output store. result is applied to the input store.
Initial value of the resulting store
Creates a new TransformedStore store by applying transform functions on both read and write.
callback used to determine if subscribers should be called
input store
callback used to transform values from the input store into values for the output store
callback used setting the value of the output store. result is applied to the input store.
Creates a new TransformedStore store by applying transform functions on both read and write.
callback used to determine if subscribers should be called
input store
callback used to transform values from the input store into values for the output store
callback used setting the value of the output store. result is applied to the input store.
Emulate svelte style store triggers. Notably,
ignored
new value
current value
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
maximum number of pending items to keep track of
a pending state tracker which is currently clean
Always return true
ignored
new value
current value
Return true if old and new values are strictly not equal
ignored
new value
current value
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
store
to get the value from
the current store
value
Do nothing
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' ]
the store to be restricted
Generated using TypeDoc
Generic action.