Skip to content

Commit

Permalink
feat(terms): use uni-time glossary for duration term (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
uladkasach committed Dec 26, 2024
1 parent 3c64724 commit b80e053
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 60 deletions.
88 changes: 82 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,14 @@
"prettier": "2.8.1",
"ts-jest": "29.2.5",
"ts-node": "10.9.2",
"type-fns": "^1.19.0",
"typescript": "5.4.5"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"dependencies": {
"@ehmpathy/uni-time": "^1.7.4"
}
}
28 changes: 5 additions & 23 deletions src/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,7 @@ describe('cache', () => {
expect(licks).toEqual(3);
});
it('should respect the default expiration for the cache', async () => {
const { set, get } = createCache({ defaultSecondsUntilExpiration: 10 }); // we're gonna use this cache to keep track of the popcorn in the microwave - we should check more regularly since it changes quickly!
set('how popped is the popcorn?', 'not popped');

// prove that we recorded the value and its accessible immediately after setting
const popcornStatus = get('how popped is the popcorn?');
expect(popcornStatus).toEqual('not popped');

// prove that the value is still accessible after 9 seconds, since default ttl is 10 seconds
await sleep(9 * 1000);
const popcornStatusAfter9Sec = get('how popped is the popcorn?');
expect(popcornStatusAfter9Sec).toEqual('not popped'); // still should say not popped

// and prove that after a total of 9 seconds, the status is no longer in the cache
await sleep(1 * 1000); // sleep 1 more second
const popcornStatusAfter10Sec = get('how popped is the popcorn?');
expect(popcornStatusAfter10Sec).toEqual(undefined); // no longer defined, since the default seconds until expiration was 15
});
it('should respect the default expiration for the cache set by shorthand alias', async () => {
const { set, get } = createCache({ seconds: 10 }); // we're gonna use this cache to keep track of the popcorn in the microwave - we should check more regularly since it changes quickly!
const { set, get } = createCache({ expiration: { seconds: 10 } }); // we're gonna use this cache to keep track of the popcorn in the microwave - we should check more regularly since it changes quickly!
set('how popped is the popcorn?', 'not popped');

// prove that we recorded the value and its accessible immediately after setting
Expand All @@ -58,7 +40,7 @@ describe('cache', () => {
});
it('should respect the item level expiration for the cache', async () => {
const { set, get } = createCache(); // remember, default expiration is greater than 1 min
set('ice cream state', 'solid', { secondsUntilExpiration: 5 }); // ice cream changes quickly in the heat! lets keep a quick eye on this
set('ice cream state', 'solid', { expiration: { seconds: 5 } }); // ice cream changes quickly in the heat! lets keep a quick eye on this

// prove that we recorded the value and its accessible immediately after setting
const iceCreamState = get('ice cream state');
Expand All @@ -74,9 +56,9 @@ describe('cache', () => {
const iceCreamStateAfter5Sec = get('ice cream state');
expect(iceCreamStateAfter5Sec).toEqual(undefined); // no longer defined, since the item level seconds until expiration was 5
});
it('should consider secondsUntilExpiration of null or infinity as never expiring', async () => {
it('should consider secondsUntilExpiration of null as never expiring', async () => {
const { set, get } = createCache({
defaultSecondsUntilExpiration: 0, // expire immediately
expiration: { seconds: 0 }, // expire immediately
});

// prove that setting something to the cache with default state will have it expired immediately
Expand All @@ -86,7 +68,7 @@ describe('cache', () => {

// prove that if we record the memory with expires-at Infinity, it persists
await set('elephant-memory', 'something', {
secondsUntilExpiration: Infinity,
expiration: null,
});
const elephantMemory = await get('elephant-memory');
expect(elephantMemory).toEqual('something');
Expand Down
43 changes: 13 additions & 30 deletions src/cache.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import type { PickOne } from 'type-fns';
import { toMilliseconds, UniDuration } from '@ehmpathy/uni-time';

export interface SimpleInMemoryCache<T> {
get: (key: string) => T | undefined;
set: (
key: string,
value: T,
options?: Partial<
PickOne<{ secondsUntilExpiration?: number; seconds?: number }>
>,
options?: { expiration?: UniDuration | null },
) => void;
keys: () => string[];
}
Expand All @@ -18,29 +16,13 @@ export interface SimpleInMemoryCacheState<T> {

const getMseNow = () => new Date().getTime();

export const createCache = <T>({
seconds,
defaultSecondsUntilExpiration: defaultSecondsUntilExpirationInput,
}: Partial<
PickOne<{
/**
* the number of seconds items in the cache expire after
*/
defaultSecondsUntilExpiration?: number;

/**
* a shorthand alias for `defaultSecondsUntilExpiration`
*
* note
* - if both options are set, `defaultSecondsUntilExpirationInput` takes precedence
*/
seconds?: number;
}>
> = {}): SimpleInMemoryCache<T> => {
// resolve input alias
const defaultSecondsUntilExpiration =
defaultSecondsUntilExpirationInput ?? seconds ?? 5 * 60;

export const createCache = <T>(
{
expiration: defaultExpiration,
}: {
expiration?: UniDuration | null;
} = { expiration: { minutes: 5 } },
): SimpleInMemoryCache<T> => {
// initialize a fresh in-memory cache object
const cache: SimpleInMemoryCacheState<T> = {};

Expand All @@ -49,8 +31,8 @@ export const createCache = <T>({
key: string,
value: T | undefined,
{
secondsUntilExpiration = defaultSecondsUntilExpiration,
}: { secondsUntilExpiration?: number } = {},
expiration = defaultExpiration,
}: { expiration?: UniDuration | null } = {},
) => {
// handle cache invalidation
if (value === undefined) {
Expand All @@ -59,7 +41,8 @@ export const createCache = <T>({
}

// handle setting
const expiresAtMse = getMseNow() + secondsUntilExpiration * 1000;
const expiresAtMse =
getMseNow() + (expiration ? toMilliseconds(expiration) : Infinity); // infinity if null
cache[key] = { value, expiresAtMse };
};

Expand Down

0 comments on commit b80e053

Please sign in to comment.