From 3e79caafdd77d0070988548a291f564c90172976 Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Sun, 11 Aug 2019 15:22:24 +0200 Subject: [PATCH 1/7] fix: better typing --- packages/core/src/AsyncStorage.ts | 61 +++++----- packages/core/src/defaults.ts | 8 ++ packages/core/src/index.ts | 18 ++- packages/core/types/index.d.ts | 101 +++++++++++------ packages/storage-legacy/src/index.ts | 107 +++++++++++++----- packages/storage-legacy/types/index.d.ts | 44 ++++--- .../storage-legacy/types/nativeModule.d.ts | 25 ++++ 7 files changed, 259 insertions(+), 105 deletions(-) create mode 100644 packages/storage-legacy/types/nativeModule.d.ts diff --git a/packages/core/src/AsyncStorage.ts b/packages/core/src/AsyncStorage.ts index 05a00ced..1f37454c 100644 --- a/packages/core/src/AsyncStorage.ts +++ b/packages/core/src/AsyncStorage.ts @@ -1,19 +1,26 @@ +/** + * Copyright (c) React Native Community. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + import {simpleErrorHandler, simpleLogger, noop} from './defaults'; import { FactoryOptions, IStorageBackend, LoggerAction, - StorageModel, StorageOptions, } from '../types'; -class AsyncStorage> { - private readonly _backend: STR; +class AsyncStorage> { + private readonly _backend: T; private readonly _config: FactoryOptions; private readonly log: (action: LoggerAction) => void; private readonly error: (e: Error | string) => void; - constructor(storageBackend: STR, asOptions: FactoryOptions) { + constructor(storageBackend: T, asOptions: FactoryOptions) { this._backend = storageBackend; this._config = asOptions; @@ -35,13 +42,15 @@ class AsyncStorage> { : simpleErrorHandler; } } - - async get(key: string, opts: StorageOptions = null): Promise { - let value = null; + async get( + key: K, + opts: StorageOptions = null, + ): Promise { + let value = null as M[K] | null; try { this.log({ action: 'read-single', - key: key, + key: key as string, }); value = await this._backend.getSingle(key, opts); } catch (e) { @@ -51,15 +60,15 @@ class AsyncStorage> { return value; } - async set( - key: string, - value: VAL, + async set( + key: K, + value: M[K], opts: StorageOptions = null, ): Promise { try { this.log({ action: 'save-single', - key, + key: key as string, value, }); await this._backend.setSingle(key, value, opts); @@ -68,16 +77,16 @@ class AsyncStorage> { } } - async getMultiple( - keys: Array, + async getMultiple( + keys: Array, opts: StorageOptions = null, - ): Promise> { - let values: Array = []; + ): Promise<{[k in K]: M[k] | null}> { + let values = {} as {[k in K]: M[k] | null}; try { this.log({ action: 'read-many', - key: keys, + key: keys as string[], }); values = await this._backend.getMany(keys, opts); } catch (e) { @@ -87,8 +96,8 @@ class AsyncStorage> { return values; } - async setMultiple( - keyValues: Array<{[key: string]: VAL}>, + async setMultiple( + keyValues: Array<{[k in K]: M[k]}>, opts: StorageOptions = null, ): Promise { try { @@ -102,11 +111,11 @@ class AsyncStorage> { } } - async remove(key: string, opts: StorageOptions = null): Promise { + async remove(key: keyof M, opts: StorageOptions = null): Promise { try { this.log({ action: 'delete-single', - key, + key: key as string, }); await this._backend.removeSingle(key, opts); } catch (e) { @@ -115,13 +124,13 @@ class AsyncStorage> { } async removeMultiple( - keys: Array, + keys: Array, opts: StorageOptions = null, ): Promise { try { this.log({ action: 'delete-many', - key: keys, + key: keys as string[], }); await this._backend.removeMany(keys, opts); } catch (e) { @@ -129,8 +138,8 @@ class AsyncStorage> { } } - async getKeys(opts: StorageOptions = null): Promise> { - let keys: Array = []; + async getKeys(opts: StorageOptions = null): Promise> { + let keys: Array = []; try { this.log({ @@ -157,7 +166,7 @@ class AsyncStorage> { // todo: think how we could provide additional functions through AS, without returning the instance // some kind of extension-like functionality - instance(): STR { + instance(): T { return this._backend; } } diff --git a/packages/core/src/defaults.ts b/packages/core/src/defaults.ts index a7c4ef4b..345ac170 100644 --- a/packages/core/src/defaults.ts +++ b/packages/core/src/defaults.ts @@ -1,3 +1,11 @@ +/** + * Copyright (c) React Native Community. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + import {FactoryOptions, LoggerAction} from '../types'; export const factoryOptions: FactoryOptions = { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0e1a4550..b96e17ec 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,6 +1,14 @@ +/** + * Copyright (c) React Native Community. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + import AsyncStorage from './AsyncStorage'; import {factoryOptions} from './defaults'; -import {IStorageBackend, FactoryOptions, StorageModel} from '../types'; +import {IStorageBackend, FactoryOptions, EmptyStorageModel} from '../types'; class AsyncStorageFactory { constructor() { @@ -9,11 +17,11 @@ class AsyncStorageFactory { ); } - static create( - storage: STR, + static create( + storage: IStorageBackend, opts: FactoryOptions = factoryOptions, - ): AsyncStorage> { - return new AsyncStorage(storage, opts); + ) { + return new AsyncStorage>(storage, opts); } } diff --git a/packages/core/types/index.d.ts b/packages/core/types/index.d.ts index c384bc96..09700b3c 100644 --- a/packages/core/types/index.d.ts +++ b/packages/core/types/index.d.ts @@ -1,63 +1,94 @@ -export class AsyncStorage< - STR extends IStorageBackend, - VAL = StorageModel -> { - get(key: string, opts?: StorageOptions): Promise; +/** + * Copyright (c) React Native Community. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +/** + * Crates an Async Storage instance for given storage backend + * Enhanced by Factory Options, to, in example, enable logging + */ +export default class AsyncStorageFactory { + static create( + storage: IStorageBackend, + opts: FactoryOptions, + ): AsyncStorage>; +} + +/** + * Do not instantiate. Create through AsyncStorageFactory. + * Main API that will be used to call underlying storage backend + */ +export class AsyncStorage> { + get(key: K, opts?: StorageOptions): Promise; - set(key: string, value: VAL, opts?: StorageOptions): Promise; + set( + key: K, + value: M[K], + opts?: StorageOptions, + ): Promise; - getMultiple( - keys: Array, + getMultiple( + keys: Array, opts?: StorageOptions, - ): Promise>; + ): Promise<{[k in K]: M[k] | null}>; - setMultiple( - keyValues: Array<{[key: string]: VAL}>, + setMultiple( + keyValues: Array>, opts?: StorageOptions, ): Promise; - remove(key: string, opts?: StorageOptions): Promise; + remove(key: keyof M, opts?: StorageOptions): Promise; - removeMultiple(keys: Array, opts?: StorageOptions): Promise; + removeMultiple(keys: Array, opts?: StorageOptions): Promise; - getKeys(opts?: StorageOptions): Promise>; + getKeys(opts?: StorageOptions): Promise>; clearStorage(opts?: StorageOptions): Promise; - instance(): STR; + instance(): T; } -export default class AsyncStorageFactory { - static create( - storage: STR, - opts: FactoryOptions, - ): AsyncStorage>; -} - -export interface IStorageBackend { - getSingle(key: string, opts?: StorageOptions): Promise; +/** + * Interface that must be implemented by any Storage Backend + * Provides basic API for reading/writing and deleting of data + */ +export interface IStorageBackend { + getSingle( + key: K, + opts?: StorageOptions, + ): Promise; - setSingle(key: string, value: VAL, opts?: StorageOptions): Promise; + setSingle( + key: K, + value: T[K], + opts?: StorageOptions, + ): Promise; - getMany( - keys: Array, + getMany( + keys: Array, opts?: StorageOptions, - ): Promise>; + ): Promise<{[k in K]: T[k] | null}>; - setMany( - values: Array<{[key: string]: VAL}>, + setMany( + values: Array<{[k in K]: T[k]}>, opts?: StorageOptions, ): Promise; - removeSingle(key: string, opts?: StorageOptions): Promise; + removeSingle(key: keyof T, opts?: StorageOptions): Promise; - removeMany(keys: Array, opts?: StorageOptions): Promise; + removeMany(keys: Array, opts?: StorageOptions): Promise; - getKeys(opts?: StorageOptions): Promise>; + getKeys(opts?: StorageOptions): Promise>; dropStorage(opts?: StorageOptions): Promise; } +/** + * Used by Factory to enhance calls + */ export type FactoryOptions = { logger?: ((action: LoggerAction) => void) | boolean; errorHandler?: ((error: Error | string) => void) | boolean; @@ -78,8 +109,12 @@ export type LoggerAction = { key?: string | Array; }; +// Helper types + export type StorageModel = T extends IStorageBackend ? V : any; +export type EmptyStorageModel = {[key in symbol | number | string]: any}; + export type StorageOptions = { [key: string]: any; } | null; diff --git a/packages/storage-legacy/src/index.ts b/packages/storage-legacy/src/index.ts index 75425f1e..6ca81aa8 100644 --- a/packages/storage-legacy/src/index.ts +++ b/packages/storage-legacy/src/index.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) React Native Community. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. @@ -8,9 +8,11 @@ import {NativeModules} from 'react-native'; import { + EmptyStorageModel, IStorageBackend, StorageOptions, } from '@react-native-community/async-storage'; +import {ILegacyNativeModule} from '../types/nativeModule'; function convertErrors(errs?: Array | Error) { if (!errs) { @@ -19,8 +21,10 @@ function convertErrors(errs?: Array | Error) { return Array.isArray(errs) ? errs.filter(e => !!e) : [errs]; } -export default class LegacyAsyncStorage implements IStorageBackend { - private readonly _asyncStorageNativeModule: any; +export default class LegacyAsyncStorage< + T extends EmptyStorageModel = EmptyStorageModel +> implements IStorageBackend { + private readonly _asyncStorageNativeModule: ILegacyNativeModule; constructor() { this._asyncStorageNativeModule = @@ -35,17 +39,20 @@ export default class LegacyAsyncStorage implements IStorageBackend { } } - async getSingle(key: string, opts?: StorageOptions): Promise { + async getSingle( + key: K, + opts?: StorageOptions, + ): Promise { if (opts) { // noop } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this._asyncStorageNativeModule.multiGet([key], function( errors: Array, - result: Array<[any, T | null]>, + result: Array<[any, T[K] | null]>, ) { - const value = result && result[0] && result[0][1] ? result[0][1] : null; + const value = (result && result[0] && result[0][1]) || null; const errs = convertErrors(errors); if (errs && errs.length) { reject(errs[0]); @@ -56,12 +63,16 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } - async setSingle(key: string, value: T, opts?: StorageOptions): Promise { + async setSingle( + key: K, + value: T[K], + opts?: StorageOptions, + ): Promise { if (opts) { // noop } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this._asyncStorageNativeModule.multiSet([[key, value]], function( errors: Array, ) { @@ -75,24 +86,30 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } - async getMany( - keys: Array, + async getMany( + keys: Array, opts?: StorageOptions, - ): Promise> { + ): Promise<{[k in K]: T[k] | null}> { if (opts) { // noop } - return new Promise>((resolve, reject) => { + return new Promise((resolve, reject) => { this._asyncStorageNativeModule.multiGet(keys, function( errors: Array, - result: any, + result: Array<[K, T[K]]>, ) { - const value = - result && - result.reduce((acc: Array, current: Array) => { - return [...acc, current[1]]; - }, []); + const value: {[k in K]: T[k]} = result.reduce( + (acc, current: [K, T[K]]) => { + const key = current[0]; + const val = current[1] || null; + return { + ...acc, + [key]: val, + }; + }, + {}, + ); const errs = convertErrors(errors); if (errs && errs.length) { reject(errs[0]); @@ -103,17 +120,17 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } - async setMany( - values: Array<{[key: string]: T}>, + async setMany( + values: Array>, opts?: StorageOptions, ): Promise { if (opts) { // noop } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const valuesArray = values.map(entry => { - return [Object.keys(entry)[0], entry]; + return [Object.keys(entry)[0] as K, entry]; }); this._asyncStorageNativeModule.multiSet([valuesArray], function( errors: Array, @@ -128,7 +145,7 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } - async removeSingle(key: string, opts?: StorageOptions): Promise { + async removeSingle(key: keyof T, opts?: StorageOptions): Promise { if (opts) { // noop } @@ -147,7 +164,7 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } - async removeMany(keys: Array, opts?: StorageOptions): Promise { + async removeMany(keys: Array, opts?: StorageOptions): Promise { if (opts) { // noop } @@ -166,15 +183,15 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } - async getKeys(opts?: StorageOptions): Promise> { + async getKeys(opts?: StorageOptions): Promise> { if (opts) { // noop } - return new Promise>((resolve, reject) => { + return new Promise((resolve, reject) => { this._asyncStorageNativeModule.getAllKeys(function( errors: Array, - keys: Array, + keys: Array, ) { const err = convertErrors(errors); @@ -205,3 +222,37 @@ export default class LegacyAsyncStorage implements IStorageBackend { }); } } + +// type MyModel = { +// user: { +// name: string; +// }; +// preferences: { +// hour: boolean | null; +// hair: string; +// }; +// isEnabled: boolean; +// }; + +// async function xxx() { +// const a = new LegacyAsyncStorage(); +// +// const x = await a.getSingle('preferences'); +// +// x.hour; +// +// const all = await a.getMany(['user', 'isEnabled']); +// +// all.user; +// +// await a.setMany([ +// {user: {name: 'Jerry'}}, +// {isEnabled: false}, +// { +// preferences: { +// hour: true, +// hair: 'streight', +// }, +// }, +// ]); +// } diff --git a/packages/storage-legacy/types/index.d.ts b/packages/storage-legacy/types/index.d.ts index 336dc8cb..f0f0fffd 100644 --- a/packages/storage-legacy/types/index.d.ts +++ b/packages/storage-legacy/types/index.d.ts @@ -1,28 +1,46 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. + * Copyright (c) React Native Community. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ + import { + EmptyStorageModel, IStorageBackend, StorageOptions, } from '@react-native-community/async-storage'; -export default class LegacyAsyncStorage implements IStorageBackend { +export default class LegacyAsyncStorage + implements IStorageBackend { private readonly _asyncStorageNativeModule; - constructor(); - getSingle(key: string, opts?: StorageOptions): Promise; - setSingle(key: string, value: T, opts?: StorageOptions): Promise; - getMany(keys: Array, opts?: StorageOptions): Promise>; - setMany( - values: Array<{ - [key: string]: T; - }>, + + getSingle( + key: K, + opts?: StorageOptions, + ): Promise; + + setSingle( + key: K, + value: T[K], + opts?: StorageOptions, + ): Promise; + + getMany( + keys: Array, + opts?: StorageOptions, + ): Promise<{[k in K]: T[k] | null}>; + + setMany( + values: Array<{[k in K]: T[k]}>, opts?: StorageOptions, ): Promise; - removeSingle(key: string, opts?: StorageOptions): Promise; - removeMany(keys: Array, opts?: StorageOptions): Promise; - getKeys(opts?: StorageOptions): Promise>; + + removeSingle(key: keyof T, opts?: StorageOptions): Promise; + + removeMany(keys: Array, opts?: StorageOptions): Promise; + + getKeys(opts?: StorageOptions): Promise>; + dropStorage(opts?: StorageOptions): Promise; } diff --git a/packages/storage-legacy/types/nativeModule.d.ts b/packages/storage-legacy/types/nativeModule.d.ts new file mode 100644 index 00000000..2897b367 --- /dev/null +++ b/packages/storage-legacy/types/nativeModule.d.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) React Native Community. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +export interface ILegacyNativeModule { + multiGet( + keys: Array, + callback: (errors: Array, result: Array<[any, any]>) => void, + ): void; + + multiSet( + values: Array>, + callback: (errors: Array) => void, + ): void; + + multiRemove(keys: Array, callback: (errors: Array) => void): void; + + getAllKeys(callback: (errors: Array, keys: Array) => void): void; + + clear(callback: (errors: Array) => void): void; +} From 6b1fa9e4f410bafc6072bfcfd0dc07f39e914c04 Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Sun, 11 Aug 2019 15:23:42 +0200 Subject: [PATCH 2/7] fix: types in tests --- packages/core/__tests__/AsyncStorage.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/__tests__/AsyncStorage.test.ts b/packages/core/__tests__/AsyncStorage.test.ts index d55bc76f..642df7ae 100644 --- a/packages/core/__tests__/AsyncStorage.test.ts +++ b/packages/core/__tests__/AsyncStorage.test.ts @@ -20,13 +20,13 @@ describe('AsyncStorage', () => { }); type testCases = [ - Partial>, + Partial>, Partial, string ][]; describe('main API', () => { - const asyncStorage = new AsyncStorage(mockedStorage, { + const asyncStorage = new AsyncStorage(mockedStorage, { logger: false, errorHandler: false, }); @@ -63,7 +63,7 @@ describe('AsyncStorage', () => { it('uses logger when provided', async () => { const loggerFunc = jest.fn(); - const as = new AsyncStorage(mockedStorage, { + const as = new AsyncStorage(mockedStorage, { logger: loggerFunc, errorHandler: false, }); @@ -81,7 +81,7 @@ describe('AsyncStorage', () => { throw error; }); - const as = new AsyncStorage(mockedStorage, { + const as = new AsyncStorage(mockedStorage, { errorHandler, logger: false, }); From d05ccdd948423d27d2124ab3830fc323d832ab2d Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Sun, 11 Aug 2019 16:01:04 +0200 Subject: [PATCH 3/7] docs: core docs --- packages/core/README.md | 59 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 packages/core/README.md diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 00000000..466cc66d --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,59 @@ +# Async Storage Core + +Main Async Storage component. + +## Installation + +```bash +$ yarn install @react-native-community/async-storage +``` + +## Usage + +Use `AsyncStorageFactory` to create your Async Storage instance + + +```typescript + +// storage.js + +import ASFactory from '@react-native-community/async-storage' + +// use any available Storage Backend +const storageBackend = new StorageBackend(); + +const mobileStorage = ASFactory.create(storageBackend); + +export default mobileStorage; +``` + +## Providing a storage model + +You can provide a `Model` type when creating Async Storage, to have a fully typed storage. + + +```typescript +import ASFactory from '@react-native-community/async-storage' + +type StorageModel = { + user: { + name: string; + age: number; + }; + preferences: { + darkModeEnabled: boolean; + }; + subscribedChannels: Array; + onboardingCompleted: boolean; +}; + +// use any available Storage Backend +const storageBackend = new StorageBackend(); + +const storage = ASFactory.create(storageBackend); + +``` + +## License + +MIT. From 919189c16ae71de8ae1d53d9026e75657d5aebb9 Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Thu, 22 Aug 2019 23:05:04 +0200 Subject: [PATCH 4/7] docs: core --- README.md | 20 +++++++++++-- packages/core/README.md | 50 ++++++++++++++++++++++++------- packages/core/types/index.d.ts | 3 -- packages/storage-legacy/README.md | 3 ++ 4 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 packages/storage-legacy/README.md diff --git a/README.md b/README.md index a7d7874c..5ff96037 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Async Storage -A storage system for React Native. +A persistent data storage solution for your next mobile, web or desktop application. ## Work in progress @@ -10,8 +10,22 @@ If you're looking for published and operational Async Storage version, please ch If you'd like to see the progress of v2, [please visit Project page.](https://github.com/react-native-community/async-storage/projects/1) -## Running Examples +## Features + +todo + +## Documentation + +todo + +## Available storage backends + +- [Legacy](./packages/storage-legacy/README.md) + + +## License + +MIT -### React Native diff --git a/packages/core/README.md b/packages/core/README.md index 466cc66d..8b8c5484 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,20 +1,22 @@ # Async Storage Core -Main Async Storage component. +Public-facing API of Async Storage. -## Installation +## Install ```bash $ yarn install @react-native-community/async-storage ``` -## Usage +## API -Use `AsyncStorageFactory` to create your Async Storage instance +### `AsyncStorageFactory` + +A factory module for `AsyncStorage` instance with selected storage backend attached. -```typescript +```typescript // storage.js import ASFactory from '@react-native-community/async-storage' @@ -22,14 +24,40 @@ import ASFactory from '@react-native-community/async-storage' // use any available Storage Backend const storageBackend = new StorageBackend(); -const mobileStorage = ASFactory.create(storageBackend); +const mobileStorage = ASFactory.create(storageBackend, options); export default mobileStorage; ``` -## Providing a storage model -You can provide a `Model` type when creating Async Storage, to have a fully typed storage. +**Factory options** + +`AsyncStorageFactory.create` accepts an options object, that enables additional features. + + +- *logger* + +```typescript +type logger = ((action: LoggerAction) => void) | boolean; +``` + +Used to log `AsyncStorage` method calls and used arguments. +You can provide your own implementation or use provided default logger (enabled by default in DEV mode). + + +- *errorHandler* + +```typescript +type errorHandler = ((error: Error | string) => void) | boolean; +```` + +Used to report any errors thrown. +You can provide your own implementation or use provided default error handler (enabled by default in DEV mode). + + +**Providing a storage model** + +If you know the structure of the stored data upfront, you can use the full potential of the type system, by providing a type argument to `AsyncStorageFactory.create` method. ```typescript @@ -43,17 +71,19 @@ type StorageModel = { preferences: { darkModeEnabled: boolean; }; - subscribedChannels: Array; + subscribedChannels: Array; onboardingCompleted: boolean; }; // use any available Storage Backend const storageBackend = new StorageBackend(); + const storage = ASFactory.create(storageBackend); ``` ## License -MIT. +MIT + diff --git a/packages/core/types/index.d.ts b/packages/core/types/index.d.ts index 09700b3c..6f466bf0 100644 --- a/packages/core/types/index.d.ts +++ b/packages/core/types/index.d.ts @@ -110,9 +110,6 @@ export type LoggerAction = { }; // Helper types - -export type StorageModel = T extends IStorageBackend ? V : any; - export type EmptyStorageModel = {[key in symbol | number | string]: any}; export type StorageOptions = { diff --git a/packages/storage-legacy/README.md b/packages/storage-legacy/README.md new file mode 100644 index 00000000..93e854a2 --- /dev/null +++ b/packages/storage-legacy/README.md @@ -0,0 +1,3 @@ +# Storage Backend: Legacy + +todo From 9037a3a919ff2944fa52a662f28041f6adfe616f Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Sun, 25 Aug 2019 21:49:29 +0200 Subject: [PATCH 5/7] docs: legacy docs --- examples/mobile/README.md | 4 ++++ packages/core/README.md | 32 +++++++++++++++++++++++++++- packages/storage-legacy/README.md | 35 ++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/examples/mobile/README.md b/examples/mobile/README.md index 7a43b515..d1e3155b 100644 --- a/examples/mobile/README.md +++ b/examples/mobile/README.md @@ -28,3 +28,7 @@ $ yarn start Let me know about any issue found or feedback you have at [Async-Storage issue page](https://github.com/react-native-community/async-storage/issues). Please mark it as `examples` label. + +## License + +MIT diff --git a/packages/core/README.md b/packages/core/README.md index 8b8c5484..f23e990f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -1,6 +1,7 @@ # Async Storage Core -Public-facing API of Async Storage. +Main, public-facing components of Async Storage. The core module contains a factory to create an `AsyncStorage` instance +and `IStorageBackend`, that needs to be implemented by any Backend Storage in order to be compatible. ## Install @@ -82,6 +83,35 @@ const storageBackend = new StorageBackend(); const storage = ASFactory.create(storageBackend); ``` + + +### `IStorageBackend` + +In order to let `AsyncStorage` use a storage backend, it has to implement this interface. +Contains basic set method of methods to get, set and remove data, return already used keys or drop the whole storage. + + +```typescript + +import { + IStorageBackend, +} from '@react-native-community/async-storage'; + +type Model = { + count: number + user: { + name: string, + rating: number + } +} + +class MyStorageSolution implements IStorageBackend { + // implement necessary methods +} + +``` + + ## License diff --git a/packages/storage-legacy/README.md b/packages/storage-legacy/README.md index 93e854a2..29703a68 100644 --- a/packages/storage-legacy/README.md +++ b/packages/storage-legacy/README.md @@ -1,3 +1,36 @@ # Storage Backend: Legacy -todo +An `AsyncStorage` storage backend, fully compatible with former version. + +## Installation + + +// todo: check naming before relase +```bash +$ yarn add @react-native-community/async-storage-legacy +``` + + +## Usage + + +```typescript + +import LegacyStorage from '@react-native-community/async-storage-legacy'; +import AsyncStorageFactory from '@react-native-community/async-storage'; + +type MyModel = { + // ...your storage model +} + +const legacyStorage = new LegacyStorage(); + +const storage = AsyncStorageFactory.create(legacyStorage); + +// ready to use +export default storage; +``` + +## License + +MIT From eae485a9e0e6558fd1402c7247bec58f17d180eb Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Wed, 11 Sep 2019 08:15:39 +0200 Subject: [PATCH 6/7] fix: naming, ignoring publish files --- .npmignore | 85 --------------------------- examples/mobile/.babelrc | 2 +- examples/mobile/src/legacy/storage.js | 2 +- lerna.json | 2 +- packages/core/.npmignore | 30 ++++++++++ packages/core/package.json | 4 +- packages/storage-legacy/.npmignore | 30 ++++++++++ packages/storage-legacy/package.json | 12 ++-- 8 files changed, 73 insertions(+), 94 deletions(-) delete mode 100644 .npmignore create mode 100644 packages/core/.npmignore create mode 100644 packages/storage-legacy/.npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index 9b128286..00000000 --- a/.npmignore +++ /dev/null @@ -1,85 +0,0 @@ -# JS -node_modules -yarn.lock - -# Project files -CONTRIBUTING.md -CODE_OF_CONDUCT.md -README.md - -# Config files -.babelrc -babel.config.js -.editorconfig -.eslintrc -.flowconfig -.watchmanconfig -jsconfig.json -.npmrc -.gitattributes -.circleci -*.coverage.json -.opensource -.circleci -.eslintignore -codecov.yml - -# Example -example/ - -# Android -android/*/build/ -android/gradlew -android/build -android/gradlew.bat -android/gradle/ -android/com_crashlytics_export_strings.xml -android/local.properties -android/.gradle/ -android/.signing/ -android/.idea/gradle.xml -android/.idea/libraries/ -android/.idea/workspace.xml -android/.idea/tasks.xml -android/.idea/.name -android/.idea/compiler.xml -android/.idea/copyright/profiles_settings.xml -android/.idea/encodings.xml -android/.idea/misc.xml -android/.idea/modules.xml -android/.idea/scopes/scope_settings.xml -android/.idea/vcs.xml -android/*.iml -android/.settings - -# iOS -ios/*.xcodeproj/xcuserdata -*.pbxuser -*.mode1v3 -*.mode2v3 -*.perspectivev3 -*.xcuserstate -project.xcworkspace/ -xcuserdata/ - -# Misc -.DS_Store -.DS_Store? -*.DS_Store -coverage.android.json -coverage.ios.json -coverage -npm-debug.log -.github -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.dbandroid/gradle -docs -.idea -tests/ -bin/test.js -codorials -.vscode -.nyc_output \ No newline at end of file diff --git a/examples/mobile/.babelrc b/examples/mobile/.babelrc index 82ff5cf6..0dec7f52 100644 --- a/examples/mobile/.babelrc +++ b/examples/mobile/.babelrc @@ -5,7 +5,7 @@ { "alias": { "@react-native-community/async-storage": "../../packages/core/build", - "@react-native-community/async-storage-legacy": "../../packages/storage-legacy/build" + "@react-native-community/async-storage-backend-legacy": "../../packages/storage-legacy/build" }, "cwd": "babelrc" } diff --git a/examples/mobile/src/legacy/storage.js b/examples/mobile/src/legacy/storage.js index bb9b9007..e7a46d2f 100644 --- a/examples/mobile/src/legacy/storage.js +++ b/examples/mobile/src/legacy/storage.js @@ -1,4 +1,4 @@ -import LegacyStorage from '@react-native-community/async-storage-legacy'; +import LegacyStorage from '@react-native-community/async-storage-backend-legacy'; import AsyncStorageFactory from '@react-native-community/async-storage'; diff --git a/lerna.json b/lerna.json index 48642ca1..f784ae75 100644 --- a/lerna.json +++ b/lerna.json @@ -1,7 +1,7 @@ { "packages": [ "core", - "storages/*" + "storages-*" ], "version": "independent", "npmClient": "yarn", diff --git a/packages/core/.npmignore b/packages/core/.npmignore new file mode 100644 index 00000000..09e55784 --- /dev/null +++ b/packages/core/.npmignore @@ -0,0 +1,30 @@ +# Core +node_modules +yarn.lock +src/ +__tests__/ +CONTRIBUTING.md +CODE_OF_CONDUCT.md + + +########## Trash ########## +.DS_Store +.DS_Store? +*.DS_Store +coverage.android.json +coverage.ios.json +coverage +npm-debug.log +.github +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.dbandroid/gradle +.idea +bin/test.js +codorials +.vscode +.nyc_output +yarn-error.log + diff --git a/packages/core/package.json b/packages/core/package.json index 0c6c07e5..afc2c512 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,13 +2,13 @@ "name": "@react-native-community/async-storage", "version": "2.0.0", "main": "build/index.js", - "files": ["build/**/*"], "types": "types/index.d.ts", "author": "Krzysztof Borowy ", "license": "MIT", "scripts": { "build": "babel src --root-mode upward --out-dir build/ --extensions .ts --ignore build/**/* --ignore types/* --source-maps inline", "clean": "rm build -rf", - "start": "yarn build:lib --watch" + "start": "yarn build:lib --watch", + "prepublish": "yarn clean && yarn build" } } diff --git a/packages/storage-legacy/.npmignore b/packages/storage-legacy/.npmignore new file mode 100644 index 00000000..dbf057c9 --- /dev/null +++ b/packages/storage-legacy/.npmignore @@ -0,0 +1,30 @@ +# Legacy storage backend +node_modules +yarn.lock +# see https://github.com/yarnpkg/yarn/issues/7540 +./src/ +CONTRIBUTING.md +CODE_OF_CONDUCT.md +android/build/ +ios/*.xcodeproj/xcuserdata + +########## Trash ########## +.DS_Store +.DS_Store? +*.DS_Store +coverage.android.json +coverage.ios.json +coverage +npm-debug.log +.github +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.dbandroid/gradle +.idea +bin/test.js +codorials +.vscode +.nyc_output +yarn-error.log diff --git a/packages/storage-legacy/package.json b/packages/storage-legacy/package.json index 99cef15f..a130da8b 100644 --- a/packages/storage-legacy/package.json +++ b/packages/storage-legacy/package.json @@ -1,14 +1,18 @@ { - "name": "@react-native-community/async-storage-legacy", + "name": "@react-native-community/async-storage-backend-legacy", "version": "2.0.0", - "main": "build/LegacyAsyncStorage.js", + "main": "build/index.js", "types": "types/index.d.ts", "author": "Krzysztof Borowy ", "license": "MIT", "scripts": { - "build": "babel src --root-mode upward --out-dir build/ --extensions .ts --ignore build/**/* --ignore types/* --source-maps inline", + "build": "babel src --root-mode upward --out-dir build/ --extensions .ts --ignore build/**/* --ignore types/**/* --source-maps inline", "clean": "rm build -rf", - "generate:types": "tsc src/index.ts --noEmit false --lib es2015 --declarationDir types/ -d true -emitDeclarationOnly true" + "generate:types": "tsc src/index.ts --noEmit false --lib es2015 --declarationDir types/ -d true -emitDeclarationOnly true", + "prepublish": "yarn clean && yarn build" + }, + "dependencies": { + "@react-native-community/async-storage": "^2.0" }, "peerDependencies": { "react": "^16.0", From 5a110c3a112a254f877a47ebbac25d5b7a5a8468 Mon Sep 17 00:00:00 2001 From: Krzysztof Borowy Date: Thu, 12 Sep 2019 07:50:39 +0200 Subject: [PATCH 7/7] docs: writing storage backend --- README.md | 2 +- packages/core/docs/Writing_Storage_Backend.md | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 packages/core/docs/Writing_Storage_Backend.md diff --git a/README.md b/README.md index 5ff96037..a8591870 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ todo ## Documentation -todo +- [Creating a custom Storage backend](./packages/core/docs/Writing_Storage_Backend.md) ## Available storage backends diff --git a/packages/core/docs/Writing_Storage_Backend.md b/packages/core/docs/Writing_Storage_Backend.md new file mode 100644 index 00000000..e17f74f4 --- /dev/null +++ b/packages/core/docs/Writing_Storage_Backend.md @@ -0,0 +1,113 @@ +# Authoring Storage Backend + +To create custom storage, one must create a class that implements `IStorageBackend` interface. +This contract makes sure that Core knows how to use it. + +## Example + +Let's create a storage backend for web, using `LocalStorage` API. + +Start by adding `AsyncStorage` to your dependencies. + +```bash +$ yarn add @react-native-community/async-storage +``` + +Then, create a class and implement `IStorageBackend` interface; + + +```typescript + +import { IStorageBackend, EmptyStorageModel, StorageOptions } from '@react-native-community/async-storage'; + +class WebStorage implements IStorageBackend { + + storage = window.localStorage; + + async getSingle( + key: K, + opts?: StorageOptions, + ): Promise { + + return this.storage.getItem(key); + } + + async setSingle( + key: K, + value: T[K], + opts?: StorageOptions, + ): Promise { + return this.storage.setItem(key, value); + } + + async getMany( + keys: Array, + opts?: StorageOptions, + ): Promise<{[k in K]: T[k] | null}>{ + + return Promise.all(keys.map(k => this.storage.getItem(k))) + } + + async setMany( + values: Array<{[k in K]: T[k]}>, + opts?: StorageOptions, + ): Promise{ + + for(let keyValue of values){ + const key = Object.getOwnPropertyNames(keyValue)[0]; + if(!key) continue; + this.storage.setItem(key, keyValue[key]) + } + } + + async removeSingle(key: keyof T, opts?: StorageOptions): Promise { + return this.storage.removeItem(key); + } + + async removeMany(keys: Array, opts?: StorageOptions): Promise { + for (let key in keys) { + this.storage.removeItem(key); + } + } + + async getKeys(opts?: StorageOptions): Promise> { + return Object.keys(this.storage); + } + + async dropStorage(opts?: StorageOptions): Promise { + const keys = await this.getKeys(); + await this.removeMany(keys); + } +} + +export default WebStorage; +``` + +### Notes + +- Each function should be asynchronous - even if access to storage is not. +- In `localStorage`, remember that __keys__ and __values__ are always `string` - it's up to you if you're going to stringify it or accept stringified arguments. +- `opts` argument can be used to 'enhance' each call, for example, one could use it to decide if the stored value should be replaced: + +```typescript + +// in a class + +async setSingle( + key: K, + value: T[K], + opts?: StorageOptions, +): Promise { + + if(!opts.replaceCurrent) { + const current = this.storage.getItem(key); + if(!current){ + this.storage.setItem(key, value); + } + return; + } + + return this.storage.setItem(key, value); +} + +```