index
index<Term, Value>(family: AtomFamily<Value>, predicate: (value: Value, term: Term) => boolean, options?: IndexOptions<Term>): (term: Term) => Selector<Atom<Value>[]>valdres Create memoized reactive family filters
index creates a memoized selector for each search term. Each selector returns
the atoms in an atomFamily whose current values satisfy the predicate.
import { atomFamily, index, store } from "valdres"
const post = atomFamily<{ title: string; tags: string[] }, [string]>(null)
const postsByTag = index(
post,
(value, tag: string) => value.tags.includes(tag),
{ name: "postsByTag" },
)
const app = store()
app.set(post("one"), { title: "First post", tags: ["news"] })
app.get(postsByTag("news")) // [post("one")]
Reactive-filter semantics
Despite its name, index is a reactive filter rather than a materialized
database index. The first read for a term is O(n) in the number of family
members. After a member changes, its predicate selector is the only predicate
that recomputes. If its boolean result is unchanged, equality pruning stops
there in O(1). If the result flips, the term selector walks the family to
preserve the ordered array result, so a membership-changing update is O(n).
This is a good fit for modest collections and for queries whose predicate is more expensive than walking the family. For large, frequently updated collections that require sublinear writes, maintain a lookup structure in store state as part of the same transaction that changes the source family.
Term selectors are cached weakly. Repeated calls return the same selector while
a caller or live store dependency graph retains it, but querying many one-off
terms does not retain every selector and term for the lifetime of the index
function.
Term identity and keyOf
Terms use the same collision-safe structural key codec as atomFamily and
selectorFamily. Primitive types remain distinct, while plain Object property
order and Map/Set insertion order do not affect identity. Symbols, functions,
Promises, class instances, accessor properties, and cyclic structures are not
supported by default.
Use keyOf to derive supported identity for one of those terms, or to
intentionally group multiple term objects:
type Query = { tag: string; uiState: Map<string, unknown> }
const postsByQuery = index(
post,
(value, query: Query) => value.tags.includes(query.tag),
{ keyOf: query => query.tag },
)
keyOf receives the term and its result is encoded by the canonical family-key
codec.
See also
- atomFamily — create the source collection
- selectorFamily — create arbitrary keyed derivations
- selector — derive a single value