feat: Implement basic general folder support (#2051)

- Related: #1748
This commit is contained in:
Dermot Duffy
2025-05-21 19:59:21 -07:00
committed by GitHub
parent 2eb0d9e35e
commit c6a4c8aea2
350 changed files with 12837 additions and 4509 deletions
+52
View File
@@ -0,0 +1,52 @@
import { CacheInterface } from './types.js';
export class CacheBase<Key, Value> implements CacheInterface<Key, Value> {
private _cache: Map<Key, Value>;
constructor(cache: Map<Key, Value>) {
this._cache = cache;
}
/**
* Determine if the cache has a given id.
* @param key
* @returns `true` if the id is in the cache, `false` otherwise.
*/
public has(key: Key): boolean {
return this._cache.has(key);
}
public entries(): MapIterator<[Key, Value]> {
return this._cache.entries();
}
public delete(key: Key): boolean {
return this._cache.delete(key);
}
public clear(): void {
this._cache.clear();
}
/**
* Get resolved media information given an id.
* @param key The id.
* @returns The `ResolvedMedia` for this id.
*/
public get(key: Key): Value | null {
return this._cache.get(key) ?? null;
}
public getMatches(predicate: (arg: Value) => boolean): Value[] {
return [...this._cache.values()].filter(predicate);
}
/**
* Add a given ResolvedMedia to the cache.
* @param key The id for the object.
* @param resolvedMedia The `ResolvedMedia` object.
*/
public set(key: Key, val: Value): void {
this._cache.set(key, val);
}
}
+7
View File
@@ -0,0 +1,7 @@
import { CacheBase } from './base';
export class Cache<Key, Value> extends CacheBase<Key, Value> {
constructor() {
super(new Map());
}
}
+8
View File
@@ -0,0 +1,8 @@
import { CacheBase } from './base';
import { EqualityMap } from './equality-map';
export class EqualityCache<Key, Value> extends CacheBase<Key, Value> {
constructor() {
super(new EqualityMap());
}
}
+80
View File
@@ -0,0 +1,80 @@
import { isEqual } from 'lodash-es';
interface EqualityMapItem<Key, Value> {
key: Key;
value: Value;
}
/** A simple equality based map. This is not performant and should be used for
* small datasets only.
*/
export class EqualityMap<Key, Value> implements Map<Key, Value> {
private _data: EqualityMapItem<Key, Value>[] = [];
get [Symbol.toStringTag](): string {
return 'EqualityMap';
}
public has(key: Key): boolean {
return !!this.get(key);
}
public get(searchKey: Key): Value | undefined {
for (const pair of this._data) {
if (isEqual(pair.key, searchKey)) {
return pair.value;
}
}
return undefined;
}
public set(key: Key, value: Value): this {
this.delete(key);
this._data.push({ key, value });
return this;
}
public delete(searchKey: Key): boolean {
for (let i = 0; i < this._data.length; i++) {
if (isEqual(this._data[i].key, searchKey)) {
this._data.splice(i, 1);
return true;
}
}
return false;
}
public clear(): void {
this._data = [];
}
public *entries(): MapIterator<[Key, Value]> {
for (const pair of this._data) {
yield [pair.key, pair.value];
}
}
public forEach(
callbackfn: (value: Value, key: Key, map: Map<Key, Value>) => void,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
thisArg?: any,
): void {
for (const pair of this._data) {
callbackfn.call(thisArg, pair.value, pair.key, this);
}
}
public get size(): number {
return this._data.length;
}
public [Symbol.iterator](): IterableIterator<[Key, Value]> {
return this.entries();
}
public keys(): IterableIterator<Key> {
return this._data.map((pair) => pair.key).values();
}
public values(): IterableIterator<Value> {
return this._data.map((pair) => pair.value).values();
}
}
+70
View File
@@ -0,0 +1,70 @@
import { CacheInterface } from './types';
import { EqualityCache } from './equality-cache';
interface ExpiringValue<Value> {
value: Value;
expires?: Date;
}
export class ExpiringEqualityCache<Key, Value> implements CacheInterface<Key, Value> {
protected _data: EqualityCache<Key, ExpiringValue<Value>> = new EqualityCache();
public get(key: Key): Value | null {
const value = this._data.get(key);
const now = new Date();
return value && (!value.expires || now <= value.expires) ? value.value : null;
}
public has(key: Key): boolean {
return !!this.get(key);
}
public set(key: Key, value: Value, expiry?: Date): void {
this._data.set(key, {
value: value,
expires: expiry,
});
// Clean up old requests on set.
this._expireOldValues();
}
public delete(key: Key): boolean {
return this._data.delete(key);
}
public clear(): void {
this._data.clear();
}
public *entries(): MapIterator<[Key, Value]> {
const now = new Date();
for (const [key, value] of this._data.entries()) {
if (!value.expires || now <= value.expires) {
yield [key, value.value];
}
}
}
public getMatches(predicate: (value: Value) => boolean): Value[] {
const out: Value[] = [];
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for (const [_key, value] of this.entries()) {
if (predicate(value)) {
out.push(value);
}
}
return out;
}
protected _expireOldValues(): void {
const now = new Date();
for (const [key, value] of this._data.entries()) {
if (value.expires && now > value.expires) {
this._data.delete(key);
}
}
}
}
+8
View File
@@ -0,0 +1,8 @@
import QuickLRU from 'quick-lru';
import { CacheBase } from './base';
export class LRUCache<Key, Value> extends CacheBase<Key, Value> {
constructor(maxSize: number) {
super(new QuickLRU({ maxSize }));
}
}
+9
View File
@@ -0,0 +1,9 @@
export interface CacheInterface<K, V> {
has(k: K): boolean;
get(k: K): V | null;
set(k: K, v: V): void;
delete(k: K): boolean;
clear(): void;
entries(): MapIterator<[K, V]>;
getMatches(predicate: (arg: V) => boolean): V[];
}