Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions src/intl-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class LRUCache<V> {
constructor(private readonly max = 100) {}

get(key: string): V | undefined {
if (lookups) {
if (lookups.length < maxLookups) lookups.push([this as LRUCache<unknown>, key, false])
else lookupsOverflowed = true
}
const value = this.#map.get(key)
if (value !== undefined) {
this.#map.delete(key)
Expand All @@ -31,6 +35,10 @@ class LRUCache<V> {
return value
}

has(key: string): boolean {
return this.#map.has(key)
}

set(key: string, value: V): void {
this.#map.delete(key)
this.#map.set(key, value)
Expand All @@ -46,6 +54,45 @@ class LRUCache<V> {

const registeredCaches: Array<LRUCache<unknown>> = []

// Each lookup records whether the render left its entry missing, as when constructing a formatter
// for an invalid locale throws. Looking up a missing entry changes nothing.
export type Lookups = Array<[LRUCache<unknown>, string, boolean]>

// Lookups made while rendering an element. Repeating them when the element skips a render
// leaves every cache in the same least-recently-used order as a full render would.
let lookups: Lookups | null = null
let lookupsOverflowed = false
// A render makes a handful of lookups. The cap bounds a recording left running by a render that
// threw; an overflowing recording is discarded.
const maxLookups = 64

export function startLookups(): void {
lookups = []
lookupsOverflowed = false
}

export function stopLookups(): Lookups | null {
const result = lookupsOverflowed ? null : lookups
lookups = null
if (result) {
for (const lookup of result) lookup[2] = !lookup[0].has(lookup[1])
}
return result
}

// Repeats recorded lookups in order. Returns false at the first entry a full render would find
// differently: no longer cached, or cached now when the render left it missing.
export function repeatLookups(recorded: Lookups): boolean {
for (const [cache, key, missing] of recorded) {
if (missing) {
if (cache.has(key)) return false
} else if (cache.get(key) === undefined) {
return false
}
}
return true
}

// Create a bounded cache that participates in `clearIntlCache` invalidation.
export function createCache<V>(max?: number): LRUCache<V> {
const cache = new LRUCache<V>(max)
Expand Down
Loading
Loading