diff --git a/README.md b/README.md index 7143140..62578c5 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ `use-cache-helper` provides helper functions to easily manage and scale your redis and database caching strategies. +## Install + +``` +npm i use-cache-helper +``` + ## Initialize ```ts diff --git a/package-lock.json b/package-lock.json index 9812f28..394bba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,22 @@ { "name": "use-cache-helper", - "version": "0.2405.09", + "version": "0.2406.1102", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "use-cache-helper", - "version": "0.2405.09", + "version": "0.2406.1102", "license": "ISC", "dependencies": { - "@upstash/redis": "^1.30.1", + "@upstash/redis": "^1.31.5", "ioredis": "^5.4.1" }, "devDependencies": { "@eslint/js": "^9.1.1", "@types/node": "^20.12.7", "cross-env": "^7.0.3", + "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "globals": "^15.1.0", @@ -410,9 +411,9 @@ "dev": true }, "node_modules/@upstash/redis": { - "version": "1.30.1", - "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.30.1.tgz", - "integrity": "sha512-Cmk2cvm1AcD6mKLg/UFhQDzM+H1HsX/k5ufvNL4Kii8DsMTKmadMJ1rRZEGQ/SM7H51EeOL/YSa6K2EPc1SYPA==", + "version": "1.31.5", + "resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.31.5.tgz", + "integrity": "sha512-2MatqeqftroSJ9Q+pqbyGAIwXX6KEPtUTUna2c/fq09h12ffwvltDTgfppeF+NzJo/SyZfHY8e1RoflduMbz1A==", "dependencies": { "crypto-js": "^4.2.0" } @@ -669,6 +670,18 @@ "node": ">=6.0.0" } }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", diff --git a/package.json b/package.json index f63a013..2696bc2 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@eslint/js": "^9.1.1", "@types/node": "^20.12.7", "cross-env": "^7.0.3", + "dotenv": "^16.4.5", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "globals": "^15.1.0", @@ -35,7 +36,7 @@ "typescript-eslint": "^7.8.0" }, "dependencies": { - "@upstash/redis": "^1.30.1", + "@upstash/redis": "^1.31.5", "ioredis": "^5.4.1" }, "repository": { diff --git a/src/features.ts b/src/features/index.ts similarity index 95% rename from src/features.ts rename to src/features/index.ts index 8a9eedf..8facbaa 100644 --- a/src/features.ts +++ b/src/features/index.ts @@ -1,6 +1,6 @@ -import { checkRedis } from './helpers'; -import { UseCacheError } from './errors'; -import { store } from './store'; +import { checkRedis } from '../helpers'; +import { UseCacheError } from '../errors'; +import { store } from '../store'; import { IAppInitParams, IGetOrRefreshDataInPaginatedListParams, @@ -11,7 +11,7 @@ import { IRemoveItemFromPaginatedListParams, ISetParams, IUpdateItemScoreFromPaginatedList, -} from './types'; +} from '../types'; /** * Get latest cache data or force refresh before returning the value @@ -145,7 +145,7 @@ export const getOrRefreshDataInPaginatedList = async ( await updateItemScoreFromPaginatedList({ id, score, - key: listKey, + listKey, }); } @@ -158,7 +158,7 @@ export const getOrRefreshDataInPaginatedList = async ( * @param {Object} params * @param {string} params.key - Data cache key * @param {string | number | Object} params.value - Your data for the cache key - * @param {number} expiry - (optional) Expiry in seconds. No expiry set by default. + * @param {number} params.expiry - (optional) Expiry in seconds. No expiry set by default. * @returns {string} 'OK' | 'Error' */ export const set = async (params: ISetParams): Promise => { @@ -229,7 +229,7 @@ export const init = (params: IAppInitParams): void => { * Get paginated list by page. * Always in ascending order. * @param {Object} params - * @param {string} params.key - Your list's cache key + * @param {string} params.listKey - Your list's cache key * @param {number} params.page - Target page * @param {number} params.sizePerPage - Total items in a single page * @param {boolean} params.ascendingOrder - (optional) Fetch list in ascending order. High to low by default. @@ -238,7 +238,7 @@ export const init = (params: IAppInitParams): void => { export const getPaginatedListByPage = async ( params: IGetPaginatedListByPageParams ): Promise => { - const { page, sizePerPage, key, ascendingOrder = false } = params; + const { page, sizePerPage, listKey: key, ascendingOrder = false } = params; const start = (page - 1) * sizePerPage; const end = start + (sizePerPage - 1); const items: { id: string; score: number }[] = []; @@ -351,7 +351,7 @@ export const insertRecordsToPaginatedList = async (params: { await insertToPaginatedList({ score, id, - key: listKey, + listKey, }); if (cachePayload) { @@ -377,7 +377,7 @@ export const insertRecordsToPaginatedList = async (params: { * Insert an item to the list using the item ID. * Use non-zero & non-negative scores. * @param {Object} params - * @param {string} params.key - Your list's cache key + * @param {string} params.listKey - Your list's cache key * @param {string} params.id - Data id * @param {number} params.score - (optional) Score order of the item in the paginated list, determining its placement. * @returns {string} @@ -385,7 +385,7 @@ export const insertRecordsToPaginatedList = async (params: { export const insertToPaginatedList = async ( params: IInsertPaginatedListItemParams ): Promise => { - const { score, key, id } = params; + const { score, listKey: key, id } = params; const total = await getPaginatedListTotalItems(key); // get count const maxPaginatedItems = store.maxPaginatedItems; const redis = store.redis; @@ -423,14 +423,14 @@ export const insertToPaginatedList = async ( /** * Remove item from the list. * @param {Object} params - * @param {string} params.key - Your list's cache key + * @param {string} params.listKey - Your list's cache key * @param {string} params.id - Item ID * @returns {string} 'OK' | 'Error' */ export const removeItemFromPaginatedList = async ( params: IRemoveItemFromPaginatedListParams ): Promise => { - const { id, key } = params; + const { id, listKey: key } = params; if (!id) { throw new UseCacheError('removeItemFromPaginatedList(): Invalid id.'); @@ -459,7 +459,7 @@ export const removeItemFromPaginatedList = async ( /** * Update the score of an item in the paginated list to move it up or down in the order. * @param {Object} params - * @param {string} params.key - Your list's cache key + * @param {string} params.listKey - Your list's cache key * @param {string} params.id - Item ID* * @param {number} params.score - Score order of the item in the paginated list, determining its placement. * @returns @@ -467,7 +467,7 @@ export const removeItemFromPaginatedList = async ( export const updateItemScoreFromPaginatedList = async ( params: IUpdateItemScoreFromPaginatedList ): Promise => { - const { score, id, key } = params; + const { score, id, listKey: key } = params; if (!id) { throw new UseCacheError('updateItemScoreFromPaginatedList(): Invalid id.'); diff --git a/src/features/paginated.ts b/src/features/paginated.ts new file mode 100644 index 0000000..9b41f2c --- /dev/null +++ b/src/features/paginated.ts @@ -0,0 +1,62 @@ +import { store } from 'store'; +import { checkRedis } from '../helpers'; + +/** + * Check if Redis stack schema for paginated list already exists. + * @returns {boolean} true or false + */ +export const validateSchemaAlreadyExists = async ( + listKey: string +): Promise => { + checkRedis(); + + try { + const redis = store.redis; + + if (redis) { + await redis.call('FT.INFO', `${listKey || ''}_idx`); + } else { + console.log( + 'UseCacheError: validateSchemaAlreadyExists() is not supported for @upstash/redis instance' + ); + + return false; + } + + return true; + } catch (err) { + return false; + } +}; + +/** + * Define schema for your paginated list. + * This allows you to call searchPaginatedListByProperty() and query your paginated data. + * @param {string} listKey Your list's cache key + * @param {Object} properties + */ +export const createSchemaForSearchableList = ( + listKey: string, + properties: Record +) => { + if (properties && typeof properties === 'object') { + const keys = Object.keys(properties); + + for (let i = 0; i < keys.length; i++) { + const property = properties[keys[i]]; + + if (property === 'string' || property === 'number') { + // @todo add schema + } + } + } +}; + +export const searchPaginatedListByProperty = ( + listKey: string, + properties: Record +) => { + if (listKey && properties && typeof properties === 'object') { + // @todo search by property + } +}; diff --git a/src/types/index.ts b/src/types/index.ts index b07e1c2..d0b995f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -41,25 +41,25 @@ export interface IAppStoreRef { } export interface IGetPaginatedListByPageParams { - key: string; + listKey: string; page: number; sizePerPage: number; ascendingOrder?: boolean; } export interface IInsertPaginatedListItemParams { - key: string; + listKey: string; id: string; score?: number; } export interface IRemoveItemFromPaginatedListParams { - key: string; + listKey: string; id: string; } export interface IUpdateItemScoreFromPaginatedList { - key: string; + listKey: string; id: string; score: number; } diff --git a/tests/index.ts b/tests/index.ts index 0646dab..13b5b79 100644 --- a/tests/index.ts +++ b/tests/index.ts @@ -1,19 +1,36 @@ -import { getOrRefresh } from '../src/features'; +import 'dotenv/config'; +import { init, getOrRefresh } from '../src/features'; +import { Redis } from '@upstash/redis'; + +interface IUserProfile { + id: string; + name: string; +} + +export const redis = new Redis({ + url: process.env.UPSTASH_REDIS_REST_URL || '', + token: process.env.UPSTASH_REDIS_REST_TOKEN || '', +}); export const start = async () => { - interface IUserProfile { - id: string; - name: string; - } + await testGetOrRefresh(); +}; + +const testGetOrRefresh = async () => { const id = 'test-user-id'; const res = await getOrRefresh({ key: 'userProfile' + id, async cacheRefreshHandler(): Promise { - return {} as IUserProfile; + return { + id, + name: 'TestUserProfile', + } as IUserProfile; }, }); if (res?.id) { - // + console.log('getOrRefresh', res); } }; + +init({ upstashRedis: redis, maxPaginatedItems: 100 });