Skip to content

[RFC] Async Storage v2 #113

Closed
Closed
@krizzu

Description

@krizzu

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)

AS

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
}
  1. Supported actions are write/update, read and delete.

  2. Promise based API (no callbacks). This is mainly to focus on one API model and leverage Promise API (callbacks could be implemented through AsyncStorageOptions).

  3. 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?

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions