Svelte's built-in stores are not always ergonomic when you have to mix them with imperative code. Storables remedy this by letting you to embed logic within a store's lifetime.
checkable
works like Svelte's writable
but with built-in asynchronous validation.
-
✅ Asynchronous validation
const initialValue = "John Smith"; const { username, usernameCheckStatus } = checkable({ name: "username", async check(newUsername) { // This value is invalid, but I know what I'm doing. if (userIsTyping) return false; // This value is invalid, scream! if (await alreadyExists(newUsername)) { throw Error("Username already taken"); } // This value is valid return true; }, }, initialValue);
$usernameCheckStatus
is"pending"
while validating the value asynchronously.$usernameCheckStatus
is"done"
when validation is complete.$usernameCheckStatus
is"error"
if the validation throws an error.In the above example,
usernameCheckStatus.error
isError("Username is already taken")
if$usernameCheckStatus === "error"
. It isnull
otherwise.Unlike the transformable's
assert
,check
is not write-blocking.The default/initial value is not validated.
Persist your store's value in localStorage or your database —store it anywhere.
-
💾 Asynchronous storage
const defaultValue = 0; const { count } = persistable({ name: "count", io: { read({ set }) { readFromDatabase() .then(value => set(value)) .catch(() => defaultValue); const cleanUp = onDatabaseChange(value => set(value)); return cleanUp; }, write: (value, { set, error }) => { try { writeToDatabase(value).then(value => set(value);); } catch (e) { error("Save unsuccessful. Please try again later."); } }, }, });
-
✅ Validation
const { count, countWriteStatus } = persistable({ name: "count", io: { read: ({ set }) => { set(JSON.parse(localStorage.getItem("count"))); }, write: (value, { set, error }) => { if (isInvalid(value)) { error("Heeyo! Value is invalid."); } localStorage.setItem("count", JSON.stringify(value)); set(value); }, }, });
$countWriteStatus
is"pending"
while writing value.$countWriteStatus
is"done"
when writing is complete.$countWriteStatus
is"error"
if thewrite
throws an error.In the above example,
countWriteStatus.error
isError("Could not write invalid value")
if$countWriteStatus === "error"
. It isnull
otherwise.
If Svelte's built-in writable
and derived
stores had a baby, it would be transformable
.
-
🔄 Two-way transforms (...more like many-way transforms)
const { dateObject, number } = transformable({ name: "number", transforms: { dateObject: { to: (date) => date.getTime(), from: (number) => new Date(number), }, }, }, new Date().getTime());
Updating
number
will call its own subscribers with$number
and subscribers ofdateObject
withnew Date($number)
. UpdatingdateObject
will call its own subscribers with$dateObject
and subscribers ofnumber
with$dateObject.getTime()
.This is handy if, for example, you want to display minutes to a user but your code thinks in milliseconds.
-
✅ Validation
const now = new Date().getTime() const { number, numberAssertStatus } = transformable({ name: "number", assert(number) { // This is an invalid value, but I know what I'm doing. if (number === undefined) return false; // This is an error, scream! if (number < now) throw Error("Date must be after now"); // This is a valid number return true; } }, now);
numberAssertStatus.error
isError("Date must be after now")
if$numberAssertStatus === "error"
. It isnull
otherwise.assert
is write-blocking. If it throws or returnsfalse
the value of the store will not change and subscribers will not be called.The default/initial value is not validated.