aboutsummaryrefslogtreecommitdiff
path: root/packages/server/src/cache
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2021-08-18 17:29:55 +0200
committerHampusM <hampus@hampusmat.com>2021-08-18 17:29:55 +0200
commitd1a1b7dc947063aef5f8375a6a1e03246b272c84 (patch)
treef5cb9bd6d4b5463d9d022026ac6fea87cb6ebe02 /packages/server/src/cache
parent6ed078de30a7bf35deace728857d1d293d59eb15 (diff)
Implemented caching for certain API endpoints, Added documentation & made backend-fixes
Diffstat (limited to 'packages/server/src/cache')
-rw-r--r--packages/server/src/cache/index.ts64
-rw-r--r--packages/server/src/cache/sources.ts314
2 files changed, 378 insertions, 0 deletions
diff --git a/packages/server/src/cache/index.ts b/packages/server/src/cache/index.ts
new file mode 100644
index 0000000..9bb4abd
--- /dev/null
+++ b/packages/server/src/cache/index.ts
@@ -0,0 +1,64 @@
+/**
+ * Utilities for managing server-side cache
+ *
+ * @module cache
+ */
+
+import { caching, Cache } from "cache-manager";
+import { cacheAllSources, CacheSource } from "./sources";
+import { CacheConfig } from "../types";
+
+export *as sources from "./sources";
+
+export class ServerCache {
+ private _cache: Cache;
+
+ public ready = false;
+
+ /**
+ * @param [config] - Cache configuration from the settings
+ */
+ constructor(config?: Omit<CacheConfig, "enabled">) {
+ this._cache = caching({
+ store: "memory",
+ max: config?.max || 5000000,
+ ttl: config?.ttl || 120,
+ refreshThreshold: config?.refreshThreshold || 80
+ });
+ }
+
+ /**
+ * Returns the cache value specified in the source & caches it if need be
+ *
+ * @template T - The constructor of a cache source
+ * @param Source - Information about where to get the value from
+ * @param args - Source arguments
+ * @returns A value from the cache
+ */
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ public async receive<T extends new(...args: any[]) => CacheSource>(Source: T, ...args: ConstructorParameters<T>): Promise<unknown> {
+ const source = new Source(...args);
+
+ const result = await this._cache.wrap(source.key(), () => source.func()) as T;
+
+ return source.post
+ ? source.post(result) as T
+ : result;
+ }
+
+ /**
+ * Initialize the cache.
+ * This will cache all of the available sources.
+ *
+ * @param git_dir - A git directory
+ */
+ public async init(git_dir: string): Promise<void> {
+ if(this.ready === true) {
+ throw(new Error("Cache has already been initialized!"));
+ }
+
+ await cacheAllSources(this, git_dir);
+
+ this.ready = true;
+ }
+} \ No newline at end of file
diff --git a/packages/server/src/cache/sources.ts b/packages/server/src/cache/sources.ts
new file mode 100644
index 0000000..9ee953d
--- /dev/null
+++ b/packages/server/src/cache/sources.ts
@@ -0,0 +1,314 @@
+import {
+ LogCommit,
+ Commit as APICommit,
+ Tag as APITag,
+ RepositorySummary,
+ Repository as APIRepository,
+ BranchSummary,
+ Branch as APIBranch
+} from "api";
+import { getBranch, getBranches, getCommit, getLogCommits, getRepositories, getRepository, getTags } from "../routes/api/v1/data";
+import { Branch, Commit, Repository, Tag } from "../git";
+import { ServerCache } from ".";
+
+/**
+ * Holds information about a cache key
+ *
+ * @abstract
+ */
+export abstract class CacheSource {
+ private _key?: string;
+
+ /**
+ * @param [key] - The name of the cache key
+ */
+ constructor(key?: string) {
+ this._key = key;
+ }
+
+ /**
+ * Returns the full cache key name
+ */
+ public key(): string {
+ return `repositories${this._key || ""}`;
+ }
+
+ /**
+ * Returns fresh data
+ */
+ public abstract func(): Promise<unknown> | unknown;
+
+ /**
+ * Returns the input but modified
+ * @param [input] - Variable to modify
+ */
+ public abstract post?(input: unknown): unknown;
+}
+
+/**
+ * Cache source for log commits
+ *
+ * @extends CacheSource
+ */
+export class LogCommitsSource extends CacheSource {
+ private _count: number;
+ private _repository: Repository;
+
+ /**
+ * @param repository - An instance of a repository
+ * @param [count] - The number of commits to return
+ */
+ constructor(repository: Repository, count = 20) {
+ super(`_${repository.name.short}_${repository.branch}_commits`);
+
+ this._repository = repository;
+ this._count = count;
+ }
+
+ /**
+ * @returns An array of log commits
+ */
+ public readonly func = async(): Promise<LogCommit[]> => getLogCommits(await this._repository.commits(true));
+
+ /**
+ * @param commits - An array of log commits
+ * @returns A modified array of log commits
+ */
+ public readonly post = (commits: LogCommit[]): LogCommit[] => commits.slice(0, this._count);
+}
+
+/**
+ * Cache source for a commit
+ *
+ * @extends CacheSource
+ */
+export class CommitSource extends CacheSource {
+ private _commit: Commit;
+
+ /**
+ * @param repository - An instance of a repository
+ * @param commit - An instance of a commit
+ */
+ constructor(repository: Repository, commit: Commit) {
+ super(`_${repository.name.short}_${repository.branch}_commits_${commit.id}`);
+
+ this._commit = commit;
+ }
+
+ /**
+ * @returns An array of API commits
+ */
+ public readonly func = (): Promise<APICommit> => getCommit(this._commit);
+
+ public readonly post = undefined;
+}
+
+/**
+ * Cache source for tags
+ *
+ * @extends CacheSource
+ */
+export class TagsSource extends CacheSource {
+ private _tags: Tag[];
+
+ /**
+ * @param repository - An instance of a repository
+ * @param tags - An array of tag instances
+ */
+ constructor(repository: Repository, tags: Tag[]) {
+ super(`_${repository.name.short}_tags`);
+
+ this._tags = tags;
+ }
+
+ /**
+ * @returns An array of API tags
+ */
+ public readonly func = (): Promise<APITag[]> => getTags(this._tags);
+
+ public readonly post = undefined;
+}
+
+/**
+ * Cache source for repositories
+ *
+ * @extends CacheSource
+ */
+export class RepositoriesSource extends CacheSource {
+ private _repositories: Repository[];
+
+ /**
+ * @param repositories An array of repository instances
+ */
+ constructor(repositories: Repository[]) {
+ super();
+
+ this._repositories = repositories;
+ }
+
+ /**
+ * @returns An array of repository summaries
+ */
+ public readonly func = (): Promise<RepositorySummary[]> => getRepositories(this._repositories);
+
+ public readonly post = undefined;
+}
+
+/**
+ * Cache source for a repository
+ *
+ * @extends CacheSource
+ */
+export class RepositorySource extends CacheSource {
+ private _repository: Repository;
+
+ /**
+ * @param repository - An instance of a repository
+ */
+ constructor(repository: Repository) {
+ super(`_${repository.name.short}`);
+
+ this._repository = repository;
+ }
+
+ /**
+ * @returns A API repository
+ */
+ public readonly func = (): Promise<APIRepository> => getRepository(this._repository);
+
+ public readonly post = undefined;
+}
+
+/**
+ * Cache source for branches
+ *
+ * @extends CacheSource
+ */
+export class BranchesSource extends CacheSource {
+ private _branches: Branch[];
+
+ /**
+ * @param repository - An instance of a repository
+ * @param branches - An array of branch instances
+ */
+ constructor(repository: Repository, branches: Branch[]) {
+ super(`_${repository.name.short}_branches`);
+
+ this._branches = branches;
+ }
+
+ /**
+ * @returns An array of branch summaries
+ */
+ public readonly func = (): BranchSummary[] => getBranches(this._branches);
+
+ public readonly post = undefined;
+}
+
+/**
+ * Cache source for a branch
+ *
+ * @extends CacheSource
+ */
+export class BranchSource extends CacheSource {
+ private _branch: Branch;
+
+ /**
+ * @param repository - An instance of a repository
+ * @param branch - An instance of a branch
+ */
+ constructor(repository: Repository, branch: Branch) {
+ super(`_${repository.name.short}_branches_${branch.name}`);
+
+ this._branch = branch;
+ }
+
+ /**
+ * @returns A API branch
+ */
+ public readonly func = (): Promise<APIBranch> => getBranch(this._branch);
+
+ public readonly post = undefined;
+}
+
+/**
+ * Caches all of the available cache sources
+ *
+ * @param cache - A server cache instance
+ * @param git_dir - A git directory
+ */
+export async function cacheAllSources(cache: ServerCache, git_dir: string): Promise<void> {
+ console.log("Initializing cache... this may take a while\n");
+
+ const repositories = await Repository.openAll(git_dir);
+
+ process.stdout.write("Caching repositories... ");
+
+ await cache.receive(RepositoriesSource, repositories);
+
+ process.stdout.write("done\n\n");
+
+ for(const repository of repositories) {
+ console.log(repository.name.short);
+
+ process.stdout.write("-> Caching repository... ");
+
+ await cache.receive(RepositorySource, repository);
+
+ process.stdout.write("done\n");
+
+ process.stdout.write("-> Caching tags... ");
+
+ const tags = await repository.tags();
+
+ await cache.receive(TagsSource, repository, tags);
+
+ process.stdout.write("done\n");
+
+ process.stdout.write("-> Caching branches... ");
+
+ const branches = await repository.branches();
+
+ await cache.receive(BranchesSource, repository, branches);
+
+ process.stdout.write("done\n");
+
+ for(const branch of branches) {
+ const branch_repository = await branch.repository();
+
+ console.log(`\n-> ${branch.name}`);
+
+ process.stdout.write(" -> Caching branch... ");
+
+ await cache.receive(BranchSource, branch_repository, branch);
+
+ process.stdout.write("done\n");
+
+ process.stdout.write(" -> Caching log commits... ");
+
+ await cache.receive(LogCommitsSource, branch_repository, 0);
+
+ process.stdout.write("done\n");
+
+ const message = " -> Caching commits... ";
+ process.stdout.write(message);
+
+ const commits = await branch_repository.commits(true);
+
+ const commits_cnt = commits.length;
+ for(const commit of commits) {
+ process.stdout.clearLine(1);
+ process.stdout.cursorTo(message.length);
+ process.stdout.write(`${Math.round(commits.indexOf(commit) / commits_cnt * 100)}%`);
+
+ await cache.receive(CommitSource, branch_repository, commit);
+ }
+
+ process.stdout.clearLine(1);
+ process.stdout.cursorTo(message.length);
+ process.stdout.write("done\n");
+ }
+
+ console.log("");
+ }
+} \ No newline at end of file