Description
Async Storage v2
Motivation
A few months back, due to the lean core effort, Async Storage
was moved to a standalone repo, making it more open for bug fixes and feature implementations. By adding semantic-versioning, we've made sure that releases happen more often, keeping the library in a good shape.
One of the discussed topics among the community was adding support for out of the tree platforms, so in a case where you have multi-platform project (say, Web app and RN app, sharing components and/or business logic), you don't have to use two different libraries for persisting data.
In a new major release, I'd like to introduce a concept of replaceable storage backends, making Async Storage more open not only to other platforms but also to other storage implementations.
Main changes
Current Async Storage
is strictly coupled with its storage implementation (SQLite
for Android and serialized dictionary/files for iOS). The replaceable storage feature would give you the ability to use adequate storage for a given task - think of using Shared Preferences for simple key-values, while still being able to use SQLite
database for larger data, using the same API.
Key benefits :
- Use one or more storages that matches your application needs, using the same API
- Support for out-of-the-tree platforms
- Storages are developed independently
Implementation
There are three main parts of this feature:
- Async Storage factory
- Async Storage Public API
- Storage implementation (using Common Storage API)
Async Storage Factory
This factory class creates an instance of AsyncStorage
with selected storage set. You can pick storage used based on platform or other condition.
class AsyncStorageFactory {
static create(storage: AsyncStorageBackend, opts?: FactoryOptions): AsyncStorage {}
}
Creation of Async Storage can be enhanced by passing FactoryOptions
options. Features such as logging can be set up here (added later, over time).
type FactoryOptions {
// use default or provide logger function
enableLogging: (logger?: boolean | function): any
}
Public API
class AsyncStorage {
/*
* expected to return a value stored under 'key'
* `null` when value not found
*/
get(key: string, options?: AsyncStorageOptions): Promise<mixed> {}
/*
* expected to save `item`, using `key`
*/
set(key: string, item: mixed, options?: AsyncStorageOptions): Promise<void> {}
/*
* expected to get a value for each passed key
* returns array with retrieved items
*/
getMultiple(
keys: Array<string>,
options?: AsyncStorageOptions,
): Promise<Array<mixed>> {}
/*
* expected to save key-values in storage
*/
setMultiple(keyValueArray: Array<Object>, options?: AsyncStorageOptions): Promise<void> {}
/*
* expected to removed item, stored under `key`
* returned value denotes success of operation ( true = success )
*/
remove(key: string, options?: AsyncStorageOptions): Promise<void> {}
/*
* expected to remove multiple values
*/
removeMultiple(keys: Array<string>, options?: AsyncStorageOptions): Promise<void> {}
/*
* expected to return all keys used to store values
*/
getKeys(options?: AsyncStorageOptions): Promise<Array<string>> {}
/*
* expected to clear all stored data in storage
*/
removeWholeDataFromTheStorage(options?: AsyncStorageOptions): Promise<void>
/*
* returns the instance of the storage backend. Useful if it has additional functionality not covered by public API
*/
instance(): AsyncStorageBackend
}
-
Supported actions are
write/update
,read
anddelete
. -
Promise based API (no callbacks). This is mainly to focus on one API model and leverage Promise API (
callbacks
could be implemented throughAsyncStorageOptions
). -
AsyncStorageOptions
is optional config passed down to storage implementation. Type is dependent on storage used.
type AsyncStorageOptions<T> = T;
Storage Backend API
interface AsyncStorageBackend<T> {
getSingle<T>(key: string, opts?: T): Promise<T | null>
setSingle<T>(key: string, value: T, opts?: T): Promise<void>
getMany(keys: Array<string>, opts?: T): Promise<Array<any>>
setMany(values: Array<{ [key: string]: any }>, opts?: T): Promise<void>
removeSingle(key: string, opts?: T): Promise<void>
removeMany(keys: Array<string>, opts?: T): Promise<void>
getKeys(opts?: T): Promise<Array<string>>
dropStorage(opts?: T): Promise<void>
}
AsyncStorageBackend
interface (Common Storage API
on the diagram) must be applied by any Storage Backend in order to be valid. Those methods are called by Public API
, passing options
down.
As an example, where's how Storage Backend would look for a Web (using localStorage
):
class WebStorageBackend implements AsyncStorageBackend<OptionsType> {
async getSingle(key: string, opts?: OptionsType) {
return window.localStorage.getItem(key)
}
async setSingle(key: string, value: any, opts?: OptionsType) {
return window.localStorage.setItem(key, value)
}
// ...
}
Other changes
-
Migration to Typescript (keeping support for Flow)
-
Monorepo (see questions below)
-
TBD.
Questions
-
API (both public and common storage)
-
Do we plan to maintain Storage Backends in the main repo? If yes, should we move to monorepo?