TriFrost now ships with a caching system thatβs not only powerful β but invisible. πͺ
Caching is one of those things you want to do wherever possible: inside services, on expensive lookups, even conditional branches. But until now, you had two options:
1. Write your own cache keys and wrap logic manually β again and again (think very generic boilerplate).
2. Forget to cache at all.
Letβs fix that.
Added
- feat:
@cache
decorator β Automatically wraps your method in cache logic:
import {cache} from '@trifrost/core';
class Releases {
@cache('releases')
async list(ctx: Context) {
return fetchFromUpstream();
}
/* Supports dynamic keys via ctx (ps: the function you pass will be typed based on the context it gets) */
@cache(ctx => `release:${ctx.state.id}`)
async one <State extends {id:string}> (ctx:Context<State>) {
return fetchRelease(ctx.state.id);
}
}
cacheFn
function β Wrap standalone or arrow functions with cache logic:import {cacheFn} from '@trifrost/core';
const getReleases = cacheFn('releases', (ctx) => fetchFromUpstream(...));
const getRelease = cacheFn <State extends {id:string}> (
ctx => `release:${ctx.state.id}`,
(ctx:Context<State>) => fetchRelease(ctx.state.id)
);
cacheSkip()
β Want to bail from caching? Just return your result wrapped in cacheSkip()
.Works when manually using cache wrap:
export async function getReleases (ctx:Context) {
return ctx.cache.wrap('myKey', async () => {
try {
const data = await maybeFails();
return data;
} catch (err) {
ctx.logger.error(err);
return cacheSkip(null);
}
});
}
Works within @cache decorated methods:
import {cacheSkip} from '@trifrost/core';
class Releases {
@cache('releases')
async getReleases(ctx: Context) {
try {
...
return fetchFromUpstream();
} catch (err) {
ctx.logger.error(err);
return cacheSkip(null);
}
}
}
Works within cacheFn wrapped methods:
import {cacheFn, cacheSkip} from '@trifrost/core';
const getRelease = cacheFn('getRelease', async (ctx:Context) => {
try {
return await fetchRelease(ctx.state.id);
} catch (err) {
ctx.logger.error(err);
return cacheSkip(null);
}
});
Improved
- feat: Caches now accept primitives as values β
null
,true
,false
,0
,"hello"
, etc. No need to always wrap things in objects. - feat:
@span
andspanFn
now supportthis.ctx.logger
as a fallback if neitherctx.logger
northis.logger
is available.
class Releases {
constructor(ctx: Context) {
this.ctx = ctx;
}
@span()
@cache('releases')
async getReleases() {
return fetchFromUpstream();
}
}
No ctx needed β both @span
and @cache
find what they need.
- deps: Upgrade @cloudflare/workers-types to 4.20250514.0
- deps: Upgrade @types/node to 22.15.18
- deps: Upgrade typescript-eslint to 8.32.1
Breaking
- feat:
ctx.cache.delete
has been renamed toctx.cache.del
. This saves 4 keystrokes π and aligns with the rest of the ecosystem:
ctx.cookies.del('token');
ctx.router.del('/route', handler);
ctx.cache.del('myKey');