/* * Copyright 2022 Andrew Aylett * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { Generators } from './index'; function* addKey( entries: Iterable, sortKey: (v: V) => S, ): IterableIterator<[S, V]> { for (const v of entries) { yield [sortKey(v), v]; } } function* removeKey(entries: Iterable<[unknown, V]>): IterableIterator { for (const [_, v] of entries) { yield v; } } export function sortByKey( collection: Iterable, sortKey: (v: V) => S, comparator: (a: S, b: S) => number, ): Iterable; export function sortByKey( collection: Iterable, sortKey: (v: V) => number, ): Iterable; export function sortByKey( collection: Iterable, sortKey: (v: V) => S, comparator?: (a: S, b: S) => number, ): Iterable { const withKeys = addKey(collection, sortKey); // comparator may only be undefined if S is number, but TS can't validate that const compareFn = comparator ? ([a]: [S, V], [b]: [S, V]) => comparator(a, b) : ((([a]: [number, V], [b]: [number, V]) => Math.sign(a - b)) as unknown as ( [a]: [S, V], [b]: [S, V], ) => number); const sorted = [...withKeys].sort(compareFn); return removeKey(sorted); } export const gc = async ( entries: IterableIterator<[K, V]>, sortKey: (v: V) => S, sortKeyComparer: (a: S, b: S) => number, weigher: (v: V) => Promise, limit: number, deleter: (k: K) => void, ): Promise => { // Latest first const sorted = sortByKey( entries, ([_, v]) => sortKey(v), (a: S, b: S) => -sortKeyComparer(a, b), ); let runningSize = 0; for (const [key, value] of sorted) { const size = await weigher(value); if (runningSize + size > limit) { deleter(key); continue; } runningSize += size; } return runningSize; }; export class GcMap extends Map { gc( limit: number, sortKey: (v: V) => S, weigher: (v: V) => Promise, sortKeyComparer: (a: S, b: S) => number, ): Promise { const entries = this.entries(); const deleter = this.delete.bind(this); return gc(entries, sortKey, sortKeyComparer, weigher, limit, deleter); } async weight(weigher: (v: V) => Promise): Promise { const weights = await Promise.all( Generators.map(this.values(), weigher), ); return weights.reduce((a, b) => a + b); } }