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/README.md b/README.md index a7d7874c..a8591870 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 + +- [Creating a custom Storage backend](./packages/core/docs/Writing_Storage_Backend.md) + +## Available storage backends + +- [Legacy](./packages/storage-legacy/README.md) + + +## License + +MIT -### React Native 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/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/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/README.md b/packages/core/README.md new file mode 100644 index 00000000..f23e990f --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,119 @@ +# Async Storage Core + +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 + +```bash +$ yarn install @react-native-community/async-storage +``` + +## API + + +### `AsyncStorageFactory` + +A factory module for `AsyncStorage` instance with selected storage backend attached. + + +```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, options); + +export default mobileStorage; +``` + + +**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 +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); + +``` + + +### `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 + +MIT + 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, }); 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); +} + +``` 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/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..6f466bf0 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>; +} - set(key: string, value: VAL, opts?: StorageOptions): Promise; +/** + * 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; - getMultiple( - keys: Array, + set( + key: K, + value: M[K], opts?: StorageOptions, - ): Promise>; + ): Promise; - setMultiple( - keyValues: Array<{[key: string]: VAL}>, + getMultiple( + keys: Array, + opts?: StorageOptions, + ): Promise<{[k in K]: M[k] | null}>; + + 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,7 +109,8 @@ export type LoggerAction = { key?: string | Array; }; -export type StorageModel = T extends IStorageBackend ? V : any; +// Helper types +export type EmptyStorageModel = {[key in symbol | number | string]: any}; export type StorageOptions = { [key: string]: any; 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/README.md b/packages/storage-legacy/README.md new file mode 100644 index 00000000..29703a68 --- /dev/null +++ b/packages/storage-legacy/README.md @@ -0,0 +1,36 @@ +# Storage Backend: Legacy + +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 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", 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; +}