Skip to main content

Cache usage

The eridu-tech/cache component provides a way for storing key-value pairs with expiration independent of data storage

Initial configuration​

To begin using the Cache class, you'll need to create and configure an instance:

./samples/cache-initial-config.ts
import { TimeSpan } from "eridu-tech/time-span";
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";
import { Cache } from "eridu-tech/cache";

export const cache = new Cache<any>({
// You can provide default TTL value
// If you set it to null it means keys will be stored forever.
defaultTtl: TimeSpan.fromSeconds(2),

// You can choose the adapter to use
adapter: new MemoryCacheAdapter(),
});
info

Here is a complete list of settings for the Cache class.

Cache basics​

Adding keys​

You can add a key with a optional TTL to overide the default:

./samples/cache-add.ts
import { cache } from "./cache-initial-config.js";
import { TimeSpan } from "eridu-tech/time-span";

await cache.add("a", "value", TimeSpan.fromSeconds(1));

The method returns true if the key does not exists.

Retrieving keys​

You can retrieve the key:

./samples/cache-get.ts
import { cache } from "./cache-initial-config.js";

await cache.get("a");

Checking key existence​

You can check if the key exists:

./samples/cache-exists.ts
import { cache } from "./cache-initial-config.js";

await cache.exists("a");

You can check if the key is missing:

./samples/cache-missing.ts
import { cache } from "./cache-initial-config.js";

await cache.missing("a");

Updating keys​

You can update a key and true will be returned if the key exists and was updated:

./samples/cache-update.ts
import { cache } from "./cache-initial-config.js";

await cache.update("a", 2);

You can increment the a key and true will be returned if the key exists and was updated. If the key is not a number an error will be thrown:

./samples/cache-increment.ts
import { cache } from "./cache-initial-config.js";

await cache.increment("a", 2);

You can decrement the a key and true will be returned if the key exists and was updated. If the key is not a number an error will be thrown,:

./samples/cache-decrement.ts
import { cache } from "./cache-initial-config.js";

await cache.decrement("a", 1);

You can perform an upsert that replaces the ttl when updated. True will be returned if the key was updated otherwise false is returned:

./samples/cache-put.ts
import { cache } from "./cache-initial-config.js";
import { TimeSpan } from "eridu-tech/time-span";

await cache.put("a", 2);
await cache.put("a", 4, TimeSpan.fromSeconds(3));

Removing keys​

You can remove a key and true will be returned if the key was found and removed:

./samples/cache-remove.ts
import { cache } from "./cache-initial-config.js";

await cache.remove("a");

You can remove multiple keys and true will be returned if one of the keys exists and where removed:

./samples/cache-remove-many.ts
import { cache } from "./cache-initial-config.js";

await cache.removeMany(["a", "b"]);

You can clear all the keys of the given namespace:

./samples/cache-clear.ts
import { cache } from "./cache-initial-config.js";

await cache.clear();

Patterns​

Compile time type safety​

You can enforce compile time type safety by setting the cache value type:

./samples/compile-time-type-safety.ts
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";
import { Cache } from "eridu-tech/cache";

type IUser = {
name: string;
email: string;
age: number;
};

const cache = new Cache<IUser>({
adapter: new MemoryCacheAdapter(),
});

// A typescript error will occur because the type is not matching.
await cache.add("a", "asd");

If you have multiple types you can use algeberical enums:

./samples/cache-union-types.ts
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";
import { Cache } from "eridu-tech/cache";

type IUser = {
type: "USER";
name: string;
email: string;
age: number;
};
type IProduct = {
type: "PRODUCT";
name: string;
price: number;
};
type CacheValue = IUser | IProduct;

const cache = new Cache<CacheValue>({
adapter: new MemoryCacheAdapter(),
});

const cacheValue = await cache.get("user1");
// You need to check the type is "USER" inorder to access IUser fields.
if (cacheValue.type === "USER") {
console.log(cacheValue.name, cacheValue.age);
}
// You need to check the type is "PRODUCT" inorder to access IProduct fields.
if (cacheValue.type === "PRODUCT") {
console.log(cacheValue.name, cacheValue.price);
}

Alternatively you can use different Cache classes with different namespaces:

./samples/cache-multiple-namespaces.ts
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";
import { Cache } from "eridu-tech/cache";

const cacheAdapter = new MemoryCacheAdapter();

type IUser = {
name: string;
email: string;
age: number;
};
const userCache = new Cache<IUser>({
adapter: cacheAdapter,
});

