From 6965862064f7c7857ca20a3926ce36404b10c5fd Mon Sep 17 00:00:00 2001 From: Aleksey Kvak Date: Thu, 23 Jan 2020 16:02:20 +0300 Subject: [PATCH] feat: Static tags can be lazy calculated --- README.md | 6 +++--- package.json | 3 ++- src/adapters/redis/index.ts | 13 ++++++------- src/storage.ts | 2 +- src/storages/base.spec.ts | 19 +++++++++++++++++-- src/storages/base.ts | 7 ++++++- 6 files changed, 35 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 060542e..791bae7 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ The library is designed to cache query results. Features: To initialize Cache instance, you need: * StorageAdapter (in the example below, an adapter for connecting to redis). RedisStorageAdapter takes as an argument the instance of ioredis client. * Settings object. The options are the following options: -   +   - prefix - prefix used by CacheManager for storing keys. In essence, this is the namespace for a specific CacheManager. -   +   - logger - instance of logger. Must implement following interface: ```typescript @@ -73,7 +73,7 @@ function getSomething() { `get` will check the tags and compare their versions with the current date, runs an executor if necessary and returns result. Options for `get`: - expiresIn?: number; - The number of milliseconds after which key values are considered expired -- tags?: string[] - Tags - keys for which checks the validity of a particular record. If the tag value in the cache + invalidation time is string[]) - Tags - keys for which checks the validity of a particular record. If the tag value in the cache + invalidation time is string[] function which extracts tags from executor result. These tags will be merged with tags given in option below. The next method, "touch", serves to invalidate tags. Calling this method with one of the tags will make all records in the cache with this tag invalid. diff --git a/package.json b/package.json index 10a8eff..a698da9 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "watch": "tsc -w", "lint": "tslint --project . \"src/**/*.ts\"", "check": "npm run lint && npm run test:unit", - "test:unit": "jest --forceExit --coverage --verbose --detectOpenHandles --passWithNoTests", + "test": "npm run test:unit", + "test:unit": "jest --coverage --verbose --passWithNoTests", "test:ci": "npm run test:unit -- --coverageReporters=text-lcov | coveralls", "test:unit:watch": "jest --watch", "prepublishOnly": "npm run check && npm run build", diff --git a/src/adapters/redis/index.ts b/src/adapters/redis/index.ts index ce7cf96..b2ad21f 100644 --- a/src/adapters/redis/index.ts +++ b/src/adapters/redis/index.ts @@ -1,4 +1,4 @@ -import { KeyType, Redis } from 'ioredis'; +import { Redis } from 'ioredis'; import { ConnectionStatus } from '../../connection-status'; import { StorageAdapter } from '../../storage-adapter'; import { withTimeout } from '../../with-timeout'; @@ -70,14 +70,13 @@ export class RedisStorageAdapter implements StorageAdapter { * Use set command to set hash value in radish */ public async set(key: string, value: string, expiresIn?: number): Promise { - const commands: [KeyType, CommandArgument] = [`${CACHE_PREFIX}:${key}`, value]; + const cacheKey = `${CACHE_PREFIX}:${key}`; - if (expiresIn) { - commands.push('PX', expiresIn); - } + const setPromise = expiresIn ? + this.redisInstance.set(cacheKey, value, 'PX', expiresIn) : + this.redisInstance.set(cacheKey, value); - return Boolean(await withTimeout( - this.redisInstance.set(...commands), this.options.operationTimeout)); + return Boolean(await withTimeout(setPromise, this.options.operationTimeout)); } /** diff --git a/src/storage.ts b/src/storage.ts index ff0bfbf..d7a91e7 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -96,7 +96,7 @@ export interface WriteOptions extends ExpireOptions { * If the tag value is in the cache and invalidation time < current time, the tag will be considered invalid and * the record will need to be obtained using the executor */ - tags?: string[]; + tags?: string[] | (() => string[]); /** * getTags allows to detect tags for record depending on executor result */ diff --git a/src/storages/base.spec.ts b/src/storages/base.spec.ts index b6ee0fe..2c542f6 100644 --- a/src/storages/base.spec.ts +++ b/src/storages/base.spec.ts @@ -1,6 +1,6 @@ -import timeout from '../timeout'; -import { ConnectionStatus } from '../connection-status'; import TestStorageAdapter from '../adapters/test'; +import { ConnectionStatus } from '../connection-status'; +import timeout from '../timeout'; import { BaseStorage, TAGS_VERSIONS_ALIAS } from './base'; const testInterface = { @@ -303,4 +303,19 @@ describe('BaseStorage', () => { } ]); }); + + it('set creates record with static tags calculated by function', async () => { + await storage.set('test', 'test', { tags: () => ['tag'] }); + + const value = JSON.parse(testInterface.internalStorage['cache-test']); + + expect(value).toMatchObject({ + key: 'test', + permanent: true, + value: '"test"' + }); + + expect(value.tags).toMatchObject([{ name: 'tag' }]); + expect(value.expiresIn).toEqual(expect.any(Number)); + }); }); diff --git a/src/storages/base.ts b/src/storages/base.ts index 75e65cf..91ae756 100644 --- a/src/storages/base.ts +++ b/src/storages/base.ts @@ -146,7 +146,12 @@ export class BaseStorage implements Storage { * set creates new record with provided options and sets it to storage using the adapter. */ public async set(key: string, value: StorageRecordValue, options: WriteOptions = {}): Promise { - const tags: string[] = options.tags || []; + let tags: string[] = []; + if (isFunction(options.tags)) { + tags = options.tags(); + } else if (options.tags !== undefined) { + tags = options.tags; + } const dynamicTags = isFunction(options.getTags) ? options.getTags(value) : []; if (!Array.isArray(dynamicTags)) {