Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
TypeScript
at icy/tmrrpn
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129// optimistic overlays for eventually-consistent bobbin reads: mutations commit// locally (the pds write is authoritative) and reconcile on natural reloads.// `key` scopes an overlay to its subject so reused components drop it.// TODO(bobbin): read-your-writes (serve at-or-after a commit rev) would let// mutations invalidate loads immediately instead of waiting for navigation.
interface OptimisticCountOptions { key: () => string; loaded: () => number | null | undefined;}
export interface OptimisticCount { readonly value: number; readonly failed: boolean; adjust(delta: number): void; fail(): void; resetFailure(): void;}
export const createOptimisticCount = (options: OptimisticCountOptions): OptimisticCount => { let failed = $state(false); // a bump is a bound: fresher data may pass it, never regress across it. let held = $state<null | { key: string; value: number; up: boolean }>(null); const currentKey = $derived(options.key()); const loaded = $derived(Math.max(0, options.loaded() ?? 0));
const value = $derived.by(() => { if (held === null || held.key !== currentKey) return loaded; return held.up ? Math.max(loaded, held.value) : Math.min(loaded, held.value); });
$effect(() => { if (held === null) return; const caughtUp = held.up ? loaded >= held.value : loaded <= held.value; if (held.key !== currentKey || caughtUp) held = null; });
return { get value() { return value; }, get failed() { return failed; }, adjust(delta) { failed = false; held = { key: currentKey, value: Math.max(0, value + delta), up: delta > 0 }; }, fail() { failed = true; }, resetFailure() { failed = false; } };};
interface OptimisticRelationOptions { key: () => string; loadedRkey: () => string | null | undefined;}
export interface OptimisticRelation { readonly rkey: string | null; readonly known: boolean; readonly active: boolean; readonly loading: boolean; readonly failed: boolean; begin(): void; created(rkey: string): void; deleted(): void; fail(): void; resetFailure(): void;}
export const createOptimisticRelation = ( options: OptimisticRelationOptions): OptimisticRelation => { let status = $state<"idle" | "loading" | "failed">("idle"); let committed = $state<null | { key: string; rkey: string | null }>(null); const currentKey = $derived(options.key()); const loaded = $derived(options.loadedRkey()); const rkey = $derived(committed?.key === currentKey ? committed.rkey : (loaded ?? null)); const known = $derived(loaded !== undefined || committed?.key === currentKey); const active = $derived(rkey !== null);
$effect(() => { if (committed === null) return; if (committed.key !== currentKey || loaded === committed.rkey) { committed = null; status = "idle"; } });
const set = (next: string | null): void => { status = "idle"; committed = { key: currentKey, rkey: next }; };
return { get rkey() { return rkey; }, get known() { return known; }, get active() { return active; }, get loading() { return status === "loading"; }, get failed() { return status === "failed"; }, begin() { status = "loading"; }, created: set, deleted: () => set(null), fail() { status = "failed"; }, resetFailure() { status = "idle"; } };};