type IProduct = {
name: string;
price: number;
};
const productCache = new Cache<IProduct>({
adapter: cacheAdapter,
});

Runtime type safety​

You can validate cache values against a standard-schema-compliant schema by providing the schema setting. This works with any library that implements the StandardSchemaV1 specification, such as Zod, ArkType and Valibot.

When a schema is provided, values are validated:

  • On write — before a value is stored, for the add, put, update and getOrAdd methods.
  • On read — when shouldValidateOutput is true (the default), values returned by get, getAndRemove and getOrAdd are validated on retrieval. This catches malformed data already present in the cache at read time, instead of silently returning it.

If validation fails, a ValidationError is thrown.

./samples/cache-runtime-validation.ts
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";
import { Cache } from "eridu-tech/cache";
import { z } from "zod";

const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number(),
});

const cache = new Cache({
adapter: new MemoryCacheAdapter(),
schema: userSchema,
});

await cache.add("user1", {
name: "John",
email: "john@example.com",
age: 30,
});

// Throws a ValidationError because the email is not valid
await cache.add("user2", {
name: "Jane",
email: "not-an-email",
age: "25",
});

Disabling output validation​

If you only want to validate values on write and skip validation when reading, set shouldValidateOutput to false:

./samples/cache-disable-output-validation.ts
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";
import { Cache } from "eridu-tech/cache";
import { z } from "zod";

const userSchema = z.object({
name: z.string(),
email: z.string().email(),
age: z.number(),
});

const cache = new Cache({
adapter: new MemoryCacheAdapter(),
schema: userSchema,
shouldValidateOutput: false,
});

Additional methods​

You can retrieve the key and if it does not exist an error will be thrown:

./samples/cache-get-or-fail.ts
import { cache } from "./cache-initial-config.js";

await cache.getOrFail("ab");

You can retrieve the key and if it does not exist you can return a default value:

./samples/cache-get-or.ts
import { cache } from "./cache-initial-config.js";

await cache.getOr("ab", 1);

You can retrieve the key and if it does not exist you can insert a default value that will aslo be returned:

./samples/cache-get-or-add.ts
import { cache } from "./cache-initial-config.js";

await cache.getOrAdd("ab", 1);

You can retrieve the key and afterwards remove it:

./samples/cache-get-and-remove.ts
import { cache } from "./cache-initial-config.js";

await cache.getAndRemove("ab");

You can add key and if it does exist an error will be thrown:

./samples/cache-add-or-fail.ts
import { cache } from "./cache-initial-config.js";

await cache.addOrFail("ab", 1);

You can update the key and if it does not exist an error will be thrown:

./samples/cache-update-or-fail.ts
import { cache } from "./cache-initial-config.js";

await cache.updateOrFail("ab", 1);

You can increment the key and if it does not exist an error will be thrown:

./samples/cache-increment-or-fail.ts
import { cache } from "./cache-initial-config.js";

await cache.incrementOrFail("ab", 1);

You can decrement the key and if it does not exist an error will be thrown:

./samples/cache-decrement-or-fail.ts
import { cache } from "./cache-initial-config.js";

await cache.decrementOrFail("ab", 1);

You can remove the key and if it does not exist an error will be thrown:

./samples/cache-remove-or-fail.ts
import { cache } from "./cache-initial-config.js";

await cache.removeOrFail("ab");

Separating cache reading from manipulation​

The library includes 2 additional contracts:

  • IReadableCache - Allows only for reading cache.

  • ICache - Allows for both reading and manipulating the cache.

This separation makes it easy to visually distinguish the two contracts, making it immediately obvious that they serve different purposes.

./samples/cache-read-write-contracts.ts
import type { ICache, IReadableCache } from "eridu-tech/cache/contracts";
import { Cache } from "eridu-tech/cache";
import { MemoryCacheAdapter } from "eridu-tech/cache/memory-cache-adapter";

async function readingFunc(cache: IReadableCache): Promise<void> {
// You cannot access write methods like put, add and update
// You will get typescript error if you try

console.log("reading only:", await cache.get("a"));
}
async function manipulatingFunc(cache: ICache): Promise<void> {
// You will get typescript error if you try

await cache.add("a", 1);
console.log("writing and reading:", await cache.get("a"));
}

const cache = new Cache({
adapter: new MemoryCacheAdapter(),
});
await manipulatingFunc(cache);
await readingFunc(cache);

Further information​

For further information refer to eridu-tech/cache API docs.