@tanstack/store
Version:
Framework agnostic type-safe store w/ reactive framework adapters
304 lines (281 loc) • 7.17 kB
text/typescript
/* eslint-disable */
// Adapted from Alien Signals
// https://github.com/stackblitz/alien-signals/
export interface ReactiveNode {
deps?: Link
depsTail?: Link
subs?: Link
subsTail?: Link
flags: ReactiveFlags
}
export interface Link {
version: number
dep: ReactiveNode
sub: ReactiveNode
prevSub: Link | undefined
nextSub: Link | undefined
prevDep: Link | undefined
nextDep: Link | undefined
}
interface Stack<T> {
value: T
prev: Stack<T> | undefined
}
export const enum ReactiveFlags {
None = 0,
Mutable = 1,
Watching = 2,
RecursedCheck = 4,
Recursed = 8,
Dirty = 16,
Pending = 32,
}
/*@__NO_SIDE_EFFECTS__*/
export function createReactiveSystem({
update,
notify,
unwatched,
}: {
update(sub: ReactiveNode): boolean
notify(sub: ReactiveNode): void
unwatched(sub: ReactiveNode): void
}) {
return {
link,
unlink,
propagate,
checkDirty,
shallowPropagate,
}
function link(dep: ReactiveNode, sub: ReactiveNode, version: number): void {
const prevDep = sub.depsTail
if (prevDep !== undefined && prevDep.dep === dep) {
return
}
const nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps
if (nextDep !== undefined && nextDep.dep === dep) {
nextDep.version = version
sub.depsTail = nextDep
return
}
const prevSub = dep.subsTail
if (
prevSub !== undefined &&
prevSub.version === version &&
prevSub.sub === sub
) {
return
}
const newLink =
(sub.depsTail =
dep.subsTail =
{
version,
dep,
sub,
prevDep,
nextDep,
prevSub,
nextSub: undefined,
})
if (nextDep !== undefined) {
nextDep.prevDep = newLink
}
if (prevDep !== undefined) {
prevDep.nextDep = newLink
} else {
sub.deps = newLink
}
if (prevSub !== undefined) {
prevSub.nextSub = newLink
} else {
dep.subs = newLink
}
}
function unlink(link: Link, sub = link.sub): Link | undefined {
const dep = link.dep
const prevDep = link.prevDep
const nextDep = link.nextDep
const nextSub = link.nextSub
const prevSub = link.prevSub
if (nextDep !== undefined) {
nextDep.prevDep = prevDep
} else {
sub.depsTail = prevDep
}
if (prevDep !== undefined) {
prevDep.nextDep = nextDep
} else {
sub.deps = nextDep
}
if (nextSub !== undefined) {
nextSub.prevSub = prevSub
} else {
dep.subsTail = prevSub
}
if (prevSub !== undefined) {
prevSub.nextSub = nextSub
} else if ((dep.subs = nextSub) === undefined) {
unwatched(dep)
}
return nextDep
}
function propagate(link: Link): void {
let next = link.nextSub
let stack: Stack<Link | undefined> | undefined
top: do {
const sub = link.sub
let flags = sub.flags
if (
!(
flags &
(ReactiveFlags.RecursedCheck |
ReactiveFlags.Recursed |
ReactiveFlags.Dirty |
ReactiveFlags.Pending)
)
) {
sub.flags = flags | ReactiveFlags.Pending
} else if (
!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
) {
flags = ReactiveFlags.None
} else if (!(flags & ReactiveFlags.RecursedCheck)) {
sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending
} else if (
!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) &&
isValidLink(link, sub)
) {
sub.flags = flags | (ReactiveFlags.Recursed | ReactiveFlags.Pending)
flags &= ReactiveFlags.Mutable
} else {
flags = ReactiveFlags.None
}
if (flags & ReactiveFlags.Watching) {
notify(sub)
}
if (flags & ReactiveFlags.Mutable) {
const subSubs = sub.subs
if (subSubs !== undefined) {
const nextSub = (link = subSubs).nextSub
if (nextSub !== undefined) {
stack = { value: next, prev: stack }
next = nextSub
}
continue
}
}
if ((link = next!) !== undefined) {
next = link.nextSub
continue
}
while (stack !== undefined) {
link = stack.value!
stack = stack.prev
if (link !== undefined) {
next = link.nextSub
continue top
}
}
break
} while (true)
}
function checkDirty(link: Link, sub: ReactiveNode): boolean {
let stack: Stack<Link> | undefined
let checkDepth = 0
let dirty = false
top: do {
const dep = link.dep
const flags = dep.flags
if (sub.flags & ReactiveFlags.Dirty) {
dirty = true
} else if (
(flags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
(ReactiveFlags.Mutable | ReactiveFlags.Dirty)
) {
if (update(dep)) {
const subs = dep.subs!
if (subs.nextSub !== undefined) {
shallowPropagate(subs)
}
dirty = true
}
} else if (
(flags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
(ReactiveFlags.Mutable | ReactiveFlags.Pending)
) {
if (link.nextSub !== undefined || link.prevSub !== undefined) {
stack = { value: link, prev: stack }
}
link = dep.deps!
sub = dep
++checkDepth
continue
}
if (!dirty) {
const nextDep = link.nextDep
if (nextDep !== undefined) {
link = nextDep
continue
}
}
while (checkDepth--) {
const firstSub = sub.subs!
const hasMultipleSubs = firstSub.nextSub !== undefined
if (hasMultipleSubs) {
link = stack!.value
stack = stack!.prev
} else {
link = firstSub
}
if (dirty) {
if (update(sub)) {
if (hasMultipleSubs) {
shallowPropagate(firstSub)
}
sub = link.sub
continue
}
dirty = false
} else {
sub.flags &= ~ReactiveFlags.Pending
}
sub = link.sub
const nextDep = link.nextDep
if (nextDep !== undefined) {
link = nextDep
continue top
}
}
return dirty
} while (true)
}
function shallowPropagate(link: Link): void {
do {
const sub = link.sub
const flags = sub.flags
if (
(flags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===
ReactiveFlags.Pending
) {
sub.flags = flags | ReactiveFlags.Dirty
if (
(flags & (ReactiveFlags.Watching | ReactiveFlags.RecursedCheck)) ===
ReactiveFlags.Watching
) {
notify(sub)
}
}
} while ((link = link.nextSub!) !== undefined)
}
function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
let link = sub.depsTail
while (link !== undefined) {
if (link === checkLink) {
return true
}
link = link.prevDep
}
return false
}
}