Install & Compatibility
Where this runs
tested against v? · npm install
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslnode 18–226 runs
build_error
glibcnode 18–226 runs
build_error
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
Cacheable
✓ import { Cacheable } from 'typescript-cacheable';
✗ const Cacheable = require('typescript-cacheable');
The Cacheable decorator is a named export. ESM import syntax is standard. CommonJS `require` is generally not suitable for decorators or modern TypeScript modules.
CacheableKey
✓ import { CacheableKey } from 'typescript-cacheable';
CacheableKey is a TypeScript interface, used for type-checking and defining custom cache key logic. It is a named export and disappears at runtime.
CacheableOptions (implicit)
✓ import { Cacheable } from 'typescript-cacheable';
// Example usage with options
@Cacheable({ scope: 'GLOBAL', ttl: 60000 })
Configuration options for the decorator are passed as an object to `Cacheable()`. While `CacheableOptions` is not directly imported as a type, its properties are part of the `Cacheable` decorator's API.
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();
Errors
Common errors & fixes
Error: Decorators are not enabled. You must enable 'experimentalDecorators' in your tsconfig.json.
The TypeScript compiler is not configured to support experimental decorators, which are required for `@Cacheable` to function.
fixAdd or update the `compilerOptions` in your `tsconfig.json` file: `"experimentalDecorators": true, "emitDecoratorMetadata": true`.
TypeError: Cannot convert circular structure to JSON
A method decorated with `@Cacheable` received an argument that contains circular references, and the library attempted to JSON-serialize it to generate a cache key.
fixFor the problematic parameter, implement the `CacheableKey` interface on its class, providing a `cacheKey()` method that generates a unique, non-circular string. Alternatively, pass a custom `keyComposer` function to the `@Cacheable` decorator options.
Always re-computing value when using LOCAL_STORAGE scope / Cache is not working as expected with AsyncLocalStorage.
The `AsyncLocalStorage` instance is not correctly bound in the call chain, or the `getStore` function provided to `@Cacheable` does not return the same `AsyncLocalStorage` instance that was used to create the execution context.
fixVerify that `AsyncLocalStorage.run()` is used to wrap the code path where caching should occur (e.g., in Express middleware) and that the `getStore` function consistently retrieves the correct `AsyncLocalStorage` instance. Refer to the `AsyncLocalStorage` example in the documentation.
Cannot apply a decorator to a non-method or non-accessor.
The `@Cacheable` decorator was applied to a class property directly, a class itself, or another non-callable member, instead of a method or property accessor (getter/setter).
fixEnsure `@Cacheable` is only used on class methods or property getters/setters, as decorators in TypeScript modify the behavior of callable members.
Audit
Dependencies
No dependency data recorded yet.