-
-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: to add cache-mode to adapter (#1888)
* feat: to add cache-mode to adapter * feat: add new cache module abstracting over specific caches * refactor: to make cache require adapter identifier * refactor: to write into cache module * refactor: shamefully cast the type * refactor: shamefully cast the type * refactor: one cache module to rule them all * feat: add cache mode to http and graphql adapter * test: adjust test case for cache mode * refactor: gimme some constants * refactor: to not flush flags when reconfiguring * refactor: to always flush on reconfigure * fix: to not flush initial remote fetching * refactor: to use sync import to get flags * test: add initial set of tests * test: add test case for cached flag restoring * Create chilled-phones-greet.md
- Loading branch information
Showing
27 changed files
with
609 additions
and
245 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
--- | ||
"@flopflip/cache": major | ||
"@flopflip/graphql-adapter": minor | ||
"@flopflip/http-adapter": minor | ||
"@flopflip/launchdarkly-adapter": major | ||
"@flopflip/react": minor | ||
"@flopflip/types": minor | ||
--- | ||
|
||
The release adds a new `cacheMode` property on the `adapterArgs` of an adapter. | ||
|
||
Using the `cacheMode` you can opt into an `eager` or `lazy`. The `cacheMode` allows you to define when remote flags should take affect in an application. Before `flopflip` behaved always `eager`. This remains the case when passing `eager` or `null` as the `cacheMode`. In `lazy` mode `flopflip` will not directly flush remote values and only silently put them in the cache. They would then take effect on the next render or `reconfigure`. | ||
|
||
In short, the `cacheMode` can be `eager` to indicate that remote values should have effect immediately. The value can also be `lazy` to indicate that values should be updated in the cache but only be applied once the adapter is configured again | ||
|
||
With the `cacheMode` we removed some likely unused functionality which explored similar ideas before: | ||
|
||
1. `unsubscribeFromCachedFlags`: This is now always the case. You can use the `lazy` `cacheMode` to indicate that you don't want flags to take immediate effect | ||
2. `subscribeToFlagChanges`: This is now always true. You can't opt-out of the flag subscription any longer |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"name": "@flopflip/cache", | ||
"version": "13.5.2", | ||
"description": "Caching for flipflop adapters", | ||
"main": "dist/flopflip-cache.cjs.js", | ||
"module": "dist/flopflip-cache.esm.js", | ||
"files": [ | ||
"readme.md", | ||
"dist/**" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/tdeekens/flopflip.git", | ||
"directory": "packages/cache" | ||
}, | ||
"author": "Tobias Deekens <[email protected]>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/tdeekens/flopflip/issues" | ||
}, | ||
"homepage": "https://github.com/tdeekens/flopflip#readme", | ||
"keywords": [ | ||
"feature-flags", | ||
"feature-toggles", | ||
"cache", | ||
"client" | ||
], | ||
"dependencies": { | ||
"@flopflip/localstorage-cache": "13.6.0", | ||
"@flopflip/sessionstorage-cache": "13.6.0", | ||
"@flopflip/types": "13.6.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { adapterIdentifiers, cacheIdentifiers } from '@flopflip/types'; | ||
|
||
import { | ||
getAllCachedFlags, | ||
getCache, | ||
getCachedFlags, | ||
getCachePrefix, | ||
} from './cache'; | ||
|
||
const cacheKey = 'test'; | ||
|
||
describe('general caching', () => { | ||
it('should allow writing values to the cache', async () => { | ||
const flags = { | ||
flag1: true, | ||
}; | ||
const cache = await getCache( | ||
cacheIdentifiers.session, | ||
adapterIdentifiers.memory, | ||
cacheKey | ||
); | ||
|
||
cache.set(flags); | ||
|
||
expect(cache.get()).toStrictEqual(flags); | ||
}); | ||
|
||
it('should allow unsetting values from the cache', async () => { | ||
const flags = { | ||
flag1: true, | ||
}; | ||
const cache = await getCache( | ||
cacheIdentifiers.session, | ||
adapterIdentifiers.memory, | ||
cacheKey | ||
); | ||
|
||
cache.set(flags); | ||
cache.unset(); | ||
|
||
expect(cache.get()).toBeNull(); | ||
}); | ||
|
||
it('should update a referencing cache to the values cache', async () => { | ||
const flags = { | ||
flag1: true, | ||
}; | ||
const cache = await getCache( | ||
cacheIdentifiers.session, | ||
adapterIdentifiers.memory, | ||
cacheKey | ||
); | ||
|
||
cache.set(flags); | ||
|
||
expect(sessionStorage.getItem).toHaveBeenLastCalledWith( | ||
`${getCachePrefix(adapterIdentifiers.memory)}/${cacheKey}/flags` | ||
); | ||
}); | ||
}); | ||
|
||
describe('flag caching', () => { | ||
describe('with a single adapter', () => { | ||
it('should allow writing and getting cached flags', async () => { | ||
const flags = { | ||
flag1: true, | ||
}; | ||
const cache = await getCache( | ||
cacheIdentifiers.session, | ||
adapterIdentifiers.memory, | ||
cacheKey | ||
); | ||
|
||
cache.set(flags); | ||
|
||
expect( | ||
getCachedFlags(cacheIdentifiers.session, adapterIdentifiers.memory) | ||
).toStrictEqual(flags); | ||
|
||
expect(sessionStorage.getItem).toHaveBeenLastCalledWith( | ||
`${getCachePrefix(adapterIdentifiers.memory)}/${cacheKey}/flags` | ||
); | ||
}); | ||
}); | ||
describe('with a multiple adapters', () => { | ||
it('should allow writing and getting cached flags for all adapters', async () => { | ||
const memoryAdapterFlags = { | ||
flag1: true, | ||
}; | ||
const localstorageAdapterFlags = { | ||
flag2: true, | ||
}; | ||
|
||
const memoryAdapterCache = await getCache( | ||
cacheIdentifiers.session, | ||
adapterIdentifiers.memory, | ||
cacheKey | ||
); | ||
const localstorageAdapterCache = await getCache( | ||
cacheIdentifiers.session, | ||
adapterIdentifiers.localstorage, | ||
cacheKey | ||
); | ||
|
||
localstorageAdapterCache.set(localstorageAdapterFlags); | ||
|
||
const fakeAdapter = { | ||
effectIds: [adapterIdentifiers.memory, adapterIdentifiers.localstorage], | ||
}; | ||
|
||
expect( | ||
getAllCachedFlags(fakeAdapter, cacheIdentifiers.session) | ||
).toStrictEqual({ ...memoryAdapterFlags, ...localstorageAdapterFlags }); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { | ||
cacheIdentifiers, | ||
type TAdapter, | ||
type TAdapterIdentifiers, | ||
type TCacheIdentifiers, | ||
type TFlags, | ||
} from '@flopflip/types'; | ||
|
||
const FLAGS_CACHE_KEY = 'flags'; | ||
const FLAGS_REFERENCE_CACHE_KEY = 'flags-reference'; | ||
|
||
function getCachePrefix(adapterIdentifiers: TAdapterIdentifiers) { | ||
return `@flopflip/${adapterIdentifiers}-adapter`; | ||
} | ||
|
||
async function importCache(cacheIdentifier: TCacheIdentifiers) { | ||
let cacheModule; | ||
|
||
switch (cacheIdentifier) { | ||
case cacheIdentifiers.local: { | ||
cacheModule = await import('@flopflip/localstorage-cache'); | ||
break; | ||
} | ||
|
||
case cacheIdentifiers.session: { | ||
cacheModule = await import('@flopflip/sessionstorage-cache'); | ||
break; | ||
} | ||
} | ||
|
||
return cacheModule; | ||
} | ||
|
||
async function getCache( | ||
cacheIdentifier: TCacheIdentifiers, | ||
adapterIdentifiers: TAdapterIdentifiers, | ||
cacheKey?: string | ||
) { | ||
const cacheModule = await importCache(cacheIdentifier); | ||
|
||
const CACHE_PREFIX = getCachePrefix(adapterIdentifiers); | ||
const createCache = cacheModule.default; | ||
const flagsCachePrefix = [CACHE_PREFIX, cacheKey].filter(Boolean).join('/'); | ||
|
||
const flagsCache = createCache({ prefix: flagsCachePrefix }); | ||
const referenceCache = createCache({ prefix: CACHE_PREFIX }); | ||
|
||
return { | ||
set(flags: TFlags) { | ||
const haveFlagsBeenWritten = flagsCache.set(FLAGS_CACHE_KEY, flags); | ||
|
||
if (haveFlagsBeenWritten) { | ||
referenceCache.set( | ||
FLAGS_REFERENCE_CACHE_KEY, | ||
[flagsCachePrefix, FLAGS_CACHE_KEY].join('/') | ||
); | ||
} | ||
|
||
return haveFlagsBeenWritten; | ||
}, | ||
get() { | ||
return flagsCache.get(FLAGS_CACHE_KEY); | ||
}, | ||
unset() { | ||
referenceCache.unset(FLAGS_REFERENCE_CACHE_KEY); | ||
|
||
return flagsCache.unset(FLAGS_CACHE_KEY); | ||
}, | ||
}; | ||
} | ||
|
||
function getCachedFlags( | ||
cacheIdentifier: TCacheIdentifiers, | ||
adapterIdentifiers: TAdapterIdentifiers | ||
): TFlags { | ||
const CACHE_PREFIX = getCachePrefix(adapterIdentifiers); | ||
|
||
const cacheModule = | ||
cacheIdentifier === cacheIdentifiers.local ? localStorage : sessionStorage; | ||
const flagReferenceKey = [CACHE_PREFIX, FLAGS_REFERENCE_CACHE_KEY].join('/'); | ||
|
||
const referenceToCachedFlags = cacheModule.getItem(flagReferenceKey); | ||
|
||
if (referenceToCachedFlags) { | ||
try { | ||
const cacheKey: string = JSON.parse(referenceToCachedFlags); | ||
const cachedFlags = cacheModule.getItem(cacheKey); | ||
|
||
if (cacheKey && cachedFlags) { | ||
return JSON.parse(cachedFlags); | ||
} | ||
} catch (error) { | ||
console.warn( | ||
`@flopflip/cache: Failed to parse cached flags from ${cacheIdentifier}.` | ||
); | ||
} | ||
} | ||
|
||
return {}; | ||
} | ||
|
||
function getAllCachedFlags( | ||
adapter: TAdapter, | ||
cacheIdentifier?: TCacheIdentifiers | ||
) { | ||
if (!cacheIdentifier) { | ||
return {}; | ||
} | ||
|
||
if (adapter.effectIds) { | ||
return adapter.effectIds.reduce( | ||
(defaultFlags, effectId) => ({ | ||
...defaultFlags, | ||
...getCachedFlags(cacheIdentifier, effectId), | ||
}), | ||
{} | ||
); | ||
} | ||
|
||
return getCachedFlags(cacheIdentifier, adapter.id); | ||
} | ||
|
||
export { getAllCachedFlags, getCache, getCachedFlags, getCachePrefix }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
const version = '__@FLOPFLIP/VERSION_OF_RELEASE__'; | ||
|
||
export { version }; | ||
export { getAllCachedFlags, getCache, getCachedFlags } from './cache'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
const version = '__@FLOPFLIP/VERSION_OF_RELEASE__'; | ||
|
||
export { getAllCachedFlags, getCache, getCachedFlags } from './cache'; | ||
export { version }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../tsconfig.json", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"extends": "../../tsconfig.lint.json", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.