Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/cacheable/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -796,6 +796,7 @@ const cache = new Cacheable({ tags: true });

await cache.set('page:/products', html, { ttl: '10m', tags: ['entity:42', 'collection:products'] });
await cache.set('page:/products/42', detailHtml, { ttl: '10m', tags: ['entity:42'] });
await cache.getOrSet('summary:products', loadSummary, { ttl: '10m', tags: ['collection:products'] });

// entity 42 changed - purge everything that referenced it
await cache.tags.invalidateTag('entity:42');
Expand Down Expand Up @@ -1069,13 +1070,14 @@ The `getOrSet` method that comes from [@cacheable/utils](https://cacheable.org/
```typescript
export type GetOrSetFunctionOptions = {
ttl?: number | string | { primary?: number | string; secondary?: number | string };
tags?: string[];
cacheErrors?: boolean;
throwErrors?: boolean;
nonBlocking?: boolean;
};
```

The `ttl` also accepts a [per-store object](#per-store-ttl-per-operation) such as `{ primary: '10s', secondary: '5m' }` to give the primary and secondary stores different expirations for this operation.
The `ttl` also accepts a [per-store object](#per-store-ttl-per-operation) such as `{ primary: '10s', secondary: '5m' }` to give the primary and secondary stores different expirations for this operation. The `tags` option associates a newly computed value with tags for [tag-based invalidation](#tag-based-invalidation).

The `nonBlocking` option allows you to override the instance-level `nonBlocking` setting for the `get` call within `getOrSet`. When set to `false`, the `get` will block and wait for a response from the secondary store before deciding whether to call the provided function. When set to `true`, the primary store returns immediately and syncs from secondary in the background.

Expand Down
4 changes: 2 additions & 2 deletions packages/cacheable/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,7 +1203,7 @@ export class Cacheable extends Hookified {
* @param {GetOrSetKey} key - The key to retrieve or set in the cache. This can also be a function that returns a string key.
* If a function is provided, it will be called with the cache options to generate the key.
* @param {() => Promise<T>} function_ - The asynchronous function that computes the value to be cached if the key does not exist.
* @param {GetOrSetFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL) or whether to cache errors.
* @param {GetOrSetFunctionOptions} [options] - Optional settings for caching, such as the time to live (TTL), tags, or whether to cache errors.
* @return {Promise<T | undefined>} - A promise that resolves to the cached or newly computed value, or undefined if an error occurs and caching is not configured for errors.
*/
public async getOrSet<T>(
Expand All @@ -1225,7 +1225,7 @@ export class Cacheable extends Hookified {
value: unknown,
ttl?: number | string | PerStoreTtl,
) => {
await this.set(key, value, { ttl });
await this.set(key, value, { ttl, tags: options?.tags });
},
/* v8 ignore next -- @preserve */
on: (event: string, listener: (...args: unknown[]) => void) => {
Expand Down
6 changes: 6 additions & 0 deletions packages/cacheable/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export type GetOrSetFunctionOptions = Omit<
"ttl"
> & {
ttl?: number | string | PerStoreTtl;
/**
* Tags to associate with a newly computed entry for tag-based invalidation. Tags are only
* applied when `getOrSet` stores a value after a cache miss.
* @type {string[]}
*/
tags?: string[];
};

/**
Expand Down
40 changes: 40 additions & 0 deletions packages/cacheable/test/tags.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,46 @@ describe("cacheable tags", () => {
expect(await cacheable.get(key)).toBeUndefined();
});

test("getOrSet associates tags with a newly computed entry", async () => {
const cacheable = new Cacheable({ tags: true });
let calls = 0;
const options = { tags: ["entity:42"] };
const getValue = async () => {
calls++;
return `value-${calls}`;
};

expect(await cacheable.getOrSet("k", getValue, options)).toEqual("value-1");
expect(await cacheable.tags.getTags("k")).toEqual(["entity:42"]);
expect(await cacheable.getOrSet("k", getValue, options)).toEqual("value-1");
expect(calls).toBe(1);

await cacheable.tags.invalidateTag("entity:42");
expect(await cacheable.getOrSet("k", getValue, options)).toEqual("value-2");
expect(calls).toBe(2);
expect(await cacheable.tags.getTags("k")).toEqual(["entity:42"]);
});

test("getOrSet leaves a recomputed entry untagged when tags are omitted", async () => {
const cacheable = new Cacheable({ tags: true });
let calls = 0;
const getValue = async () => {
calls++;
return `value-${calls}`;
};

await cacheable.getOrSet("k", getValue, { tags: ["entity:42"] });
await cacheable.tags.invalidateTag("entity:42");

expect(await cacheable.getOrSet("k", getValue)).toEqual("value-2");
expect(calls).toBe(2);
expect(await cacheable.tags.getTags("k")).toBeUndefined();

await cacheable.tags.invalidateTag("entity:42");
expect(await cacheable.getOrSet("k", getValue)).toEqual("value-2");
expect(calls).toBe(2);
});

test("set still supports ttl as the third argument", async () => {
const cacheable = new Cacheable();
const key = faker.string.uuid();
Expand Down