summary refs log tree commit diff
path: root/src/util/cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/util/cache')
-rw-r--r--src/util/cache/Cache.ts85
-rw-r--r--src/util/cache/EntityCache.ts162
-rw-r--r--src/util/cache/LocalCache.ts94
-rw-r--r--src/util/cache/index.ts3
4 files changed, 0 insertions, 344 deletions
diff --git a/src/util/cache/Cache.ts b/src/util/cache/Cache.ts
deleted file mode 100644

index fb66c2e3..00000000 --- a/src/util/cache/Cache.ts +++ /dev/null
@@ -1,85 +0,0 @@ -/* - Fosscord: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Fosscord and Fosscord Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { EntityMetadata, FindOptionsWhere } from "typeorm"; -import { LocalCache } from "./LocalCache"; - -declare module "typeorm" { - interface BaseEntity { - metadata?: EntityMetadata; - cache: CacheManager; - } -} - -export type BaseEntityWithId = { id: string; [name: string]: any }; -export type Criteria = - | string - | string[] - | number - | number[] - | FindOptionsWhere<never>; - -export interface Cache { - get(id: string): BaseEntityWithId | undefined; - set(id: string, entity: BaseEntityWithId): this; - find(options: Record<string, never>): BaseEntityWithId | undefined; - filter(options: Record<string, never>): BaseEntityWithId[]; - delete(id: string): boolean; -} - -export class CacheManager { - // last access time to automatically remove old entities from cache after 5 minutes of inactivity (to prevent memory leaks) - cache: Cache; - - constructor() { - this.cache = new LocalCache(); - // TODO: Config.get().cache.redis; - } - - delete(id: string) { - return this.cache.delete(id); - } - - insert(entity: BaseEntityWithId) { - if (!entity.id) return; - - return this.cache.set(entity.id, entity); - } - - find(options?: Record<string, never>, select?: string[] | undefined) { - if (!options) return null; - const entity = this.cache.find(options); - if (!entity) return null; - if (!select) return entity; - - const result = {}; - for (const prop of select) { - // @ts-ignore - result[prop] = entity[prop]; - } - - // @ts-ignore - return entity.constructor.create(result); - } - - filter(options: Record<string, never>) { - return this.cache.filter(options); - } -} diff --git a/src/util/cache/EntityCache.ts b/src/util/cache/EntityCache.ts deleted file mode 100644
index ba1e5bd8..00000000 --- a/src/util/cache/EntityCache.ts +++ /dev/null
@@ -1,162 +0,0 @@ -/* - Fosscord: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Fosscord and Fosscord Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ -/* eslint-disable */ -import { - DataSource, - FindOneOptions, - EntityNotFoundError, - FindOptionsWhere, -} from "typeorm"; -import { QueryDeepPartialEntity } from "typeorm/query-builder/QueryPartialEntity"; -import { BaseClassWithId } from "../entities/BaseClass"; -import { Config, getDatabase, RabbitMQ } from "../util"; -import { CacheManager } from "./Cache"; - -function getObjectKeysAsArray(obj?: Record<string, any>) { - if (!obj) return []; - if (Array.isArray(obj)) return obj; - return Object.keys(obj); -} - -export type ThisType<T> = { - new (): T; -} & typeof BaseEntityCache; - -interface BaseEntityCache { - constructor: typeof BaseEntityCache; -} - -// @ts-ignore -class BaseEntityCache extends BaseClassWithId { - static cache: CacheManager; - static cacheEnabled: boolean; - - public get metadata() { - return getDatabase()?.getMetadata(this.constructor)!; - } - - static useDataSource(dataSource: DataSource | null) { - super.useDataSource(dataSource); - const isMultiThreaded = - process.env.EVENT_TRANSMISSION === "process" || RabbitMQ.connection; - this.cacheEnabled = Config.get().cache.enabled ?? !isMultiThreaded; - if (Config.get().cache.redis) return; // TODO: Redis cache - if (!this.cacheEnabled) return; - this.cache = new CacheManager(); - } - - static async findOne<T extends BaseEntityCache>( - this: ThisType<T>, - options: FindOneOptions<T>, - ) { - // @ts-ignore - if (!this.cacheEnabled) return super.findOne(options); - let select = getObjectKeysAsArray(options.select); - - if (!select.length) { - // get all columns that are marked as select - getDatabase() - ?.getMetadata(this) - .columns.forEach((x) => { - if (!x.isSelect) return; - select.push(x.propertyName); - }); - } - if (options.relations) { - select.push(...getObjectKeysAsArray(options.relations)); - } - - const cacheResult = this.cache.find(options.where as never, select); - if (cacheResult) { - const hasAllProps = select.every((key) => { - if (key.includes(".")) return true; // @ts-ignore - return cacheResult[key] !== undefined; - }); - // console.log(`[Cache] get ${cacheResult.id} from ${cacheResult.constructor.name}`,); - if (hasAllProps) return cacheResult; - } - - // @ts-ignore - const result = await super.findOne<T>(options); - if (!result) return null; - - this.cache.insert(result as any); - - return result; - } - - static async findOneOrFail<T extends BaseEntityCache>( - this: ThisType<T>, - options: FindOneOptions<T>, - ) { - const result = await this.findOne<T>(options); - if (!result) throw new EntityNotFoundError(this, options); - return result; - } - - save() { - if (this.constructor.cacheEnabled) this.constructor.cache.insert(this); - return super.save(); - } - - remove() { - if (this.constructor.cacheEnabled) - this.constructor.cache.delete(this.id); - return super.remove(); - } - - static async update<T extends BaseEntityCache>( - this: ThisType<T>, - criteria: FindOptionsWhere<T>, - partialEntity: QueryDeepPartialEntity<T>, - ) { - // @ts-ignore - const result = super.update<T>(criteria, partialEntity); - if (!this.cacheEnabled) return result; - - const entities = this.cache.filter(criteria as never); - for (const entity of entities) { - // @ts-ignore - partialEntity.id = entity.id; - this.cache.insert(partialEntity as never); - } - - return result; - } - - static async delete<T extends BaseEntityCache>( - this: ThisType<T>, - criteria: FindOptionsWhere<T>, - ) { - // @ts-ignore - const result = super.delete<T>(criteria); - if (!this.cacheEnabled) return result; - - const entities = this.cache.filter(criteria as never); - for (const entity of entities) { - this.cache.delete(entity.id); - } - - return result; - } -} - -// needed, because typescript can't infer the type of the static methods with generics -const EntityCache = BaseEntityCache as unknown as typeof BaseClassWithId; - -export { EntityCache }; diff --git a/src/util/cache/LocalCache.ts b/src/util/cache/LocalCache.ts deleted file mode 100644
index 9c4f23b3..00000000 --- a/src/util/cache/LocalCache.ts +++ /dev/null
@@ -1,94 +0,0 @@ -/* - Fosscord: A FOSS re-implementation and extension of the Discord.com backend. - Copyright (C) 2023 Fosscord and Fosscord Contributors - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see <https://www.gnu.org/licenses/>. -*/ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -import { BaseEntityWithId, Cache } from "./Cache"; - -export const cacheTimeout = 1000 * 60 * 5; - -export class LocalCache extends Map<string, BaseEntityWithId> implements Cache { - last_access = new Map<string, number>(); - - constructor() { - super(); - - setInterval(() => { - const now = Date.now(); - for (const [key, value] of this.last_access) { - if (now - value > cacheTimeout) { - this.delete(key); - this.last_access.delete(key); - } - } - }, cacheTimeout); - } - - set(key: string, value: BaseEntityWithId): this { - if (this.has(key)) { - this.update(key, value); - return this; - } - this.last_access.set(key, Date.now()); - return super.set(key, value as never); - } - - get(key: string) { - const value = super.get(key); - if (value) this.last_access.set(key, Date.now()); - return value; - } - - update(id: string, entity: BaseEntityWithId) { - const oldEntity = this.get(id); - if (!oldEntity) return; - for (const key in entity) { - // @ts-ignore - if (entity[key] === undefined) continue; // @ts-ignore - oldEntity[key] = entity[key]; - } - } - - find(options: Record<string, never>): BaseEntityWithId | undefined { - if (options.id && Object.keys(options).length === 1) { - return this.get(options.id); - } - for (const entity of this.values()) { - if (objectFulfillsQuery(entity, options)) return entity; - } - } - - filter(options: Record<string, never>): BaseEntityWithId[] { - const result = []; - for (const entity of this.values()) { - if (objectFulfillsQuery(entity, options)) { - result.push(entity); - } - } - return result; - } -} - -function objectFulfillsQuery( - entity: BaseEntityWithId, - options: Record<string, never>, -) { - for (const key in options) { - // @ts-ignore - if (entity[key] !== options[key]) return false; - } - return true; -} diff --git a/src/util/cache/index.ts b/src/util/cache/index.ts deleted file mode 100644
index 43f0aa96..00000000 --- a/src/util/cache/index.ts +++ /dev/null
@@ -1,3 +0,0 @@ -export * from "./EntityCache"; -export * from "./Cache"; -export * from "./LocalCache";