Skip to content

Commit

Permalink
Add CacheProvider<TEnum> utility class (#23)
Browse files Browse the repository at this point in the history
Resolves #10 and #25
  • Loading branch information
drebrez authored Jan 29, 2024
1 parent 162ba47 commit 364299f
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 5 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- added `CacheProvider<TEnum>` utility class
- export the `StandardEnum<T>` type

## [0.3.1] - 2024-01-19

- added export `lib/string` and `lib/guid`
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./lib/cacheProvider";
export * from "./lib/date";
export * from "./lib/enum";
export * from "./lib/guid";
export * from "./lib/localStorage";
export * from "./lib/string";
export * from "./lib/guid";
83 changes: 83 additions & 0 deletions src/lib/cacheProvider.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { CacheProvider } from "./cacheProvider";

enum CacheContainer {
Colors,
Seasons,
}

const redColor = { hex: "#FF0000", rgb: "255,0,0" };
const greenColor = { hex: "#00FF00", rgb: "0,255,0" };
const blueColor = { hex: "#0000FF", rgb: "0,0,255" };
const seasons = ["Spring", "Summer", "Fall", "Winter"];

let cacheProvider: CacheProvider<CacheContainer>;

const insertTestData = () => {
cacheProvider.setObject(CacheContainer.Colors, "Red", redColor);
cacheProvider.setObject(CacheContainer.Colors, "Green", greenColor);
cacheProvider.setObject(CacheContainer.Colors, "Blue", blueColor);
cacheProvider.setObject(CacheContainer.Seasons, undefined, seasons);
};

describe("CacheProvider tests", () => {
beforeEach(() => {
cacheProvider = new CacheProvider<CacheContainer>(CacheContainer, "CacheContainer");
});

test.each([
[CacheContainer.Colors, null, "CacheContainer:Colors"],
[CacheContainer.Colors, undefined, "CacheContainer:Colors"],
[CacheContainer.Colors, "", "CacheContainer:Colors"],
[CacheContainer.Colors, "Red", "CacheContainer:Colors_Red"],
])("createCacheKey", (container, key, expected) => {
expect(cacheProvider["createCacheKey"](container, key as unknown as string)).toBe(expected);
});

test("setObject with key", () => {
expect(cacheProvider["cache"].size).toBe(0);
cacheProvider.setObject(CacheContainer.Colors, "Red", redColor);
expect(cacheProvider["cache"].size).toBe(1);
expect(cacheProvider["cache"].get("CacheContainer:Colors_Red")).toBe(redColor);
cacheProvider.setObject(CacheContainer.Colors, "Green", greenColor);
expect(cacheProvider["cache"].size).toBe(2);
expect(cacheProvider["cache"].get("CacheContainer:Colors_Green")).toBe(greenColor);
cacheProvider.setObject(CacheContainer.Colors, "Blue", blueColor);
expect(cacheProvider["cache"].size).toBe(3);
expect(cacheProvider["cache"].get("CacheContainer:Colors_Blue")).toBe(blueColor);
});

test("setObject without key", () => {
expect(cacheProvider["cache"].size).toBe(0);
cacheProvider.setObject(CacheContainer.Seasons, undefined, seasons);
expect(cacheProvider["cache"].size).toBe(1);
expect(cacheProvider["cache"].get("CacheContainer:Seasons")).toBe(seasons);
});

test("getObject with/without key", () => {
insertTestData();
expect(cacheProvider.getObject(CacheContainer.Colors)).toBe(undefined);
expect(cacheProvider.getObject(CacheContainer.Colors, "Yellow")).toBe(undefined);
expect(cacheProvider.getObject(CacheContainer.Colors, "Red")).toBe(redColor);
expect(cacheProvider.getObject(CacheContainer.Colors, "Green")).toBe(greenColor);
expect(cacheProvider.getObject(CacheContainer.Colors, "Blue")).toBe(blueColor);
expect(cacheProvider.getObject(CacheContainer.Seasons)).toBe(seasons);
});

test("reset with key", () => {
insertTestData();
expect(cacheProvider["cache"].size).toBe(4);
cacheProvider.reset(CacheContainer.Colors, "Blue");
expect(cacheProvider["cache"].size).toBe(3);
cacheProvider.reset(CacheContainer.Colors, "Green");
expect(cacheProvider["cache"].size).toBe(2);
cacheProvider.reset(CacheContainer.Colors, "Red");
expect(cacheProvider["cache"].size).toBe(1);
});

test("reset without key", () => {
insertTestData();
expect(cacheProvider["cache"].size).toBe(4);
cacheProvider.reset(CacheContainer.Seasons);
expect(cacheProvider["cache"].size).toBe(3);
});
});
81 changes: 81 additions & 0 deletions src/lib/cacheProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { getEnumNameFromValue, StandardEnum } from "./enum";
import { isNullOrWhitespace } from "./string";

/**
* Methods to help caching data of any type.
* Data is put in containers and may be identified by a string key.
* Data can be saved, retrieved and reset.
* Based on Neolution.Abstractions.Caching.CacheProvider<TEnum>
* Ref: https://github.com/neolution-ch/Neolution.Abstractions/blob/main/Neolution.Abstractions/Caching/CacheProvider.cs
*/
export class CacheProvider<TEnum> {
/**
* The enum variable
*/
private readonly enumVariable: StandardEnum<TEnum>;

/**
* The name of the cache
*/
private readonly cacheName: string;

/**
* The cache
*/
private readonly cache = new Map<string, object>();

/**
* Initializes a new instance of the CacheProvider<TEnum> class
* @param enumVariable The enum variable
* @param cacheName The name of the cache
*/
constructor(enumVariable: StandardEnum<TEnum>, cacheName: string) {
this.enumVariable = enumVariable;
this.cacheName = cacheName;
}

/**
* Creates the cache key
* @param container The container
* @param key The key
* @returns The cache key
*/
private createCacheKey(container: TEnum, key?: string): string {
let containerName = getEnumNameFromValue(this.enumVariable, container);

if (key && !isNullOrWhitespace(key)) {
containerName = `${containerName}_${key}`;
}

return `${this.cacheName}:${containerName}`;
}

/**
* Gets cached data from the specified container
* @param container The container
* @param key The key
* @returns The data from cache
*/
getObject<T>(container: TEnum, key?: string): T | undefined {
return this.cache.get(this.createCacheKey(container, key)) as T;
}

/**
* Sets cached data identified by a key in the specified container
* @param container The container
* @param key The key
* @param cacheObject The cache object
*/
setObject<T>(container: TEnum, key: string | undefined, cacheObject: T): void {
this.cache.set(this.createCacheKey(container, key), cacheObject as object);
}

/**
* Resets cached data identified by a key of the specified container
* @param container The container
* @param key The key
*/
reset(container: TEnum, key?: string): void {
this.cache.delete(this.createCacheKey(container, key));
}
}
6 changes: 2 additions & 4 deletions src/lib/enum.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* The standard enum type
*/
type StandardEnum<T> = {
export type StandardEnum<T> = {
[id: string]: T | string;
[nu: number]: string;
};
Expand All @@ -12,7 +12,7 @@ type StandardEnum<T> = {
* @param enumValue The value of the enum for which you want to get the name
* @returns A string containing the name of the enum
*/
function getEnumNameFromValue<T>(enumVariable: StandardEnum<T>, enumValue: T): string {
export function getEnumNameFromValue<T>(enumVariable: StandardEnum<T>, enumValue: T): string {
return Object.keys(enumVariable)[Object.values(enumVariable).findIndex((x) => x === enumValue)];
}

Expand Down Expand Up @@ -61,5 +61,3 @@ function isEnumString<T>(enumVariable: StandardEnum<T>) {

return keys.length > 0 && !/^\d+$/.test(keys.at(0) as string);
}

export { getEnumNameFromValue };

0 comments on commit 364299f

Please sign in to comment.