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
44 changes: 43 additions & 1 deletion src/utils/cache-key-params.utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { buildCanonicalParamString } from './cache-key-params.utils';
import { buildCanonicalParamString, buildCacheKey } from './cache-key-params.utils';

describe('buildCanonicalParamString()', () => {
it('produces identical output regardless of input key order', () => {
Expand Down Expand Up @@ -79,3 +79,45 @@ describe('buildCanonicalParamString()', () => {
);
});
});

describe('buildCacheKey()', () => {
it('produces identical cache keys for identical params in different order', () => {
const keyA = buildCacheKey('creator:123', {
limit: 20,
sort: 'createdAt',
order: 'desc',
});
const keyB = buildCacheKey('creator:123', {
order: 'desc',
limit: 20,
sort: 'createdAt',
});

expect(keyA).toBe(keyB);
});

it('produces different cache keys for different params', () => {
const keyA = buildCacheKey('creator:123', { limit: 20, sort: 'createdAt' });
const keyB = buildCacheKey('creator:123', { limit: 10, sort: 'createdAt' });

expect(keyA).not.toBe(keyB);
});

it('returns a string of fixed length format', () => {
const key1 = buildCacheKey('creator:123', { limit: 20 });
const key2 = buildCacheKey('creator:123', {
limit: 100,
offset: 500,
sort: 'verylongsortfieldname',
search: 'searching for something very long and detailed',
});

expect(typeof key1).toBe('string');
expect(typeof key2).toBe('string');
// Base prefix ('creator:123:') + 16 chars hash hex = 28 length
expect(key1.length).toBe(key2.length);
expect(key1).toMatch(/^creator:123:[a-f0-9]{16}$/);
expect(key2).toMatch(/^creator:123:[a-f0-9]{16}$/);
});
});

28 changes: 22 additions & 6 deletions src/utils/cache-key-params.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,30 @@
* // => "limit:20:order:desc:sort:createdAt"
*/
export function buildCanonicalParamString(
params: Record<string, string | number | boolean | undefined>
params: Record<string, unknown>
): string {
return Object.entries(params)
.filter(
(entry): entry is [string, string | number | boolean] =>
entry[1] !== undefined
)
.filter((entry): entry is [string, unknown] => entry[1] !== undefined)
.sort(([a], [b]) => a.localeCompare(b))
.map(([key, value]) => `${key}:${value}`)
.map(([key, value]) => `${key}:${typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value)}`)
.join(':');
}

import { createHash } from 'crypto';

/**
* Generates a stable, fixed-length cache key from a base string and query parameter set.
*
* Param keys are sorted lexicographically before hashing, ensuring that parameter order
* does not affect the generated key.
*
* @param base - The base prefix string (e.g. creator ID or endpoint prefix)
* @param params - Object containing query parameter key-value pairs
* @returns A fixed-length string cache key
*/
export function buildCacheKey(base: string, params: Record<string, unknown>): string {
const canonical = buildCanonicalParamString(params);
const hash = createHash('sha256').update(canonical).digest('hex').slice(0, 16);
return `${base}:${hash}`;
}

Loading