Skip to content

Instantly share code, notes, and snippets.

@laser
Last active October 4, 2024 18:23
Show Gist options
  • Save laser/717b6851b9e728f7376be2ab2c58d5f8 to your computer and use it in GitHub Desktop.
Save laser/717b6851b9e728f7376be2ab2c58d5f8 to your computer and use it in GitHub Desktop.
Easy TypeScript Decorator for TTL Cache (using isaacs/ttlcache)
import { ServerInferRequest, ServerInferResponses, initContract } from '@ts-rest/core';
import { createExpressEndpoints, initServer } from '@ts-rest/express';
import express from 'express';
import { z } from 'zod';
import { CacheMethod } from '~/util/cache-method';
export const EchoSchema = initContract().router({
echo: {
body: z.object({
call: z.string(),
}),
method: 'POST',
path: '/echo',
responses: {
200: z.object({
response: z.string(),
}),
},
},
});
class EchoController {
private logger: (msg: string) => void;
constructor(logger: (msg: string) => void) {
this.logger = logger;
}
@CacheMethod(5_000, (req) => JSON.stringify(req.body), (res) => res.status === 200)
public async echo(
req: ServerInferRequest<typeof EchoSchema>['echo'],
): Promise<ServerInferResponses<typeof EchoSchema>['echo']> {
this.logger(`echoing: ${req.body.call}`);
return {
body: {
response: `you said: ${req.body.call}`,
},
status: 200,
};
}
}
const app = express();
app.use(express.urlencoded({ extended: false }));
app.use(express.json());
const logger = (msg: string) => console.log(msg);
const controller = new EchoController(logger);
const router = initServer().router(EchoSchema, { echo: controller.echo.bind(controller) });
createExpressEndpoints(EchoSchema, router, app);
const host = '127.0.0.1';
const port = 8888;
console.log(`starting server: host=${host}, port=${port}`);
app.listen(port, host);
/* eslint-disable @typescript-eslint/no-explicit-any */
import TTLCache from '@isaacs/ttlcache';
export function CacheMethod<T, U, K>(ttlMs: number, computeKey: (input: T) => K, shouldCache: (output: U) => boolean) {
if (ttlMs <= 0) {
return function (
_target: unknown,
_propertyKey: string,
_descriptor: TypedPropertyDescriptor<(input: T) => Promise<U>>,
) {
// noop
};
}
return function (target: any, propertyKey: string, descriptor: TypedPropertyDescriptor<(input: T) => Promise<U>>) {
const original = descriptor.value!;
descriptor.value = function (input: T): Promise<U> {
const that: any = this;
if (!that.__cache) {
that.__cache = {};
}
if (!that.__cache[`${target.constructor.name}__${propertyKey}`]) {
that.__cache[`${target.constructor.name}__${propertyKey}`] = new TTLCache<K, U>({ ttl: ttlMs });
}
const key = computeKey(input);
const hit = that.__cache[`${target.constructor.name}__${propertyKey}`].get(key);
if (hit) {
return Promise.resolve(hit);
}
return Promise.resolve(original.call(this, input)).then((output: U) => {
if (shouldCache(output)) {
that.__cache[`${target.constructor.name}__${propertyKey}`].set(key, output);
}
return output;
});
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment