Skip to content
Merged
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
12 changes: 8 additions & 4 deletions src/cache/cache.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ export interface CacheAdapter {
/**
* Delete all entries whose key starts with the given prefix.
*
* **Optional** — the SDK falls back to a less efficient strategy when this
* method is absent (e.g. deleting known exact keys or clearing the entire
* cache). Implementing this enables granular per-guild and per-wallet cache
* invalidation without flushing unrelated entries.
* **Optional, by design** — kept off the required contract so minimal
* custom adapters (a plain object, a single-key KV store) still satisfy
* `CacheAdapter` without implementing prefix scanning.
*
* Strongly recommended: without it, {@link GuildPassClient.invalidateGuildCache}
* and {@link GuildPassClient.invalidateWalletCache} fall back to exact-key
* deletion or a full {@link CacheAdapter.clear}, which is slower and can
* evict unrelated entries. `InMemoryCacheAdapter` implements it below.
*
* Must never throw.
*/
Expand Down
18 changes: 17 additions & 1 deletion tests/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,11 +345,27 @@ describe('GuildPassClient – cache integration', () => {
expect(await adapter.get('roles:getRoles:prime-guild')).toBeNull();
});

it('invalidateGuildCache is a no-op when no adapter is configured', async () => {
it('invalidateGuildCache is a no-op when no adapter is configured', async () => {
const client = new GuildPassClient(BASE_CONFIG);
await expect(client.invalidateGuildCache('any')).resolves.toBeUndefined();
});

// Regression test for #283: pins the acceptance criteria directly against
// InMemoryCacheAdapter.deleteByPrefix so a future refactor can't silently
// reintroduce a full-cache-scan fallback for this adapter.
it('[#283] invalidateGuildCache removes every prime-guild entry and no others', async () => {
const adapter = new InMemoryCacheAdapter();
await adapter.set('access:checkAccess:prime-guild:docs:0xabc', true);
await adapter.set('roles:getRoles:prime-guild', ['admin']);
await adapter.set('access:checkAccess:other-guild:docs:0xabc', true);

const client = new GuildPassClient({ ...BASE_CONFIG, cache: adapter });
await client.invalidateGuildCache('prime-guild');

expect(await adapter.get('access:checkAccess:prime-guild:docs:0xabc')).toBeNull();
expect(await adapter.get('roles:getRoles:prime-guild')).toBeNull();
expect(await adapter.get('access:checkAccess:other-guild:docs:0xabc')).toBe(true);
});
it('clearCache clears the entire adapter store', async () => {
const adapter = new InMemoryCacheAdapter();
await adapter.set('a', 1);
Expand Down