
Security News
Happy Birthday, Shai-Hulud
It has been one year since Shai-Hulud made its first appearance on npm.
Svelte stores for asynchronous data with fetching, caching, revalidation, and persistence.
Svelte stores for asynchronous data with fetching, caching, revalidation, and persistence.
Everything below this line is written by AI.
Maybe<T> convention (null | T | Error) for all async stateslocalStorage / sessionStorage persistencenpm i svas
<script lang="ts">
import { value, ok } from 'svas'
const user = value<User>({
get: () => fetch('/api/me').then((r) => r.json()),
persist: 'user'
})
</script>
{#if $user instanceof Error}
<p>Failed to load</p>
{:else if $user === null}
<p>Loading…</p>
{:else}
<p>Hello, {$user.name}</p>
{/if}
All async stores expose their state as Maybe<T, E> = null | T | E where E extends Error:
null — no data yet (loading, cleared, or not fetched)T — resolved valueE — fetch errorStores share a common lifecycle:
subscriberevalidate ms (default 300_000)stale: true keeps the previous value visible while revalidatingbind: Readable<unknown | null> nullifies the store when the bound store becomes null (useful for tying data to a session/user)persist: string mirrors the store into localStorage under the given keyvalue<T>(options)A single asynchronous value.
import { value } from 'svas'
const profile = value<Profile>({
get: () => api.getProfile(),
revalidate: 60_000,
persist: 'profile',
session: false,
default: null,
bind: session
})
Interface:
class Value<T> implements Writable<T | null> {
subscribe(run): Unsubscriber // triggers sync on first subscribe
set(value: T | null): void
update(updater): void
extract(): T | null // synchronous read
sync(): void // revalidate if stale
}
Options:
get?: () => Promise<T | Error> — fetcherrevalidate?: number — ms before re-fetching (default 300_000)persist?: string — storage keysession?: boolean — use sessionStorage instead of localStoragedefault?: T — initial value when no persisted data existsbind?: Readable<unknown | null> — reset to default when bound store is nullvalues<T>(options)A keyed cache of asynchronous values. Each key has its own Readable<Maybe<T>> lifecycle.
import { values } from 'svas'
const products = values<Product>({
get: (id) => api.getProduct(id),
revalidate: 60_000,
permanent: false,
stale: true,
persist: 'products',
bind: session
})
const product = products.get('42')
Interface:
class Values<T, E extends Error = Error> {
get(key, options?: { fetch?: boolean }): Readable<Maybe<T, E>>
set(key, value: T | E, options?: { stash?: boolean }): Readable<T | E>
reset(key): Maybe<T | E> // restore stashed persistent value
extract(key): T | null // synchronous read, ignores errors
delete(key): void
clear(): void
}
Options:
get?: (key: string) => Promise<T | E> — per-key fetcherrevalidate?: number — ms before re-fetching (default 300_000)stale?: boolean — keep value while revalidating (default false)persist?: string — storage key for the whole mappermanent?: boolean — don't revalidate persisted entries on startup; combine with revalidate: Infinity to disable revalidation entirelybind?: Readable<unknown | null> — clear the cache when bound store is nullThe stash flag marks a value as transient: the previously persisted value is remembered and can be restored with reset(key). Useful for optimistic updates.
collection<T>(options)A list of identifiable items, optionally backed by a values store so individual items can be observed independently.
Items must extend Identifiable:
interface Identifiable {
id: string
}
import { collection, values } from 'svas'
const items = values<Todo>({ persist: 'todos' })
const todos = collection<Todo>({
get: () => api.listTodos(),
values: items,
revalidate: 60_000,
stale: true,
persist: 'todos:list',
bind: session
})
Interface:
class Collection<T extends Identifiable, E extends Error = Error>
implements Readable<Maybe<T[], E>>
{
subscribe(run): Unsubscriber
add(item: T): void
set(item: T, options?: { add?: boolean }): void
update(id, updater: (item) => T | void, options?): void
delete(id): void
get(id, options?): Readable<Maybe<T, E>> // requires `values`
extract(id): T | null // requires `values`
replace(items: T[]): void
sync(): this
fetch(): Promise<this>
}
Options:
get?: () => Promise<T[] | E> — list fetcherrevalidate?: number — ms before re-fetching (default 300_000)stale?: boolean — keep list while revalidating (default false)values?: Values<T, E> — side store for per-item subscriptionspersist?: string — storage key for the listbind?: Readable<unknown | null> — clear on bound store nullificationWhen a values store is provided, mutations to the collection are mirrored into it, so components subscribed via get(id) update without re-fetching.
ok(value)Type guard narrowing a Maybe<T> to T:
import { ok } from 'svas'
if (ok($user)) {
$user.name // typed as T, excludes null | Error
}
ensure(store)Synchronously reads a store and throws if the value is null or an Error. Use in code paths where the value is known to be resolved.
import { ensure } from 'svas'
const user = ensure(userStore)
having(store)Returns a promise that resolves with the first non-null, non-error value of the store.
import { having } from 'svas'
const user = await having(userStore) // T
awaited(store)Returns a promise that resolves with the first non-null value of the store, including errors.
import { awaited } from 'svas'
const result = await awaited(userStore) // T | Error
once(store, condition)Returns a promise that resolves with the first value satisfying condition. Building block for having and awaited.
import { once } from 'svas'
const ready = await once(status, (s) => s === 'ready')
combined(...stores)Combines multiple Maybe stores into one. Resolves to the tuple of values when all are non-null, to the first encountered Error, or to null while any is still loading.
import { combined } from 'svas'
const both = combined(user, settings)
// $both: [User, Settings] | null | Error
sync(store, item, options?)Merges a versioned item into a Collection or Value based on VERSION, and optionally deletes when DELETED is set. Useful for applying server events.
interface Comparable {
id: string
VERSION: number
DELETED?: number | null
}
import { sync } from 'svas'
sync(todos, incoming) // insert/update if newer
sync(todos, incoming, { delete: false }) // ignore tombstones
AsyncSvelte component for rendering a Maybe store with waiting, error, and awaited snippets.
While the store has no value, Async renders waiting, and nothing where it is not given: what loading looks like is the app's to declare. An error it renders with error, or a default message with a reload button — nothing, with silent.
<script lang="ts">
import { Async } from 'svas'
</script>
<Async store={user}>
{#snippet waiting()}<Spinner />{/snippet}
{#snippet error(e)}<ErrorView {e} />{/snippet}
{#snippet awaited(value)}<Profile {value} />{/snippet}
</Async>
import type { Maybe } from 'svas'
type Maybe<T, E extends Error = Error> = null | T | E
FAQs
Svelte stores for asynchronous data with fetching, caching, revalidation, and persistence.
We found that svas demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
It has been one year since Shai-Hulud made its first appearance on npm.

Research
/Security News
Operators behind PolinRider used a compromised GitHub account to plant malware in four development versions of a Packagist package with 700,000+ downloads.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.