TypeScript Cacheable Decorator

3.0.3 · active · verified Sun Apr 19

TypeScript Cacheable is an in-memory memoization decorator library designed to cache the results of expensive TypeScript methods or property accessors. Currently at version 3.0.3, it provides a stable API for enhancing application performance by storing computed values for subsequent retrieval. The library distinguishes itself by offering flexible caching scopes: 'GLOBAL' for application-wide caching, and 'LOCAL_STORAGE' which leverages Node.js's `AsyncLocalStorage` to provide request-scoped or call-chain-scoped caching, crucial for web applications. Cache keys can be automatically inferred from method parameters if they are JSON-serializable, or explicitly defined by implementing the `CacheableKey` interface for complex objects. While the library itself does not have a stated release cadence, its `3.x` version indicates active development and maintenance.

Common errors

Warnings

Install

Imports

Quickstart

This quickstart demonstrates `typescript-cacheable` with global, parameterized, and `AsyncLocalStorage`-scoped caching. It shows how to apply the `@Cacheable` decorator, highlights automatic key inference for JSON-serializable arguments, and illustrates the setup required for request-scoped caching using Node.js's `AsyncLocalStorage` to ensure calls within the same 'request' hit the cache, while separate 'requests' compute new values.

import { Cacheable } from 'typescript-cacheable';
import { AsyncLocalStorage } from 'async_hooks';

interface Dwarf { name: string; lastName: string; }

// Simulate a context for AsyncLocalStorage (e.g., an HTTP request context)
class Context { constructor(public requestId: string) {} }
const als = new AsyncLocalStorage<Context>();

// Helper to get the store for LOCAL_STORAGE scope
export const getStore = (): unknown => als.getStore();

class DwarfService {
    private callCount: number = 0;

    // Caching globally without parameters
    @Cacheable()
    public async findHappiest(): Promise<Dwarf> {
        this.callCount++;
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve({ name: 'Huck', lastName: 'Finn' });
            }, 100); // Simulate expensive operation
        });
    }

    // Caching with parameters, inferring key from JSON-serializable args
    @Cacheable()
    public async countByLastName(name: string): Promise<number> {
        this.callCount++;
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(name.length * 5);
            }, 50); // Simulate expensive operation
        });
    }

    // Caching with AsyncLocalStorage (request scope)
    @Cacheable({ scope: 'LOCAL_STORAGE', getStore: getStore })
    public async getRequestScopedValue(): Promise<string> {
        const store = als.getStore();
        const requestId = store ? store.requestId : 'no-request-id';
        this.callCount++;
        return new Promise((resolve) => {
            setTimeout(() => {
                resolve(`Value for req ${requestId}, call ${this.callCount}`);
            }, 20); // Simulate expensive operation
        });
    }

    public getCallCount(): number { return this.callCount; }
}

// Example Usage
async function runExamples() {
    const service = new DwarfService();

    console.log('--- Global Cache Example ---');
    const dwarf1 = await service.findHappiest();
    console.log('First call (global):', dwarf1, 'Call Count:', service.getCallCount());
    const dwarf2 = await service.findHappiest();
    console.log('Second call (global, cached):', dwarf2, 'Call Count:', service.getCallCount());

    console.log('\n--- Parameterized Cache Example ---');
    service['callCount'] = 0; // Reset for demonstration
    const count1 = await service.countByLastName('Snow');
    console.log('First call (params):', count1, 'Call Count:', service.getCallCount());
    const count2 = await service.countByLastName('Snow');
    console.log('Second call (params, cached):', count2, 'Call Count:', service.getCallCount());
    const count3 = await service.countByLastName('White');
    console.log('Third call (params, new arg):', count3, 'Call Count:', service.getCallCount());

    console.log('\n--- Local Storage Cache Example (Simulated Request) ---');
    service['callCount'] = 0; // Reset for demonstration

    await als.run(new Context('req-123'), async () => {
        const valA1 = await service.getRequestScopedValue();
        console.log('Req 123 - Call 1:', valA1, 'Call Count:', service.getCallCount());
        const valA2 = await service.getRequestScopedValue();
        console.log('Req 123 - Call 2 (cached):', valA2, 'Call Count:', service.getCallCount());
    });

    await als.run(new Context('req-456'), async () => {
        const valB1 = await service.getRequestScopedValue();
        console.log('Req 456 - Call 1:', valB1, 'Call Count:', service.getCallCount());
        const valB2 = await service.getRequestScopedValue();
        console.log('Req 456 - Call 2 (cached):', valB2, 'Call Count:', service.getCallCount());
    });
}

runExamples();

view raw JSON →