import { Future } from "atom.io/foundations/future" import { Tracker, type Transceiver } from "./mutable/index.ts" import { closeOperation, openOperation } from "./operation.ts" import { safeCompute } from "./safe-compute.ts" import { evictDownstreamFromAtom, evictDownstreamFromSelector, } from "./set-state/evict-downstream.ts" import type { PureSelector, ReadableState } from "./state-types.ts" import type { Store } from "./store/index.ts" import { isChildStore } from "./transaction/index.ts" import { deferTransactionFutureRecomputation, retainTransactionPreviousValue, } from "./transaction/transaction-notification-batch.ts" export function writeToCache( target: Store, state: ReadableState, value: E | T, ): E | T export function writeToCache, E>( target: Store, state: ReadableState, value: T, ): Future> export function writeToCache( target: Store, state: ReadableState, value: E | T, ): E | Future | T { const { key, subject, type } = state const currentValue = target.valueMap.get(key) if (currentValue instanceof Future && !currentValue.done) { const future = currentValue if (value instanceof Promise) { future.use(value) return future } target.valueMap.set(key, value) return value } if (value instanceof Promise) { const future = new Future(value) target.valueMap.set(key, future) future .then(function handleResolvedFuture(resolved) { const current = target.valueMap.get(key) if (current === future) { openOperation(target, state) writeToCache(target, state, resolved) // oxlint-disable-next-line typescript/switch-exhaustiveness-check switch (type) { case `atom`: evictDownstreamFromAtom(target, state) break case `readonly_pure_selector`: case `writable_pure_selector`: evictDownstreamFromSelector(target, key) break // held selectors, by definition, don't become promises } closeOperation(target) subject.next({ newValue: resolved, oldValue: future }) } }) .catch((thrown) => { target.logger.error(`💥`, state.type, key, `rejected:`, thrown) }) return future } target.logger.info(`📝`, state.type, state.key, `writing to cache`, value) target.valueMap.set(key, value) return value } /** * @param target - the newest layer of the store * @param state - the state to read from cache * @param mut - whether the value is intended to be mutable * @returns the state's current value */ export function readFromCache( target: Store, state: ReadableState, mut: `mut` | undefined, ): E | T { target.logger.info(`📖`, state.type, state.key, `reading cached value`) let value = target.valueMap.get(state.key) as E | T const mayNeedToBeCopied = mut === `mut` && state.type === `mutable_atom` && isChildStore(target) if (mayNeedToBeCopied) { const mutableAtom = state const { parent } = target if (target.valueMap.hasOwn(mutableAtom.key)) { return value } const parentValue = parent.valueMap.get(mutableAtom.key) as T & Transceiver target.logger.info(`📃`, `atom`, mutableAtom.key, `copying`) const jsonValue = parentValue.toJSON() const copiedValue = mutableAtom.class.fromJSON(jsonValue) target.valueMap.set(mutableAtom.key, copiedValue) new Tracker(mutableAtom, parent) value = copiedValue } return value } export function evictCachedValue(target: Store, key: string): void { const currentValue = target.valueMap.get(key) if (currentValue instanceof Future) { if (isChildStore(target) && !target.valueMap.hasOwn(key)) { if (target.operation.open) { target.operation.prev.set(key, currentValue) } target.valueMap.delete(key) target.logger.info(`🗑`, `state`, key, `evicted from child store`) return } const readonly = target.readonlySelectors.get(key) const writable = target.writableSelectors.get(key) const selector = readonly ?? writable const recompute = () => { const recomputed = safeCompute(target, selector as PureSelector) // This is needed for certain edge cases where a loadable selector is downstream of another loadable selector currentValue.use(recomputed) } if (deferTransactionFutureRecomputation(target, key, recompute)) return recompute() return } if (target.operation.open) { target.operation.prev.set(key, currentValue) } retainTransactionPreviousValue(target, key, currentValue) target.valueMap.delete(key) target.logger.info(`🗑`, `state`, key, `evicted`) }