diff --git a/CHANGELOG.md b/CHANGELOG.md index e9abae1..47fcda3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## RoadMap ### Add - - tests + - *finish support for LocalStorage (e.g. add multiSet)!* + - *rewrite EnchantedInMemoryCache into `class`!* + - add tests - ability for 2-ways updating linked/nested queries as optional - migration support - support Fragments @@ -15,6 +17,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased +## v1.2.0-beta.0 +### Changed + **Breaking changes* + - *added as 3rd EnchantedInMemoryCache param `AppStorage`* - support of both `AsyncStorage` and `LocalStorage` via Dependency Injection as `GraphQLStorage` (previous `storage`) + - EnchantedInMemoryCache 3rd param `logs` became 4th + - `AppStorage` and `GQLStorage` are not more available directly and as set of static only methods - they are created with dependencies provided as params in `enchantInMemoryCache` and set as properties to the new "enchanted" cache: + ```javascript +const cache = createEnchantedInMemoryCache(...); +const GQLStorage = cache.GQLStorage; // to get `GQLStorage` +const AppStorage = cache.AppStorage; // to get `AppStorage` + ``` + ## v1.1.3 ### Changed - _Readme.md_ - fixed definitions, improved by new changes diff --git a/README.md b/README.md index 5591fc5..544885a 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,13 @@ import { InMemoryCache } from 'apollo-cache-inmemory'; import ApolloClient from 'apollo-client'; import { withClientState } from 'apollo-link-state'; import { ApolloLink } from 'apollo-link'; -// ... +// if React Native +// up to RN v0.58 +// import { AsyncStorage } from 'react-native'; +// since RN v0.59 +import AsyncStorage from '@react-native-community/async-storage'; +// if Web just use window.LocalStorage + const inMemoryCache = new InMemoryCache({ // ... }); @@ -190,8 +196,12 @@ const logs = { const cache = createEnchantedInMemoryCache( inMemoryCache, subscribedQueries, + AsyncStorage, // or LocalStorage logs, + // alternative to `GraphQLStorage` class, eg for wrapper Realm ); +const GQLStorage = cache.GQLStorage; // to get `GQLStorage` +const AppStorage = cache.AppStorage; // to get `AppStorage` - AsyncStorage or LocalStorage // ... const stateLink = withClientState({ cache, diff --git a/package.json b/package.json index 1cda41d..c3e7975 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "apollo-enchanted-cache-inmemory", - "version": "1.1.3", + "version": "1.2.0-beta.0", "description": "Apollo InMemoryCache wrapper for storing selected only queries and for updating linked/nested without IDs", "main": "src/index.js", "scripts": { @@ -29,15 +29,16 @@ "keywords": [ "apollo", "cache", + "store", + "save", + "queries", + "query", "storage", + "selected", "restore", - "store", "update", "nest", - "bind", - "query", - "queries", - "selected" + "bind" ], "author": "Kosiak MD", "license": "MIT", diff --git a/src/AppStorage/AppStorage.js b/src/AppStorage/AppStorage.js index f89f979..3a05afb 100644 --- a/src/AppStorage/AppStorage.js +++ b/src/AppStorage/AppStorage.js @@ -1,6 +1,68 @@ -import { AsyncStorage } from 'react-native'; +/** + * @typedef {{ + * getItem: Function, + * setItem: Function, + * removeItem: Function, + * clear: Function, + * getAllKeys: Function, + * multiGet?: Function, + * multiRemove?: Function, + * mergeItem?: Function, + * }} AnyStorage + * */ class AppStorage { + /** @type AnyStorage || null */ + storage = null; + + constructor(storage) { + if (storage) { + this.storage = storage; + if (!this.storage.multiGet || !this.storage.multiRemove) { + this.enchantLocalStorage(this.storage); + } + } else { + throw new Error('No Storage provided'); + } + } + + enchantLocalStorage(localStorage) { + /** add multiGet */ + localStorage.multiGet = async (keys, callback) => { + const errors = []; + const results = []; + const interCallback = (error, result) => { + if (error) errors.push(error); + if (result) results.push(result); + }; + try { + return await Promise.all(keys.map(key => this.get(key, interCallback))); + } catch (e) { + throw e; + } finally { + if (callback) callback(errors, results); + } + }; + /** add multiRemove */ + localStorage.multiRemove = async (keys, callback) => { + const errors = []; + const results = []; + const interCallback = (error, result) => { + if (error) errors.push(error); + if (result) results.push(result); + }; + try { + return await Promise.all( + keys.map(key => this.remove(key, interCallback)), + ); + } catch (e) { + throw e; + } finally { + if (callback) callback(errors, results); + } + }; + } + /** * @callback storageCallback * @param {Error|null} error @@ -23,8 +85,8 @@ class AppStorage { * @param {getCallback?} callback * @return {Promise} * */ - static async get(key, callback) { - return AsyncStorage.getItem(key, callback); + async get(key, callback) { + return this.storage.getItem(key, callback); } /** @@ -34,12 +96,12 @@ class AppStorage { * @return void */ /** - * @param {Query | String} key + * @param {Array} keys * @param {multiActionCallback?} callback * @return {Promise} * */ - static async multiGet(key, callback) { - return AsyncStorage.multiGet(key, callback); + async multiGet(keys, callback) { + return this.storage.multiGet(keys, callback); } /** @@ -47,8 +109,8 @@ class AppStorage { * @param {multiActionCallback?} callback * @return {Promise} * */ - static async multiRemove(key, callback) { - return AsyncStorage.multiRemove(key, callback); + async multiRemove(key, callback) { + return this.storage.multiRemove(key, callback); } /** @@ -56,8 +118,8 @@ class AppStorage { * @param {String} data * @param {storageCallback?} callback * */ - static async set(key, data, callback) { - return AsyncStorage.setItem(key, data, callback); + async set(key, data, callback) { + return this.storage.setItem(key, data, callback); } /** @@ -65,8 +127,8 @@ class AppStorage { * @param {String} data * @param {storageCallback?} callback * */ - static async merge(key, data, callback) { - return AsyncStorage.mergeItem(key, data, callback); + async merge(key, data, callback) { + return this.storage.mergeItem(key, data, callback); } /** @@ -74,24 +136,24 @@ class AppStorage { * @param {storageCallback?} callback * @return {Promise} * */ - static async remove(key, callback) { - return AsyncStorage.removeItem(key, callback); + async remove(key, callback) { + return this.storage.removeItem(key, callback); } /** * @param {storageCallback?} callback * @return {Promise} * */ - static async reset(callback) { - return AsyncStorage.clear(callback); + async reset(callback) { + return this.storage.clear(callback); } /** * @param {keysCallback?} callback * @return {Promise} * */ - static async getKeys(callback) { - return AsyncStorage.getAllKeys(callback); + async getKeys(callback) { + return this.storage.getAllKeys(callback); } } diff --git a/src/GQLStorage/GQLStorage.js b/src/GQLStorage/GQLStorage.js index f59a35f..69589a9 100644 --- a/src/GQLStorage/GQLStorage.js +++ b/src/GQLStorage/GQLStorage.js @@ -20,6 +20,10 @@ class GQLStorage { return `${GQLStorage.queryMark}:${queryName}`; } + constructor(appStorage) { + this.appStorage = new AppStorage(appStorage); + } + /** * @static * @async @@ -27,9 +31,9 @@ class GQLStorage { * @param {Object | String} storeData * @return {Promise} * */ - static async saveQuery(query, storeData) { + async saveQuery(query, storeData) { const queryName = GQLStorage.getQueryName(query); - return AppStorage.set(queryName, JSON.stringify(storeData)); + return this.appStorage.set(queryName, JSON.stringify(storeData)); } /** @@ -45,9 +49,9 @@ class GQLStorage { * @param {getCallback?} callback * @return {Promise} * */ - static async getQuery(query, callback) { + async getQuery(query, callback) { const queryName = GQLStorage.getQueryName(query); - const result = await AppStorage.get(queryName, callback); + const result = await this.appStorage.get(queryName, callback); return JSON.parse(result); } @@ -64,9 +68,9 @@ class GQLStorage { * @param {multiActionCallback?} callback * @return {Promise} * */ - static async multiGet(queries, callback) { + async multiGet(queries, callback) { const keys = queries.map(GQLStorage.getQueryName); - const results = await AppStorage.multiGet(keys, callback); + const results = await this.appStorage.multiGet(keys, callback); return results.map(keyValueArray => JSON.parse(keyValueArray[1])); } @@ -77,9 +81,9 @@ class GQLStorage { * @param {multiActionCallback?} callback * @return {Promise} * */ - static async multiRemove(queries, callback) { + async multiRemove(queries, callback) { const keys = queries.map(GQLStorage.getQueryName); - await AppStorage.multiRemove(keys, callback); + await this.appStorage.multiRemove(keys, callback); return true; } @@ -96,9 +100,9 @@ class GQLStorage { * @param {removeCallback?} callback * @return {Promise} * */ - static async removeQuery(query, callback) { + async removeQuery(query, callback) { const queryName = GQLStorage.getQueryName(query); - return AppStorage.remove(queryName, callback); + return this.appStorage.remove(queryName, callback); } } diff --git a/src/index.js b/src/index.js index 4771eff..affca8e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -export GQLStorage from './GQLStorage'; export EnchantedPromise from './helpers/EnchantedPromise'; export * from './helpers'; export * from './utils'; diff --git a/src/lib/EnchantedInMemoryCache.js b/src/lib/EnchantedInMemoryCache.js index 5570f51..006a018 100644 --- a/src/lib/EnchantedInMemoryCache.js +++ b/src/lib/EnchantedInMemoryCache.js @@ -111,17 +111,20 @@ import EnchantedPromise from '../helpers/EnchantedPromise'; * @param {InMemoryCache | EnchantedInMemoryCache} aCache * @param {EnchantedInMemoryCacheConfig} enchantedInMemoryCacheConfig * @param {Logs?} logs - * @param {GQLStorage?} storage - storage DI + * @param {AsyncStorage || LocalStorage} AppStorage - storage DI + * @param {GQLStorage?} GraphQLStorage - storage DI * @return {EnchantedInMemoryCache} * */ // TODO better to extend but seems it's redundant for 1 override & 2 new methods const createEnchantedInMemoryCache = ( aCache, enchantedInMemoryCacheConfig, + AppStorage, logs = {}, - storage, + GraphQLStorage, ) => { - const { logCacheWrite, beforeHandlers, beforeWrite, afterWrite } = logs; + /** static begin */ + const versionQueryName = '&_cacheVersion_$'; const logError = (title, er, name = '') => { if (__DEV__) { @@ -135,6 +138,22 @@ const createEnchantedInMemoryCache = ( } } }; + /** static end */ + + /** constructor begin */ + const GQLStorage = GraphQLStorage || new DefaultGQLStorage(AppStorage); + + if (!enchantedInMemoryCacheConfig) { + throw new Error('No EnchantedInMemoryCacheConfig provided'); + } + + /** @type EnchantedPromise */ + let versionSyncing; + + const { subscribedQueries, version } = enchantedInMemoryCacheConfig; + + const { logCacheWrite, beforeHandlers, beforeWrite, afterWrite } = logs; + /** constructor end */ const asyncVersionSyncing = async (resolve, reject) => { try { @@ -165,18 +184,7 @@ const createEnchantedInMemoryCache = ( } }; - // eslint-disable-next-line - const GQLStorage = storage ? storage : DefaultGQLStorage; // for JSDoc to be handle by WebStorm IDE only - - if (!enchantedInMemoryCacheConfig) { - throw new Error('No EnchantedInMemoryCacheConfig provided'); - } - - const versionQueryName = '&_cacheVersion_$'; - /** @type EnchantedPromise */ - let versionSyncing; - - const { subscribedQueries, version } = enchantedInMemoryCacheConfig; + /** constructor */ if (version == null) { throw new Error('No version of EnchantedInMemoryCacheConfig provided'); } else { @@ -326,12 +334,14 @@ const createEnchantedInMemoryCache = ( * @return void * */ const disenchant = () => { - aCache.write = oldWrite; // eslint-disable-line - aCache.writeQuery = oldWriteQuery; // eslint-disable-line - aCache.disenchant = void 0; // eslint-disable-line - delete aCache.disenchant; // eslint-disable-line - aCache.restoreAllQueries = void 0; // eslint-disable-line - delete aCache.restoreAllQueries; // eslint-disable-line + aCache.write = oldWrite; + aCache.writeQuery = oldWriteQuery; + aCache.disenchant = void 0; + delete aCache.disenchant; + aCache.restoreAllQueries = void 0; + delete aCache.restoreAllQueries; + delete aCache.GraphQLStorage; + delete aCache.AppStorage; }; /** @@ -347,10 +357,14 @@ const createEnchantedInMemoryCache = ( } }; - aCache.write = write; // eslint-disable-line - aCache.writeQuery = writeQuery; // eslint-disable-line - aCache.disenchant = disenchant; // eslint-disable-line - aCache.restoreAllQueries = restoreAllQueries; // eslint-disable-line + /** constructor */ + /** enchant */ + aCache.write = write; + aCache.writeQuery = writeQuery; + aCache.disenchant = disenchant; + aCache.restoreAllQueries = restoreAllQueries; + aCache.GQLStorage = GQLStorage; + aCache.AppStorage = GQLStorage.appStorage; return aCache; };