diff --git a/.vscode/settings.json b/.vscode/settings.json index 25fa621..f119d8b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,12 @@ { - "typescript.tsdk": "node_modules/typescript/lib" + "typescript.tsdk": "node_modules/typescript/lib", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/node_modules": false + } } diff --git a/README.md b/README.md index 418b9d9..873211d 100644 --- a/README.md +++ b/README.md @@ -80,11 +80,10 @@ const { withRemoteStateQueryCaching, withRemoteStateMutationRegistration } = cre ``` -note, in this particular example we're using [simple-localstorage-cache](https://github.com/ehmpathy/simple-localstorage-cache), but you can use any cache which works with [with-simple-caching](https://github.com/ehmpathy/with-simple-caching), for example +note, in this particular example we're using [simple-localstorage-cache](https://github.com/ehmpathy/simple-localstorage-cache), but you can use any async cache which works with [with-simple-caching](https://github.com/ehmpathy/with-simple-caching), for example - [simple-on-disk-caching](https://github.com/ehmpathy/simple-on-disk-cache) for s3 and mounted persistance - [simple-dynamodb-caching](https://github.com/ehmpathy/simple-dynamodb-cache) for dynamodb persistance - [simple-localstorage-caching](https://github.com/ehmpathy/simple-localstorage-cache) for browser localstorage persistance -- [simple-in-memory-caching](https://github.com/ehmpathy/simple-in-memory-cache) for in-memory persistance - etc diff --git a/package-lock.json b/package-lock.json index f638ac9..4cf7144 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "1.2.0", "license": "MIT", "dependencies": { + "@types/sha.js": "^2.4.0", + "sha.js": "^2.4.11", "type-fns": "^0.4.1", - "with-simple-caching": "^0.9.5" + "with-simple-caching": "^0.10.1" }, "devDependencies": { "@types/jest": "^27.0.0", @@ -27,7 +29,7 @@ "jest": "^27.0.0", "prettier": "^2.0.4", "simple-in-memory-cache": "^0.3.0", - "simple-on-disk-cache": "^1.1.0", + "simple-on-disk-cache": "^1.3.1", "ts-jest": "^27.0.0", "typescript": "^4.5.5", "uuid": "^3.3.3" @@ -1482,8 +1484,7 @@ "node_modules/@types/node": { "version": "18.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", - "dev": true + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" }, "node_modules/@types/prettier": { "version": "2.7.1", @@ -1491,6 +1492,14 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "node_modules/@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -2061,6 +2070,12 @@ } ] }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -3908,8 +3923,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/inquirer": { "version": "6.5.2", @@ -7341,6 +7355,25 @@ "npm": ">=2.0.0" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -7380,6 +7413,18 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -7417,12 +7462,14 @@ } }, "node_modules/simple-on-disk-cache": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-on-disk-cache/-/simple-on-disk-cache-1.1.0.tgz", - "integrity": "sha512-vK8jSbhoL2Lr5sp9eI03x3+CkyjqpRIM3PM20WTQ8nXVC/PfRbQzkJVPAyUanHsO2o3oQsvHapSc9Ajh63BJJA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-on-disk-cache/-/simple-on-disk-cache-1.3.1.tgz", + "integrity": "sha512-dqHJsc1Fp/228rUI/XLO142RQVLyiyXtBUTL6CcgipzKQ0/t1GGB/lIWsvj8/aDxQMYww6jVvvssfB6Pj6SW/Q==", "dev": true, "dependencies": { - "aws-sdk": "^2.1231.0" + "aws-sdk": "^2.1231.0", + "bottleneck": "^2.19.5", + "type-fns": "^0.4.1" }, "engines": { "node": ">=8.0.0" @@ -8297,16 +8344,24 @@ } }, "node_modules/with-simple-caching": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/with-simple-caching/-/with-simple-caching-0.9.5.tgz", - "integrity": "sha512-C99GmV+eHNuFoHIErEc5gKZ8QWVdx8KopvqjcP96rd6IaeKGp6iRy4R6sgRImdXaR8/K/hk6C2fyP1QKlgBrJw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/with-simple-caching/-/with-simple-caching-0.10.1.tgz", + "integrity": "sha512-yDD32ZP9l/olJJ3ijIRxh+3vc9UPYIRITq+0tYp4I0W0e6RWAr7Dsr2Sb1x0bp9WvYg9zRvk2zMe/lFCxjlgXg==", "dependencies": { - "type-fns": "^0.4.1" + "type-fns": "^0.6.0" }, "engines": { "node": ">=8.0.0" } }, + "node_modules/with-simple-caching/node_modules/type-fns": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-0.6.0.tgz", + "integrity": "sha512-PRECv24BnwGm1/bUWtcNJYSTjM2pyZXy8B5+qINXmsfQNnhK3VFQgfn6/SzukaCdQAxz9B5LhmAuKrEcDvBQug==", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -9699,8 +9754,7 @@ "@types/node": { "version": "18.11.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==", - "dev": true + "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" }, "@types/prettier": { "version": "2.7.1", @@ -9708,6 +9762,14 @@ "integrity": "sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==", "dev": true }, + "@types/sha.js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/sha.js/-/sha.js-2.4.0.tgz", + "integrity": "sha512-amxKgPy6WJTKuw8mpUwjX2BSxuBtBmZfRwIUDIuPJKNwGN8CWDli8JTg5ONTWOtcTkHIstvT7oAhhYXqEjStHQ==", + "requires": { + "@types/node": "*" + } + }, "@types/stack-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", @@ -10109,6 +10171,12 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -11540,8 +11608,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "inquirer": { "version": "6.5.2", @@ -14078,6 +14145,11 @@ "tslib": "^1.9.0" } }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -14111,6 +14183,15 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "requires": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -14139,12 +14220,14 @@ "dev": true }, "simple-on-disk-cache": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/simple-on-disk-cache/-/simple-on-disk-cache-1.1.0.tgz", - "integrity": "sha512-vK8jSbhoL2Lr5sp9eI03x3+CkyjqpRIM3PM20WTQ8nXVC/PfRbQzkJVPAyUanHsO2o3oQsvHapSc9Ajh63BJJA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/simple-on-disk-cache/-/simple-on-disk-cache-1.3.1.tgz", + "integrity": "sha512-dqHJsc1Fp/228rUI/XLO142RQVLyiyXtBUTL6CcgipzKQ0/t1GGB/lIWsvj8/aDxQMYww6jVvvssfB6Pj6SW/Q==", "dev": true, "requires": { - "aws-sdk": "^2.1231.0" + "aws-sdk": "^2.1231.0", + "bottleneck": "^2.19.5", + "type-fns": "^0.4.1" } }, "sisteransi": { @@ -14808,11 +14891,18 @@ } }, "with-simple-caching": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/with-simple-caching/-/with-simple-caching-0.9.5.tgz", - "integrity": "sha512-C99GmV+eHNuFoHIErEc5gKZ8QWVdx8KopvqjcP96rd6IaeKGp6iRy4R6sgRImdXaR8/K/hk6C2fyP1QKlgBrJw==", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/with-simple-caching/-/with-simple-caching-0.10.1.tgz", + "integrity": "sha512-yDD32ZP9l/olJJ3ijIRxh+3vc9UPYIRITq+0tYp4I0W0e6RWAr7Dsr2Sb1x0bp9WvYg9zRvk2zMe/lFCxjlgXg==", "requires": { - "type-fns": "^0.4.1" + "type-fns": "^0.6.0" + }, + "dependencies": { + "type-fns": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fns/-/type-fns-0.6.0.tgz", + "integrity": "sha512-PRECv24BnwGm1/bUWtcNJYSTjM2pyZXy8B5+qINXmsfQNnhK3VFQgfn6/SzukaCdQAxz9B5LhmAuKrEcDvBQug==" + } } }, "word-wrap": { diff --git a/package.json b/package.json index 510eb6c..5e6aa1c 100644 --- a/package.json +++ b/package.json @@ -59,13 +59,15 @@ "jest": "^27.0.0", "prettier": "^2.0.4", "simple-in-memory-cache": "^0.3.0", - "simple-on-disk-cache": "^1.1.0", + "simple-on-disk-cache": "^1.3.1", "ts-jest": "^27.0.0", "typescript": "^4.5.5", "uuid": "^3.3.3" }, "dependencies": { + "@types/sha.js": "^2.4.0", + "sha.js": "^2.4.11", "type-fns": "^0.4.1", - "with-simple-caching": "^0.9.5" + "with-simple-caching": "^0.10.1" } } diff --git a/src/RemoteStateCache.ts b/src/RemoteStateCache.ts index 93cc133..a04d8d7 100644 --- a/src/RemoteStateCache.ts +++ b/src/RemoteStateCache.ts @@ -1,4 +1,4 @@ -import { SimpleCache } from 'with-simple-caching'; +import { SimpleAsyncCache } from 'with-simple-caching'; /** * any simple-cache can be used as a remote-state-cache as long as it also implements a `keys` method which returns all of the currently valid keys for the cache @@ -6,6 +6,6 @@ import { SimpleCache } from 'with-simple-caching'; * relevance * - in order to trigger updates/invalidations, we need to expose all of the keys the user may want to choose from to the user */ -export interface RemoteStateCache extends SimpleCache { +export interface RemoteStateCache extends SimpleAsyncCache { keys: () => Promise | string[]; } diff --git a/src/createRemoteStateCachingContext.test.ts b/src/createRemoteStateCachingContext.test.ts index 72c845d..5cd218a 100644 --- a/src/createRemoteStateCachingContext.test.ts +++ b/src/createRemoteStateCachingContext.test.ts @@ -1,8 +1,10 @@ -import { createCache } from 'simple-in-memory-cache'; +import { promises as fs } from 'fs'; +import { createCache as createOnDiskCache } from 'simple-on-disk-cache'; import { HasMetadata } from 'type-fns'; import uuid from 'uuid'; import { SimpleCache } from 'with-simple-caching'; import { createRemoteStateCachingContext } from './createRemoteStateCachingContext'; +import { defaultKeySerializationMethod } from './defaults'; import { RemoteStateCache } from './RemoteStateCache'; /** @@ -16,7 +18,21 @@ type Recipe = { steps: string[]; }; +const cacheDir = `${__dirname}/__test_assets__/__tmp__`; +const createCache = () => + createOnDiskCache({ + directoryToPersistTo: { + mounted: { + path: cacheDir, + }, + }, + }); + describe('createRemoteStateCachingContext', () => { + beforeEach(() => + // invalidate all of the current cached data, so past tests dont interfere + fs.unlink([cacheDir, '_.simple_on_disk_cache.valid_keys'].join('/')).catch(() => {}), + ); describe('caching', () => { it('should be possible to add extended caching to a query', async () => { // start the context @@ -59,9 +75,9 @@ describe('createRemoteStateCachingContext', () => { // update a request await queryGetRecipes.update({ - forKey: [queryGetRecipes.name, JSON.stringify([{ searchFor: 'smoothie' }])].join('.'), // update by key, instead of input + forKey: [queryGetRecipes.name, defaultKeySerializationMethod({ forInput: [{ searchFor: 'smoothie' }] })].join('.'), // update by key, instead of input toValue: async ({ fromCachedOutput }) => [ - ...((await fromCachedOutput) ?? []), + ...(fromCachedOutput ?? []), { title: 'new smoothie', description: 'great smothie', ingredients: [], steps: [] }, // add a new recipe to it ], }); @@ -102,7 +118,7 @@ describe('createRemoteStateCachingContext', () => { await queryGetRecipes.execute({ searchFor: 'smoothie' }); // check that the keys look correct - const keys = cache.keys(); + const keys = await cache.keys(); expect(keys.length).toEqual(2); expect(keys[0]).toEqual('queryGetRecipes.for.steak'); expect(keys[1]).toEqual('queryGetRecipes.for.smoothie'); @@ -196,24 +212,21 @@ describe('createRemoteStateCachingContext', () => { // define a mutation which we'll have as a trigger for cache invalidation const mutationAddRecipe = withRemoteStateMutationRegistration( - async ({ recipe }: { recipe: Recipe }, _: { cache: RemoteStateCache }) => recipe, + async ({ recipe }: { recipe: Recipe }, _: { cache: RemoteStateCache }) => recipe, { name: 'mutationAddRecipe', }, ); // define a mutation which we'll have as a trigger for cache update - const mutationDeleteRecipe = withRemoteStateMutationRegistration( - async (_: { recipeUuid: string }, __: { cache: RemoteStateCache }) => {}, - { - name: 'mutationDeleteRecipe', - }, - ); + const mutationDeleteRecipe = withRemoteStateMutationRegistration(async (_: { recipeUuid: string }, __: { cache: RemoteStateCache }) => {}, { + name: 'mutationDeleteRecipe', + }); // define a query that we'll be caching const apiCalls = []; const queryGetRecipes = withRemoteStateQueryCaching( - async ({ searchFor }: { searchFor: string }, _: { cache: RemoteStateCache }): Promise[]> => { + async ({ searchFor }: { searchFor: string }, _: { cache: RemoteStateCache }): Promise[]> => { apiCalls.push(searchFor); return [{ uuid: uuid(), title: '__TITLE__', description: '__DESCRIPTION__', ingredients: [], steps: [] }]; }, @@ -408,12 +421,12 @@ describe('createRemoteStateCachingContext', () => { it('should allow user to specify default context level serialization and deserialization', async () => { // start the context const { withRemoteStateQueryCaching } = createRemoteStateCachingContext({ - cache: createCache(), + cache: createCache(), serialize: { - value: async (output) => JSON.stringify(await output), + value: (output) => JSON.stringify(output), }, deserialize: { - value: async (cached) => JSON.parse(await cached), + value: (cached) => JSON.parse(cached), }, }); @@ -452,7 +465,7 @@ describe('createRemoteStateCachingContext', () => { // update a request await queryGetRecipes.update({ - forKey: [queryGetRecipes.name, JSON.stringify([{ searchFor: 'smoothie' }])].join('.'), // update by key, instead of input + forKey: [queryGetRecipes.name, defaultKeySerializationMethod({ forInput: [{ searchFor: 'smoothie' }] })].join('.'), // update by key, instead of input toValue: async ({ fromCachedOutput }) => [ ...((await fromCachedOutput) ?? []), { title: 'new smoothie', description: 'great smothie', ingredients: [], steps: [] }, // add a new recipe to it diff --git a/src/createRemoteStateCachingContext.ts b/src/createRemoteStateCachingContext.ts index 1fe2ca2..c7f775d 100644 --- a/src/createRemoteStateCachingContext.ts +++ b/src/createRemoteStateCachingContext.ts @@ -3,16 +3,15 @@ import { WithSimpleCachingOptions, LogicWithExtendableCaching, withExtendableCaching, - SimpleCacheResolutionMethod, - defaultValueDeserializationMethod, - defaultKeySerializationMethod, KeySerializationMethod, - defaultValueSerializationMethod, + WithSimpleCachingCacheOption, + WithSimpleCachingAsyncOptions, } from 'with-simple-caching'; import { RemoteStateCacheContext, RemoteStateCacheContextQueryRegistration } from './RemoteStateCacheContext'; import { RemoteStateQueryInvalidationTrigger, RemoteStateQueryUpdateTrigger } from './RemoteStateQueryCachingOptions'; import { BadRequestError } from './errors/BadRequestError'; import { RemoteStateCache } from './RemoteStateCache'; +import { defaultKeySerializationMethod, defaultValueDeserializationMethod, defaultValueSerializationMethod } from './defaults'; interface WithRemoteStateCachingOptions { /** @@ -91,7 +90,7 @@ export type QueryWithRemoteStateCachingAddTriggerMethod any, CV extends any> extends LogicWithExtendableCaching { +export interface QueryWithRemoteStateCaching any, C extends RemoteStateCache> extends LogicWithExtendableCaching { /** * the registered name of this query */ @@ -134,12 +133,9 @@ export const createRemoteStateCachingContext = < */ SLI extends any[], /** - * specifies the shared types that can be set to the cache - * - * note: - * - if it is too restrictive, you can define a serialize + deserialize method for your function's output w/ options + * the type of the cache used for operations in this context */ - SCV extends any // SCV = shared cache value + C extends RemoteStateCache >({ cache, ...defaultOptions @@ -147,7 +143,7 @@ export const createRemoteStateCachingContext = < /** * specify the cache to use across operations in this remote-state cache context */ - cache: RemoteStateCache | SimpleCacheResolutionMethod>; + cache: WithSimpleCachingCacheOption; /** * allow specifying default serialization options @@ -161,7 +157,7 @@ export const createRemoteStateCachingContext = < /** * allow specifying a default value serialization method */ - value?: (output: Promise) => SCV; + value?: Required>['serialize']['value']; }; /** @@ -171,7 +167,7 @@ export const createRemoteStateCachingContext = < /** * allow specifying a default value deserialization method */ - value?: (cached: SCV) => Promise; + value?: Required>['deserialize']['value']; }; }) => { /** @@ -206,10 +202,10 @@ export const createRemoteStateCachingContext = < * - automatically invalidating or updating the cached response for a query, triggered by mutations * - manually invalidating or updating the cached response for a query */ - const withRemoteStateQueryCaching = any, CV extends SCV = ReturnType>( + const withRemoteStateQueryCaching =
  • Promise>( logic: L, - options: Omit, 'cache'> & WithRemoteStateCachingOptions, - ): QueryWithRemoteStateCaching => { + options: Omit, 'cache'> & WithRemoteStateCachingOptions, + ): QueryWithRemoteStateCaching => { // grab the name of this query const name = extractNameFromRegistrationInputs({ operation: RemoteStateOperation.QUERY, logic, options }); @@ -219,27 +215,31 @@ export const createRemoteStateCachingContext = < const keySerializationMethodWithNamespace: KeySerializationMethod> = (...args) => [name, keySerializationMethodFromOptions(...args)].join('.'); + // define the serde methods + const valueSerializationMethod = options.serialize?.value ?? (defaultOptions.serialize?.value as any) ?? defaultValueSerializationMethod; + const valueDeserialiationMethod = options.deserialize?.value ?? (defaultOptions.deserialize?.value as any) ?? defaultValueDeserializationMethod; + // extend the logic with caching const logicExtendedWithCaching = withExtendableCaching(logic, { ...options, serialize: { key: keySerializationMethodWithNamespace, - value: options.serialize?.value ?? (defaultOptions.serialize?.value as any) ?? defaultValueSerializationMethod, + value: valueSerializationMethod, }, deserialize: { - value: options.deserialize?.value ?? (defaultOptions.deserialize?.value as any) ?? defaultValueDeserializationMethod, + value: valueDeserialiationMethod, }, - cache: cache as WithSimpleCachingOptions['cache'], // we've asserted that CV is a subset of SCV, so this in reality will work; // TODO: determine why typescript is not happy here + cache, // this works in practice // TODO: resolve the type assertion error }); // register this query - const registration: RemoteStateCacheContextQueryRegistration = { + const registration: RemoteStateCacheContextQueryRegistration = { name, query: logicExtendedWithCaching, options: { invalidatedBy: [], updatedBy: [], - deserialize: { value: options.deserialize?.value ?? defaultValueDeserializationMethod }, + deserialize: { value: valueDeserialiationMethod }, }, }; registerQueryToRemoteStateContext({ registration }); diff --git a/src/defaults.ts b/src/defaults.ts new file mode 100644 index 0000000..9602f0e --- /dev/null +++ b/src/defaults.ts @@ -0,0 +1,25 @@ +import { KeySerializationMethod, WithSimpleCachingAsyncOptions } from 'with-simple-caching'; + +import shajs from 'sha.js'; +import { RemoteStateCache } from '.'; + +export const defaultKeySerializationMethod: KeySerializationMethod = ({ forInput }) => + [ + // display a preview of the request + JSON.stringify(forInput) + .replace(/[{}[\]:]/gi, '_') + .replace(/[^0-9a-z_]/gi, '') + .replace(/__+/g, '_') + .slice(0, 50) + .replace(/^_/, '') + .replace(/_$/, ''), // stringify + replace all non-alphanumeric input + + // add a unique token, from the hashed inputs + shajs('sha256').update(JSON.stringify(forInput)).digest('hex'), + ].join('.'); + +export const defaultValueSerializationMethod: Required>['serialize']['value'] = (output) => + JSON.stringify(output); + +export const defaultValueDeserializationMethod: Required>['deserialize']['value'] = (cached) => + JSON.parse(cached);