Mathilde JEAN 2 years ago
commit 441ff3d59d

@ -0,0 +1,9 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
export const deleteFavoriteCity = async () => {
try {
await AsyncStorage.removeItem('favorite_city')
} catch(e) {
console.log("An error occurred", e);
}
}

@ -0,0 +1,12 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
import { City } from '../data/stub';
import {FETCH_FAVORITE_CITY} from '../redux/constants';
export const getFavoriteCityStorage = async () => {
try {
const cityJson = await AsyncStorage.getItem('favorite_city')
return cityJson != null ? JSON.parse(cityJson) : null;
} catch(e) {
console.log("An error occurred", e);
}
}

@ -0,0 +1,11 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { City } from "../data/stub";
export const storeFavoriteCity = async (city: City | null) => {
try {
const cityJson = JSON.stringify(city)
await AsyncStorage.setItem('favorite_city', cityJson);
} catch (e) {
console.log("An error occurred", e);
}
}

@ -1,15 +1,28 @@
import React, { useState, useSyncExternalStore } from "react";
import React, { useEffect, useState, useSyncExternalStore } from "react";
import { View, StyleSheet, Text, Button, TouchableHighlight, Image } from "react-native";
import { useDispatch } from "react-redux";
import { City, FAVORITE_CITY_DATA, getCurrentWeather, Weather } from "../data/stub";
import { addFavoriteCity } from "../redux/actions/addFavoriteCity";
type VilleProps = {
weather: Weather,
fav: City
fav: City | null
}
export function VilleCompopo(props: VilleProps){
const dispatch = useDispatch();
async function changeFavoriteCity(city: City | null, already: boolean) {
if (already){
city = null
}
dispatch(addFavoriteCity(city))
}
const isFavorite = props.fav != null && props.weather.city.longitude===props.fav.longitude && props.weather.city.latitude===props.fav.latitude
return (
<View style={styles.container}>
@ -19,8 +32,8 @@ export function VilleCompopo(props: VilleProps){
<Text>{props.weather.city.latitude} - {props.weather.city.longitude}</Text>
</View>
<Text style={styles.temperature}>{props.weather.temperature}</Text>
<TouchableHighlight /*onPress={() => FAVORITE_CITY_DATA = this.city}*/>
<Image source={props.weather.city.longitude===FAVORITE_CITY_DATA.longitude && props.weather.city.latitude===FAVORITE_CITY_DATA.latitude ? require('../assets/yellowstar.png') : require('../assets/blackstar.png')} style={styles.button}/>
<TouchableHighlight onPress={() => changeFavoriteCity(props.weather.city, isFavorite)}>
<Image source={ isFavorite ? require('../assets/yellowstar.png') : require('../assets/blackstar.png')} style={styles.button}/>
</TouchableHighlight>
</View>
</View>

@ -7,6 +7,7 @@
"linkedModules": [],
"topLevelPatterns": [
"@babel/core@^7.12.9",
"@react-native-async-storage/async-storage@^1.17.12",
"@react-navigation/bottom-tabs@^6.5.4",
"@react-navigation/native@^6.1.3",
"@react-navigation/stack@^6.3.12",
@ -327,6 +328,7 @@
"@nodelib/fs.walk@^1.2.3": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"@npmcli/fs@^1.0.0": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
"@npmcli/move-file@^1.0.1": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
"@react-native-async-storage/async-storage@^1.17.12": "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.17.12.tgz#a39e4df5b06795ce49b2ca5b7ca9b8faadf8e621",
"@react-native-community/cli-clean@^9.2.1": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-9.2.1.tgz",
"@react-native-community/cli-config@^9.2.1": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-9.2.1.tgz",
"@react-native-community/cli-debugger-ui@^9.0.0": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-9.0.0.tgz",
@ -1154,6 +1156,7 @@
"is-path-in-cwd@^2.0.0": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz",
"is-path-inside@^2.1.0": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-2.1.0.tgz",
"is-path-inside@^3.0.2": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"is-plain-obj@^2.1.0": "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287",
"is-plain-object@^2.0.3": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"is-plain-object@^2.0.4": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"is-port-reachable@^2.0.1": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-2.0.1.tgz",
@ -1296,6 +1299,7 @@
"memory-fs@^0.4.1": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz",
"memory-fs@^0.5.0": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
"merge-descriptors@1.0.1": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"merge-options@^3.0.4": "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7",
"merge-stream@^2.0.0": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"merge2@^1.3.0": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"merge2@^1.4.1": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2015-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,27 @@
# React Native Async Storage
An asynchronous, unencrypted, persistent, key-value storage system for React Native.
## Supported platforms
- iOS
- Android
- [Web](https://github.com/react-native-async-storage/async-storage/releases/tag/v1.9.0)
- [MacOS](https://github.com/react-native-async-storage/async-storage/releases/tag/v1.8.1)
- [Windows](https://github.com/react-native-async-storage/async-storage/releases/tag/v1.10.0)
## Getting Started
Head over to [documentation](https://react-native-async-storage.github.io/async-storage/docs/install) to learn more.
## Contribution
Pull requests are welcome. Please open an issue first to discuss what you would like to change.
See the [CONTRIBUTING](CONTRIBUTING.md) file for more information.
## License
MIT.

@ -0,0 +1,19 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
Pod::Spec.new do |s|
s.name = "RNCAsyncStorage"
s.version = package['version']
s.summary = package['description']
s.license = package['license']
s.authors = package['author']
s.homepage = package['homepage']
s.platforms = { :ios => "9.0", :tvos => "9.2", :osx => "10.14" }
s.source = { :git => "https://github.com/react-native-async-storage/async-storage.git", :tag => "v#{s.version}" }
s.source_files = "ios/**/*.{h,m}"
s.dependency 'React-Core'
end

@ -0,0 +1,146 @@
import java.nio.file.Paths
def resolveModulePath(packageName) {
def basePath = rootDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), 'node_modules', packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
def getFlagOrDefault(flagName, defaultValue) {
rootProject.hasProperty(flagName) ? rootProject.properties[flagName] == "true" : defaultValue
}
def getVersionOrDefault(String flagName, String defaultVersion) {
rootProject.hasProperty(flagName) ? rootProject.properties[flagName] : defaultVersion
}
configurations {
compileClasspath
}
buildscript {
// kotlin version is dictated by rootProject extension or property in gradle.properties
ext.asyncStorageKtVersion = rootProject.ext.has('kotlinVersion')
? rootProject.ext['kotlinVersion']
: rootProject.hasProperty('AsyncStorage_kotlinVersion')
? rootProject.properties['AsyncStorage_kotlinVersion']
: '1.6.10'
repositories {
mavenCentral()
google()
}
dependencies {
def projectExampleDir = Paths.get(project.projectDir.getParent(), "example", "android").toString()
def rootProjectDir = rootProject.projectDir.getPath()
if (projectExampleDir == rootProjectDir) {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$asyncStorageKtVersion"
}
}
}
// AsyncStorage has default size of 6MB.
// This is a sane limit to protect the user from the app storing too much data in the database.
// This also protects the database from filling up the disk cache and becoming malformed.
// If you really need bigger size, please keep in mind the potential consequences.
long dbSizeInMB = 6L
def newDbSize = rootProject.properties['AsyncStorage_db_size_in_MB']
if (newDbSize != null && newDbSize.isLong()) {
dbSizeInMB = newDbSize.toLong()
}
// Instead of reusing AsyncTask thread pool, AsyncStorage can use its own executor
def useDedicatedExecutor = getFlagOrDefault('AsyncStorage_dedicatedExecutor', false)
// Use next storage implementation
def useNextStorage = getFlagOrDefault("AsyncStorage_useNextStorage", false)
apply plugin: 'com.android.library'
if (useNextStorage) {
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply from: './testresults.gradle'
}
android {
compileSdkVersion safeExtGet('compileSdkVersion', 31)
defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 21)
targetSdkVersion safeExtGet('targetSdkVersion', 31)
buildConfigField "Long", "AsyncStorage_db_size", "${dbSizeInMB}L"
buildConfigField "boolean", "AsyncStorage_useDedicatedExecutor", "${useDedicatedExecutor}"
buildConfigField "boolean", "AsyncStorage_useNextStorage", "${useNextStorage}"
}
lintOptions {
abortOnError false
}
if (useNextStorage) {
testOptions {
unitTests {
returnDefaultValues = true
includeAndroidResources = true
}
}
}
}
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "${resolveModulePath("react-native")}/android"
}
google()
mavenCentral()
}
dependencies {
if (useNextStorage) {
def room_version = getVersionOrDefault('AsyncStorage_next_roomVersion', '2.4.2')
def coroutines_version = "1.6.0"
def coroutinesTest_version = "1.6.0"
// if we don't provide explicit dependency on reflection, kotlin plugin
// would add one automatically, probably a version that is not compatible with
// used kotlin
def kotlinReflect_version = project.ext.asyncStorageKtVersion
def junit_version = "4.13.2"
def robolectric_version = "4.7.3"
def truth_version = "1.1.3"
def androidxtest_version = "1.4.0"
def androidtest_junit_version = "1.1.3"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinReflect_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
kapt "androidx.room:room-compiler:$room_version"
testImplementation "junit:junit:$junit_version"
testImplementation "androidx.test:runner:$androidxtest_version"
testImplementation "androidx.test:rules:$androidxtest_version"
testImplementation "androidx.test.ext:junit:$androidtest_junit_version"
testImplementation "org.robolectric:robolectric:$robolectric_version"
testImplementation "com.google.truth:truth:$truth_version"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesTest_version"
}
//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
}

@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativecommunity.asyncstorage">
</manifest>

@ -0,0 +1,178 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Arrays;
import java.util.Iterator;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.facebook.react.bridge.ReadableArray;
import org.json.JSONException;
import org.json.JSONObject;
import static com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier.KEY_COLUMN;
import static com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier.TABLE_CATALYST;
import static com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier.VALUE_COLUMN;
/**
* Helper for database operations.
*/
public class AsyncLocalStorageUtil {
/**
* Build the String required for an SQL select statement:
* WHERE key IN (?, ?, ..., ?)
* without 'WHERE' and with selectionCount '?'
*/
/* package */ static String buildKeySelection(int selectionCount) {
String[] list = new String[selectionCount];
Arrays.fill(list, "?");
return KEY_COLUMN + " IN (" + TextUtils.join(", ", list) + ")";
}
/**
* Build the String[] arguments needed for an SQL selection, i.e.:
* {a, b, c}
* to be used in the SQL select statement: WHERE key in (?, ?, ?)
*/
/* package */ static String[] buildKeySelectionArgs(ReadableArray keys, int start, int count) {
String[] selectionArgs = new String[count];
for (int keyIndex = 0; keyIndex < count; keyIndex++) {
selectionArgs[keyIndex] = keys.getString(start + keyIndex);
}
return selectionArgs;
}
/**
* Returns the value of the given key, or null if not found.
*/
public static @Nullable String getItemImpl(SQLiteDatabase db, String key) {
String[] columns = {VALUE_COLUMN};
String[] selectionArgs = {key};
Cursor cursor = db.query(
TABLE_CATALYST,
columns,
KEY_COLUMN + "=?",
selectionArgs,
null,
null,
null);
try {
if (!cursor.moveToFirst()) {
return null;
} else {
return cursor.getString(0);
}
} finally {
cursor.close();
}
}
/**
* Sets the value for the key given, returns true if successful, false otherwise.
*/
/* package */ static boolean setItemImpl(SQLiteDatabase db, String key, String value) {
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_COLUMN, key);
contentValues.put(VALUE_COLUMN, value);
long inserted = db.insertWithOnConflict(
TABLE_CATALYST,
null,
contentValues,
SQLiteDatabase.CONFLICT_REPLACE);
return (-1 != inserted);
}
/**
* Does the actual merge of the (key, value) pair with the value stored in the database.
* NB: This assumes that a database lock is already in effect!
* @return the errorCode of the operation
*/
/* package */ static boolean mergeImpl(SQLiteDatabase db, String key, String value)
throws JSONException {
String oldValue = getItemImpl(db, key);
String newValue;
if (oldValue == null) {
newValue = value;
} else {
JSONObject oldJSON = new JSONObject(oldValue);
JSONObject newJSON = new JSONObject(value);
deepMergeInto(oldJSON, newJSON);
newValue = oldJSON.toString();
}
return setItemImpl(db, key, newValue);
}
/**
* Merges two {@link JSONObject}s. The newJSON object will be merged with the oldJSON object by
* either overriding its values, or merging them (if the values of the same key in both objects
* are of type {@link JSONObject}). oldJSON will contain the result of this merge.
*/
private static void deepMergeInto(JSONObject oldJSON, JSONObject newJSON)
throws JSONException {
Iterator<?> keys = newJSON.keys();
while (keys.hasNext()) {
String key = (String) keys.next();
JSONObject newJSONObject = newJSON.optJSONObject(key);
JSONObject oldJSONObject = oldJSON.optJSONObject(key);
if (newJSONObject != null && oldJSONObject != null) {
deepMergeInto(oldJSONObject, newJSONObject);
oldJSON.put(key, oldJSONObject);
} else {
oldJSON.put(key, newJSON.get(key));
}
}
}
/**
* From Pie and up, Android started to use Write-ahead logging (WAL), instead of journal rollback
* for atomic commits and rollbacks.
* Basically, WAL does not write directly to the database file, rather to the supporting WAL file.
* Because of that, migration to the next storage might not be successful, because the content of
* RKStorage might be still in WAL file instead. Committing all data from WAL to db file is called
* a "checkpoint" and is done automatically (by default) when the WAL file reaches a threshold
* size of 1000 pages.
* More here: https://sqlite.org/wal.html
*
* This helper will force checkpoint on RKStorage, if Next storage file does not exists yet.
*/
public static void verifyAndForceSqliteCheckpoint(Context ctx) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
Log.i("AsyncStorage_Next", "SQLite checkpoint not required on this API version.");
}
File nextStorageFile = ctx.getDatabasePath("AsyncStorage");
File currentStorageFile = ctx.getDatabasePath(ReactDatabaseSupplier.DATABASE_NAME);
boolean isCheckpointRequired = !nextStorageFile.exists() && currentStorageFile.exists();
if (!isCheckpointRequired) {
Log.i("AsyncStorage_Next", "SQLite checkpoint not required.");
return;
}
try {
ReactDatabaseSupplier supplier = ReactDatabaseSupplier.getInstance(ctx);
supplier.get().rawQuery("PRAGMA wal_checkpoint", null).close();
supplier.closeDatabase();
Log.i("AsyncStorage_Next", "Forcing SQLite checkpoint successful.");
} catch (Exception e) {
Log.w("AsyncStorage_Next", "Could not force checkpoint on RKStorage, the Next storage might not migrate the data properly: " + e.getMessage());
}
}
}

@ -0,0 +1,45 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import javax.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
/**
* Helper class for database errors.
*/
public class AsyncStorageErrorUtil {
/**
* Create Error object to be passed back to the JS callback.
*/
/* package */ static WritableMap getError(@Nullable String key, String errorMessage) {
WritableMap errorMap = Arguments.createMap();
errorMap.putString("message", errorMessage);
if (key != null) {
errorMap.putString("key", key);
}
return errorMap;
}
/* package */ static WritableMap getInvalidKeyError(@Nullable String key) {
return getError(key, "Invalid key");
}
/* package */ static WritableMap getInvalidValueError(@Nullable String key) {
return getError(key, "Invalid Value");
}
/* package */ static WritableMap getDBError(@Nullable String key) {
return getError(key, "Database Error");
}
}

@ -0,0 +1,154 @@
package com.reactnativecommunity.asyncstorage;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
// A utility class that migrates a scoped AsyncStorage database to RKStorage.
// This utility only runs if the RKStorage file has not been created yet.
public class AsyncStorageExpoMigration {
static final String LOG_TAG = "AsyncStorageExpoMigration";
public static void migrate(Context context) {
// Only migrate if the default async storage file does not exist.
if (isAsyncStorageDatabaseCreated(context)) {
return;
}
ArrayList<File> expoDatabases = getExpoDatabases(context);
File expoDatabase = getLastModifiedFile(expoDatabases);
if (expoDatabase == null) {
Log.v(LOG_TAG, "No scoped database found");
return;
}
try {
// Create the storage file
ReactDatabaseSupplier.getInstance(context).get();
copyFile(new FileInputStream(expoDatabase), new FileOutputStream(context.getDatabasePath(ReactDatabaseSupplier.DATABASE_NAME)));
Log.v(LOG_TAG, "Migrated most recently modified database " + expoDatabase.getName() + " to RKStorage");
} catch (Exception e) {
Log.v(LOG_TAG, "Failed to migrate scoped database " + expoDatabase.getName());
e.printStackTrace();
return;
}
try {
for (File file : expoDatabases) {
if (file.delete()) {
Log.v(LOG_TAG, "Deleted scoped database " + file.getName());
} else {
Log.v(LOG_TAG, "Failed to delete scoped database " + file.getName());
}
}
} catch (Exception e) {
e.printStackTrace();
}
Log.v(LOG_TAG, "Completed the scoped AsyncStorage migration");
}
private static boolean isAsyncStorageDatabaseCreated(Context context) {
return context.getDatabasePath(ReactDatabaseSupplier.DATABASE_NAME).exists();
}
// Find all database files that the user may have created while using Expo.
private static ArrayList<File> getExpoDatabases(Context context) {
ArrayList<File> scopedDatabases = new ArrayList<>();
try {
File databaseDirectory = context.getDatabasePath("noop").getParentFile();
File[] directoryListing = databaseDirectory.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
// Find all databases matching the Expo scoped key, and skip any database journals.
if (child.getName().startsWith("RKStorage-scoped-experience-") && !child.getName().endsWith("-journal")) {
scopedDatabases.add(child);
}
}
}
} catch (Exception e) {
// Just in case anything happens catch and print, file system rules can tend to be different across vendors.
e.printStackTrace();
}
return scopedDatabases;
}
// Returns the most recently modified file.
// If a user publishes an app with Expo, then changes the slug
// and publishes again, a new database will be created.
// We want to select the most recent database and migrate it to RKStorage.
private static File getLastModifiedFile(ArrayList<File> files) {
if (files.size() == 0) {
return null;
}
long lastMod = -1;
File lastModFile = null;
for (File child : files) {
long modTime = getLastModifiedTimeInMillis(child);
if (modTime > lastMod) {
lastMod = modTime;
lastModFile = child;
}
}
if (lastModFile != null) {
return lastModFile;
}
return files.get(0);
}
private static long getLastModifiedTimeInMillis(File file) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return getLastModifiedTimeFromBasicFileAttrs(file);
} else {
return file.lastModified();
}
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
@RequiresApi(Build.VERSION_CODES.O)
private static long getLastModifiedTimeFromBasicFileAttrs(File file) {
try {
return Files.readAttributes(file.toPath(), BasicFileAttributes.class).creationTime().toMillis();
} catch (Exception e) {
return -1;
}
}
private static void copyFile(FileInputStream fromFile, FileOutputStream toFile) throws IOException {
FileChannel fromChannel = null;
FileChannel toChannel = null;
try {
fromChannel = fromFile.getChannel();
toChannel = toFile.getChannel();
fromChannel.transferTo(0, fromChannel.size(), toChannel);
} finally {
try {
if (fromChannel != null) {
fromChannel.close();
}
} finally {
if (toChannel != null) {
toChannel.close();
}
}
}
}
}

@ -0,0 +1,424 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import android.os.AsyncTask;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.common.ModuleDataCleaner;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ReactModule(name = AsyncStorageModule.NAME)
public final class AsyncStorageModule
extends ReactContextBaseJavaModule implements ModuleDataCleaner.Cleanable, LifecycleEventListener {
// changed name to not conflict with AsyncStorage from RN repo
public static final String NAME = "RNC_AsyncSQLiteDBStorage";
// SQL variable number limit, defined by SQLITE_LIMIT_VARIABLE_NUMBER:
// https://raw.githubusercontent.com/android/platform_external_sqlite/master/dist/sqlite3.c
private static final int MAX_SQL_KEYS = 999;
private ReactDatabaseSupplier mReactDatabaseSupplier;
private boolean mShuttingDown = false;
private final SerialExecutor executor;
public AsyncStorageModule(ReactApplicationContext reactContext) {
this(
reactContext,
BuildConfig.AsyncStorage_useDedicatedExecutor
? Executors.newSingleThreadExecutor()
: AsyncTask.THREAD_POOL_EXECUTOR
);
}
@VisibleForTesting
AsyncStorageModule(ReactApplicationContext reactContext, Executor executor) {
super(reactContext);
// The migration MUST run before the AsyncStorage database is created for the first time.
AsyncStorageExpoMigration.migrate(reactContext);
this.executor = new SerialExecutor(executor);
reactContext.addLifecycleEventListener(this);
// Creating the database MUST happen after the migration.
mReactDatabaseSupplier = ReactDatabaseSupplier.getInstance(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public void initialize() {
super.initialize();
mShuttingDown = false;
}
@Override
public void onCatalystInstanceDestroy() {
mShuttingDown = true;
}
@Override
public void clearSensitiveData() {
// Clear local storage. If fails, crash, since the app is potentially in a bad state and could
// cause a privacy violation. We're still not recovering from this well, but at least the error
// will be reported to the server.
mReactDatabaseSupplier.clearAndCloseDatabase();
}
@Override
public void onHostResume() {}
@Override
public void onHostPause() {}
@Override
public void onHostDestroy() {
// ensure we close database when activity is destroyed
mReactDatabaseSupplier.closeDatabase();
}
/**
* Given an array of keys, this returns a map of (key, value) pairs for the keys found, and
* (key, null) for the keys that haven't been found.
*/
@ReactMethod
public void multiGet(final ReadableArray keys, final Callback callback) {
if (keys == null) {
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null), null);
return;
}
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null), null);
return;
}
String[] columns = {ReactDatabaseSupplier.KEY_COLUMN, ReactDatabaseSupplier.VALUE_COLUMN};
HashSet<String> keysRemaining = new HashSet<>();
WritableArray data = Arguments.createArray();
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
Cursor cursor = mReactDatabaseSupplier.get().query(
ReactDatabaseSupplier.TABLE_CATALYST,
columns,
AsyncLocalStorageUtil.buildKeySelection(keyCount),
AsyncLocalStorageUtil.buildKeySelectionArgs(keys, keyStart, keyCount),
null,
null,
null);
keysRemaining.clear();
try {
if (cursor.getCount() != keys.size()) {
// some keys have not been found - insert them with null into the final array
for (int keyIndex = keyStart; keyIndex < keyStart + keyCount; keyIndex++) {
keysRemaining.add(keys.getString(keyIndex));
}
}
if (cursor.moveToFirst()) {
do {
WritableArray row = Arguments.createArray();
row.pushString(cursor.getString(0));
row.pushString(cursor.getString(1));
data.pushArray(row);
keysRemaining.remove(cursor.getString(0));
} while (cursor.moveToNext());
}
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
return;
} finally {
cursor.close();
}
for (String key : keysRemaining) {
WritableArray row = Arguments.createArray();
row.pushString(key);
row.pushNull();
data.pushArray(row);
}
keysRemaining.clear();
}
callback.invoke(null, data);
}
}.executeOnExecutor(executor);
}
/**
* Inserts multiple (key, value) pairs. If one or more of the pairs cannot be inserted, this will
* return AsyncLocalStorageFailure, but all other pairs will have been inserted.
* The insertion will replace conflicting (key, value) pairs.
*/
@ReactMethod
public void multiSet(final ReadableArray keyValueArray, final Callback callback) {
if (keyValueArray.size() == 0) {
callback.invoke();
return;
}
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
String sql = "INSERT OR REPLACE INTO " + ReactDatabaseSupplier.TABLE_CATALYST + " VALUES (?, ?);";
SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql);
WritableMap error = null;
try {
mReactDatabaseSupplier.get().beginTransaction();
for (int idx=0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
if (keyValueArray.getArray(idx).getString(0) == null) {
error = AsyncStorageErrorUtil.getInvalidKeyError(null);
return;
}
if (keyValueArray.getArray(idx).getString(1) == null) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
statement.clearBindings();
statement.bindString(1, keyValueArray.getArray(idx).getString(0));
statement.bindString(2, keyValueArray.getArray(idx).getString(1));
statement.execute();
}
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally {
try {
mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
}
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke();
}
}
}.executeOnExecutor(executor);
}
/**
* Removes all rows of the keys given.
*/
@ReactMethod
public void multiRemove(final ReadableArray keys, final Callback callback) {
if (keys.size() == 0) {
callback.invoke();
return;
}
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
WritableMap error = null;
try {
mReactDatabaseSupplier.get().beginTransaction();
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
mReactDatabaseSupplier.get().delete(
ReactDatabaseSupplier.TABLE_CATALYST,
AsyncLocalStorageUtil.buildKeySelection(keyCount),
AsyncLocalStorageUtil.buildKeySelectionArgs(keys, keyStart, keyCount));
}
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally {
try {
mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
}
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke();
}
}
}.executeOnExecutor(executor);
}
/**
* Given an array of (key, value) pairs, this will merge the given values with the stored values
* of the given keys, if they exist.
*/
@ReactMethod
public void multiMerge(final ReadableArray keyValueArray, final Callback callback) {
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
WritableMap error = null;
try {
mReactDatabaseSupplier.get().beginTransaction();
for (int idx = 0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
if (keyValueArray.getArray(idx).getString(0) == null) {
error = AsyncStorageErrorUtil.getInvalidKeyError(null);
return;
}
if (keyValueArray.getArray(idx).getString(1) == null) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
if (!AsyncLocalStorageUtil.mergeImpl(
mReactDatabaseSupplier.get(),
keyValueArray.getArray(idx).getString(0),
keyValueArray.getArray(idx).getString(1))) {
error = AsyncStorageErrorUtil.getDBError(null);
return;
}
}
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally {
try {
mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
}
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke();
}
}
}.executeOnExecutor(executor);
}
/**
* Clears the database.
*/
@ReactMethod
public void clear(final Callback callback) {
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!mReactDatabaseSupplier.ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
try {
mReactDatabaseSupplier.clear();
callback.invoke();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
}
}
}.executeOnExecutor(executor);
}
/**
* Returns an array with all keys from the database.
*/
@ReactMethod
public void getAllKeys(final Callback callback) {
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null), null);
return;
}
WritableArray data = Arguments.createArray();
String[] columns = {ReactDatabaseSupplier.KEY_COLUMN};
Cursor cursor = mReactDatabaseSupplier.get()
.query(ReactDatabaseSupplier.TABLE_CATALYST, columns, null, null, null, null, null);
try {
if (cursor.moveToFirst()) {
do {
data.pushString(cursor.getString(0));
} while (cursor.moveToNext());
}
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
return;
} finally {
cursor.close();
}
callback.invoke(null, data);
}
}.executeOnExecutor(executor);
}
/**
* Verify the database is open for reads and writes.
*/
private boolean ensureDatabase() {
return !mShuttingDown && mReactDatabaseSupplier.ensureDatabase();
}
}

@ -0,0 +1,58 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import android.util.Log;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class AsyncStoragePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> moduleList = new ArrayList<>(1);
if (BuildConfig.AsyncStorage_useNextStorage) {
try {
Class storageClass = Class.forName("com.reactnativecommunity.asyncstorage.next.StorageModule");
NativeModule inst = (NativeModule) storageClass.getDeclaredConstructor(new Class[]{ReactContext.class}).newInstance(reactContext);
moduleList.add(inst);
AsyncLocalStorageUtil.verifyAndForceSqliteCheckpoint(reactContext);
} catch (Exception e) {
String message = "Something went wrong when initializing module:"
+ "\n"
+ e.getCause().getClass()
+ "\n"
+ "Cause:" + e.getCause().getLocalizedMessage();
Log.e("AsyncStorage_Next", message);
}
} else {
moduleList.add(new AsyncStorageModule(reactContext));
}
return moduleList;
}
// Deprecated in RN 0.47
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
@SuppressWarnings("rawtypes")
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

@ -0,0 +1,163 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import com.facebook.common.logging.FLog;
import com.facebook.react.common.ReactConstants;
import javax.annotation.Nullable;
/**
* Database supplier of the database used by react native. This creates, opens and deletes the
* database as necessary.
*/
public class ReactDatabaseSupplier extends SQLiteOpenHelper {
// VisibleForTesting
public static final String DATABASE_NAME = "RKStorage";
private static final int DATABASE_VERSION = 1;
private static final int SLEEP_TIME_MS = 30;
static final String TABLE_CATALYST = "catalystLocalStorage";
static final String KEY_COLUMN = "key";
static final String VALUE_COLUMN = "value";
static final String VERSION_TABLE_CREATE =
"CREATE TABLE " + TABLE_CATALYST + " (" +
KEY_COLUMN + " TEXT PRIMARY KEY, " +
VALUE_COLUMN + " TEXT NOT NULL" +
")";
private static @Nullable ReactDatabaseSupplier sReactDatabaseSupplierInstance;
private Context mContext;
private @Nullable SQLiteDatabase mDb;
private long mMaximumDatabaseSize = BuildConfig.AsyncStorage_db_size * 1024L * 1024L;
private ReactDatabaseSupplier(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
public static ReactDatabaseSupplier getInstance(Context context) {
if (sReactDatabaseSupplierInstance == null) {
sReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context.getApplicationContext());
}
return sReactDatabaseSupplierInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(VERSION_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
deleteDatabase();
onCreate(db);
}
}
/**
* Verify the database exists and is open.
*/
/* package */ synchronized boolean ensureDatabase() {
if (mDb != null && mDb.isOpen()) {
return true;
}
// Sometimes retrieving the database fails. We do 2 retries: first without database deletion
// and then with deletion.
SQLiteException lastSQLiteException = null;
for (int tries = 0; tries < 2; tries++) {
try {
if (tries > 0) {
deleteDatabase();
}
mDb = getWritableDatabase();
break;
} catch (SQLiteException e) {
lastSQLiteException = e;
}
// Wait before retrying.
try {
Thread.sleep(SLEEP_TIME_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (mDb == null) {
throw lastSQLiteException;
}
// This is a sane limit to protect the user from the app storing too much data in the database.
// This also protects the database from filling up the disk cache and becoming malformed
// (endTransaction() calls will throw an exception, not rollback, and leave the db malformed).
mDb.setMaximumSize(mMaximumDatabaseSize);
return true;
}
/**
* Create and/or open the database.
*/
public synchronized SQLiteDatabase get() {
ensureDatabase();
return mDb;
}
public synchronized void clearAndCloseDatabase() throws RuntimeException {
try {
clear();
closeDatabase();
FLog.d(ReactConstants.TAG, "Cleaned " + DATABASE_NAME);
} catch (Exception e) {
// Clearing the database has failed, delete it instead.
if (deleteDatabase()) {
FLog.d(ReactConstants.TAG, "Deleted Local Database " + DATABASE_NAME);
return;
}
// Everything failed, throw
throw new RuntimeException("Clearing and deleting database " + DATABASE_NAME + " failed");
}
}
/* package */ synchronized void clear() {
get().delete(TABLE_CATALYST, null, null);
}
/**
* Sets the maximum size the database will grow to. The maximum size cannot
* be set below the current size.
*/
public synchronized void setMaximumSize(long size) {
mMaximumDatabaseSize = size;
if (mDb != null) {
mDb.setMaximumSize(mMaximumDatabaseSize);
}
}
private synchronized boolean deleteDatabase() {
closeDatabase();
return mContext.deleteDatabase(DATABASE_NAME);
}
public synchronized void closeDatabase() {
if (mDb != null && mDb.isOpen()) {
mDb.close();
mDb = null;
}
}
// For testing purposes only!
public static void deleteInstance() {
sReactDatabaseSupplierInstance = null;
}
}

@ -0,0 +1,40 @@
package com.reactnativecommunity.asyncstorage;
import java.util.ArrayDeque;
import java.util.concurrent.Executor;
/**
* Detox is using this implementation detail in its environment setup,
* so in order for Next storage to work, this class has been made public
*
* Adapted from https://android.googlesource.com/platform/frameworks/base.git/+/1488a3a19d4681a41fb45570c15e14d99db1cb66/core/java/android/os/AsyncTask.java#237
*/
public class SerialExecutor implements Executor {
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
private Runnable mActive;
private final Executor executor;
public SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
executor.execute(mActive);
}
}
}

@ -0,0 +1,86 @@
package com.reactnativecommunity.asyncstorage.next
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import org.json.JSONException
import org.json.JSONObject
fun ReadableArray.toEntryList(): List<Entry> {
val list = mutableListOf<Entry>()
for (keyValue in this.toArrayList()) {
if (keyValue !is ArrayList<*> || keyValue.size != 2) {
throw AsyncStorageError.invalidKeyValueFormat()
}
val key = keyValue[0]
val value = keyValue[1]
if (key !is String) {
when (key) {
null -> throw AsyncStorageError.keyIsNull()
else -> throw AsyncStorageError.keyNotString()
}
}
if (value !is String) {
throw AsyncStorageError.valueNotString(key)
}
list.add(Entry(key, value))
}
return list
}
fun ReadableArray.toKeyList(): List<String> {
val list = this.toArrayList()
for (item in list) {
if (item !is String) {
throw AsyncStorageError.keyNotString()
}
}
return list as List<String>
}
fun List<Entry>.toKeyValueArgument(): ReadableArray {
val args = Arguments.createArray()
for (entry in this) {
val keyValue = Arguments.createArray()
keyValue.pushString(entry.key)
keyValue.pushString(entry.value)
args.pushArray(keyValue)
}
return args
}
fun String?.isValidJson(): Boolean {
if (this == null) return false
return try {
JSONObject(this)
true
} catch (e: JSONException) {
false
}
}
fun JSONObject.mergeWith(newObject: JSONObject): JSONObject {
val keys = newObject.keys()
val mergedObject = JSONObject(this.toString())
while (keys.hasNext()) {
val key = keys.next()
val curValue = this.optJSONObject(key)
val newValue = newObject.optJSONObject(key)
if (curValue != null && newValue != null) {
val merged = curValue.mergeWith(newValue)
mergedObject.put(key, merged)
} else {
mergedObject.put(key, newObject.get(key))
}
}
return mergedObject
}

@ -0,0 +1,39 @@
package com.reactnativecommunity.asyncstorage.next
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import kotlinx.coroutines.CoroutineExceptionHandler
internal fun createExceptionHandler(cb: Callback): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, throwable ->
val error = Arguments.createMap()
if (throwable !is AsyncStorageError) {
error.putString(
"message", "Unexpected AsyncStorage error: ${throwable.localizedMessage}"
)
} else {
error.putString("message", throwable.errorMessage)
}
cb(error)
}
}
internal class AsyncStorageError private constructor(val errorMessage: String) :
Throwable(errorMessage) {
companion object {
fun keyIsNull() = AsyncStorageError("Key cannot be null.")
fun keyNotString() = AsyncStorageError("Provided key is not string. Only strings are supported as storage key.")
fun valueNotString(key: String?): AsyncStorageError {
val detail = if (key == null) "Provided value" else "Value for key \"$key\""
return AsyncStorageError("$detail is not a string. Only strings are supported as a value.")
}
fun invalidKeyValueFormat() =
AsyncStorageError("Invalid key-value format. Expected a list of [key, value] list.")
}
}

@ -0,0 +1,90 @@
package com.reactnativecommunity.asyncstorage.next
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.reactnativecommunity.asyncstorage.SerialExecutor
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.launch
class StorageModule(reactContext: ReactContext) : ReactContextBaseJavaModule(), CoroutineScope {
override fun getName() = "RNC_AsyncSQLiteDBStorage"
// this executor is not used by the module, but it must exists here due to
// Detox relying on this implementation detail to run
@VisibleForTesting
private val executor = SerialExecutor(Dispatchers.Main.asExecutor())
override val coroutineContext =
Dispatchers.IO + CoroutineName("AsyncStorageScope") + SupervisorJob()
private val storage = StorageSupplier.getInstance(reactContext)
companion object {
@JvmStatic
fun getStorageInstance(ctx: Context): AsyncStorageAccess {
return StorageSupplier.getInstance(ctx)
}
}
@ReactMethod
fun multiGet(keys: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
val entries = storage.getValues(keys.toKeyList())
cb(null, entries.toKeyValueArgument())
}
}
@ReactMethod
fun multiSet(keyValueArray: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
val entries = keyValueArray.toEntryList()
storage.setValues(entries)
cb(null)
}
}
@ReactMethod
fun multiRemove(keys: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
storage.removeValues(keys.toKeyList())
cb(null)
}
}
@ReactMethod
fun multiMerge(keyValueArray: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
val entries = keyValueArray.toEntryList()
storage.mergeValues(entries)
cb(null)
}
}
@ReactMethod
fun getAllKeys(cb: Callback) {
launch(createExceptionHandler(cb)) {
val keys = storage.getKeys()
val result = Arguments.createArray()
keys.forEach { result.pushString(it) }
cb.invoke(null, result)
}
}
@ReactMethod
fun clear(cb: Callback) {
launch(createExceptionHandler(cb)) {
storage.clear()
cb(null)
}
}
}

@ -0,0 +1,161 @@
package com.reactnativecommunity.asyncstorage.next
import android.content.Context
import android.util.Log
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.Transaction
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import org.json.JSONObject
private const val DATABASE_VERSION = 2
private const val DATABASE_NAME = "AsyncStorage"
private const val TABLE_NAME = "Storage"
private const val COLUMN_KEY = "key"
private const val COLUMN_VALUE = "value"
@Entity(tableName = TABLE_NAME)
data class Entry(
@PrimaryKey @ColumnInfo(name = COLUMN_KEY) val key: String,
@ColumnInfo(name = COLUMN_VALUE) val value: String?
)
@Dao
internal interface StorageDao {
@Transaction
@Query("SELECT * FROM $TABLE_NAME WHERE `$COLUMN_KEY` IN (:keys)")
suspend fun getValues(keys: List<String>): List<Entry>
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setValues(entries: List<Entry>)
@Transaction
@Query("DELETE FROM $TABLE_NAME WHERE `$COLUMN_KEY` in (:keys)")
suspend fun removeValues(keys: List<String>)
@Transaction
suspend fun mergeValues(entries: List<Entry>) {
val currentDbEntries = getValues(entries.map { it.key })
val newEntries = mutableListOf<Entry>()
entries.forEach { newEntry ->
val oldEntry = currentDbEntries.find { it.key == newEntry.key }
if (oldEntry?.value == null) {
newEntries.add(newEntry)
} else if (!oldEntry.value.isValidJson() || !newEntry.value.isValidJson()) {
newEntries.add(newEntry)
} else {
val newValue =
JSONObject(oldEntry.value).mergeWith(JSONObject(newEntry.value)).toString()
newEntries.add(newEntry.copy(value = newValue))
}
}
setValues(newEntries)
}
@Transaction
@Query("SELECT `$COLUMN_KEY` FROM $TABLE_NAME")
suspend fun getKeys(): List<String>
@Transaction
@Query("DELETE FROM $TABLE_NAME")
suspend fun clear()
}
/**
* Previous version of AsyncStorage is violating the SQL standard (based on bug in SQLite),
* where PrimaryKey ('key' column) should never be null (https://www.sqlite.org/lang_createtable.html#the_primary_key).
* Because of that, we cannot reuse the old DB, because ROOM is guarded against that case (won't compile).
*
* In order to work around this, two steps are necessary:
* - Room DB pre-population from the old database file (https://developer.android.com/training/data-storage/room/prepopulate#from-asset)
* - Version migration, so that we can mark 'key' column as NOT-NULL
*
* This migration will happens only once, when developer enable this feature (when DB is still not created).
*/
@Suppress("ClassName")
private object MIGRATION_TO_NEXT : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val oldTableName = "catalystLocalStorage" // from ReactDatabaseSupplier
database.execSQL("CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`${COLUMN_KEY}` TEXT NOT NULL, `${COLUMN_VALUE}` TEXT, PRIMARY KEY(`${COLUMN_KEY}`));")
// even if the old AsyncStorage has checks for not nullable keys
// make sure we don't copy any, to not fail migration
database.execSQL("DELETE FROM $oldTableName WHERE `${COLUMN_KEY}` IS NULL")
database.execSQL(
"""
INSERT INTO $TABLE_NAME (`${COLUMN_KEY}`, `${COLUMN_VALUE}`)
SELECT `${COLUMN_KEY}`, `${COLUMN_VALUE}`
FROM $oldTableName;
""".trimIndent()
)
Log.e("AsyncStorage_Next", "Migration to Next storage completed.")
}
}
@Database(entities = [Entry::class], version = DATABASE_VERSION, exportSchema = true)
internal abstract class StorageDb : RoomDatabase() {
abstract fun storage(): StorageDao
companion object {
private var instance: StorageDb? = null
fun getDatabase(context: Context): StorageDb {
var inst = instance
if (inst != null) {
return inst
}
synchronized(this) {
val oldDbFile = context.getDatabasePath("RKStorage")
val db = Room.databaseBuilder(
context, StorageDb::class.java, DATABASE_NAME
)
if (oldDbFile.exists()) {
// migrate data from old database, if it exists
db.createFromFile(oldDbFile).addMigrations(MIGRATION_TO_NEXT)
}
inst = db.build()
instance = inst
return instance!!
}
}
}
}
interface AsyncStorageAccess {
suspend fun getValues(keys: List<String>): List<Entry>
suspend fun setValues(entries: List<Entry>)
suspend fun removeValues(keys: List<String>)
suspend fun getKeys(): List<String>
suspend fun clear()
suspend fun mergeValues(entries: List<Entry>)
}
class StorageSupplier internal constructor(db: StorageDb) : AsyncStorageAccess {
companion object {
fun getInstance(ctx: Context): AsyncStorageAccess {
return StorageSupplier(StorageDb.getDatabase(ctx))
}
}
private val access = db.storage()
override suspend fun getValues(keys: List<String>) = access.getValues(keys)
override suspend fun setValues(entries: List<Entry>) = access.setValues(entries)
override suspend fun removeValues(keys: List<String>) = access.removeValues(keys)
override suspend fun mergeValues(entries: List<Entry>) = access.mergeValues(entries)
override suspend fun getKeys() = access.getKeys()
override suspend fun clear() = access.clear()
}

@ -0,0 +1,93 @@
package com.reactnativecommunity.asyncstorage.next
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.ReadableArray
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
class ArgumentHelpersTest {
@Test
fun transformsArgumentsToEntryList() {
val args = JavaOnlyArray.of(
arrayListOf("key1", "value1"),
arrayListOf("key2", "value2"),
arrayListOf("key3", "value3")
)
assertThat(args.toEntryList()).isEqualTo(
listOf(
Entry("key1", "value1"),
Entry("key2", "value2"),
Entry("key3", "value3"),
)
)
}
@Test
fun transfersArgumentsToKeyList() {
val keyList = listOf("key1", "key2", "key3")
val args = keyList.toReadableArray()
assertThat(args.toKeyList()).isEqualTo(keyList)
}
@Test
fun throwsIfArgumentsNotValidFormat() {
val invalid = arrayListOf("invalid")
val args = JavaOnlyArray.of(invalid)
val error = assertThrows(AsyncStorageError::class.java) {
args.toEntryList()
}
assertThat(error is AsyncStorageError).isTrue()
assertThat(error).hasMessageThat()
.isEqualTo("Invalid key-value format. Expected a list of [key, value] list.")
}
@Test
fun throwsIfArgumentKeyIsNullOrNotString() {
val argsInvalidNull = JavaOnlyArray.of(arrayListOf(null, "invalid"))
val errorArgsInvalidNull = assertThrows(AsyncStorageError::class.java) {
argsInvalidNull.toEntryList()
}
assertThat(errorArgsInvalidNull is AsyncStorageError).isTrue()
assertThat(errorArgsInvalidNull).hasMessageThat().isEqualTo("Key cannot be null.")
val notStringArgs = JavaOnlyArray.of(arrayListOf(123, "invalid"))
val errorNotString = assertThrows(AsyncStorageError::class.java) {
notStringArgs.toEntryList()
}
assertThat(errorNotString is AsyncStorageError).isTrue()
assertThat(errorNotString).hasMessageThat()
.isEqualTo("Provided key is not string. Only strings are supported as storage key.")
}
@Test
fun throwsIfArgumentValueNotString() {
val invalidArgs = JavaOnlyArray.of(arrayListOf("my_key", 666))
val error = assertThrows(AsyncStorageError::class.java) {
invalidArgs.toEntryList()
}
assertThat(error is AsyncStorageError).isTrue()
assertThat(error).hasMessageThat()
.isEqualTo("Value for key \"my_key\" is not a string. Only strings are supported as a value.")
}
}
fun List<Any?>.toReadableArray(): ReadableArray {
val arr = JavaOnlyArray()
forEach {
when (it) {
null -> arr.pushNull()
is Boolean -> arr.pushBoolean(it)
is Double -> arr.pushDouble(it)
is Int -> arr.pushInt(it)
is String -> arr.pushString(it)
else -> throw NotImplementedError()
}
}
return arr
}

@ -0,0 +1,141 @@
package com.reactnativecommunity.asyncstorage.next
import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.random.Random
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class AsyncStorageAccessTest {
private lateinit var asyncStorage: AsyncStorageAccess
private lateinit var database: StorageDb
@Before
fun setup() {
database = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context, StorageDb::class.java
).allowMainThreadQueries().build()
asyncStorage = StorageSupplier(database)
}
@After
fun tearDown() {
database.close()
}
@Test
fun performsBasicGetSetRemoveOperations() = runBlocking {
val entriesCount = 10
val entries = createRandomEntries(entriesCount)
val keys = entries.map { it.key }
assertThat(asyncStorage.getValues(keys)).hasSize(0)
asyncStorage.setValues(entries)
assertThat(asyncStorage.getValues(keys)).hasSize(entriesCount)
val indicesToRemove = (1..4).map { Random.nextInt(0, entriesCount) }.distinct()
val toRemove = entries.filterIndexed { index, _ -> indicesToRemove.contains(index) }
asyncStorage.removeValues(toRemove.map { it.key })
val currentEntries = asyncStorage.getValues(keys)
assertThat(currentEntries).hasSize(entriesCount - toRemove.size)
}
@Test
fun readsAllKeysAndClearsDb() = runBlocking {
val entries = createRandomEntries(8)
val keys = entries.map { it.key }
asyncStorage.setValues(entries)
val dbKeys = asyncStorage.getKeys()
assertThat(dbKeys).isEqualTo(keys)
asyncStorage.clear()
assertThat(asyncStorage.getValues(keys)).hasSize(0)
}
@Test
fun mergesDeeplyTwoValues() = runBlocking {
val initialEntry = Entry("key", VALUE_INITIAL)
val overrideEntry = Entry("key", VALUE_OVERRIDES)
asyncStorage.setValues(listOf(initialEntry))
asyncStorage.mergeValues(listOf(overrideEntry))
val current = asyncStorage.getValues(listOf("key"))[0]
assertThat(current.value).isEqualTo(VALUE_MERGED)
}
@Test
fun updatesExistingValues() = runBlocking {
val key = "test_key"
val value = "test_value"
val entries = listOf(Entry(key, value))
assertThat(asyncStorage.getValues(listOf(key))).hasSize(0)
asyncStorage.setValues(entries)
assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(entries)
val modifiedEntries = listOf(Entry(key, "updatedValue"))
asyncStorage.setValues(modifiedEntries)
assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(modifiedEntries)
}
// Test Helpers
private fun createRandomEntries(count: Int = Random.nextInt(10)): List<Entry> {
val entries = mutableListOf<Entry>()
for (i in 0 until count) {
entries.add(Entry("key$i", "value$i"))
}
return entries
}
private val VALUE_INITIAL = JSONObject(
"""
{
"key":"value",
"key2":"override",
"key3":{
"key4":"value4",
"key6":{
"key7":"value7",
"key8":"override"
}
}
}
""".trimMargin()
).toString()
private val VALUE_OVERRIDES = JSONObject(
"""
{
"key2":"value2",
"key3":{
"key5":"value5",
"key6":{
"key8":"value8"
}
}
}
"""
).toString()
private val VALUE_MERGED = JSONObject(
"""
{
"key":"value",
"key2":"value2",
"key3":{
"key4":"value4",
"key6":{
"key7":"value7",
"key8":"value8"
},
"key5":"value5"
}
}
""".trimMargin()
).toString()
}

@ -0,0 +1,38 @@
// pretty print test results
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
tasks.withType(Test) {
testLogging {
events TestLogEvent.FAILED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.STANDARD_OUT
exceptionFormat TestExceptionFormat.FULL
showExceptions true
showCauses true
showStackTraces true
debug {
events TestLogEvent.STARTED,
TestLogEvent.FAILED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.STANDARD_ERROR,
TestLogEvent.STANDARD_OUT
exceptionFormat TestExceptionFormat.FULL
}
info.events = debug.events
info.exceptionFormat = debug.exceptionFormat
afterSuite { desc, result ->
if (!desc.parent) { // will match the outermost suite
def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
def startItem = '| ', endItem = ' |'
def repeatLength = startItem.length() + output.length() + endItem.length()
println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
}
}
}
}

@ -0,0 +1,51 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInvalidating.h>
#import "RNCAsyncStorageDelegate.h"
/**
* A simple, asynchronous, persistent, key-value storage system designed as a
* backend to the AsyncStorage JS module, which is modeled after LocalStorage.
*
* Current implementation stores small values in serialized dictionary and
* larger values in separate files. Since we use a serial file queue
* `RKFileQueue`, reading/writing from multiple threads should be perceived as
* being atomic, unless someone bypasses the `RNCAsyncStorage` API.
*
* Keys and values must always be strings or an error is returned.
*/
@interface RNCAsyncStorage : NSObject <RCTBridgeModule, RCTInvalidating>
@property (nonatomic, weak, nullable) id<RNCAsyncStorageDelegate> delegate;
@property (nonatomic, assign) BOOL clearOnInvalidate;
@property (nonatomic, readonly, getter=isValid) BOOL valid;
// Clear the RNCAsyncStorage data from native code
- (void)clearAllData;
// For clearing data when the bridge may not exist, e.g. when logging out.
+ (void)clearAllData;
// Grab data from the cache. ResponseBlock result array will have an error at position 0, and an
// array of arrays at position 1.
- (void)multiGet:(NSArray<NSString *> *)keys callback:(RCTResponseSenderBlock)callback;
// Add multiple key value pairs to the cache.
- (void)multiSet:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback;
// Interface for natively fetching all the keys from the storage data.
- (void)getAllKeys:(RCTResponseSenderBlock)callback;
@end

@ -0,0 +1,898 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RNCAsyncStorage.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
static NSString *const RCTOldStorageDirectory = @"RNCAsyncLocalStorage_V1";
static NSString *const RCTExpoStorageDirectory = @"RCTAsyncLocalStorage";
static NSString *const RCTManifestFileName = @"manifest.json";
static const NSUInteger RCTInlineValueThreshold = 1024;
#pragma mark - Static helper functions
static NSDictionary *RCTErrorForKey(NSString *key)
{
if (![key isKindOfClass:[NSString class]]) {
return RCTMakeAndLogError(@"Invalid key - must be a string. Key: ", key, @{@"key": key});
} else if (key.length < 1) {
return RCTMakeAndLogError(
@"Invalid key - must be at least one character. Key: ", key, @{@"key": key});
} else {
return nil;
}
}
static BOOL RCTAsyncStorageSetExcludedFromBackup(NSString *path, NSNumber *isExcluded)
{
NSFileManager *fileManager = [[NSFileManager alloc] init];
BOOL isDir;
BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDir];
BOOL success = false;
if (isDir && exists) {
NSURL *pathUrl = [NSURL fileURLWithPath:path];
NSError *error = nil;
success = [pathUrl setResourceValue:isExcluded
forKey:NSURLIsExcludedFromBackupKey
error:&error];
if (!success) {
NSLog(@"Could not exclude AsyncStorage dir from backup %@", error);
}
}
return success;
}
static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> **errors)
{
if (error && errors) {
if (!*errors) {
*errors = [NSMutableArray new];
}
[*errors addObject:error];
}
}
static NSArray<NSDictionary *> *RCTMakeErrors(NSArray<id<NSObject>> *results)
{
NSMutableArray<NSDictionary *> *errors;
for (id object in results) {
if ([object isKindOfClass:[NSError class]]) {
NSError *error = (NSError *)object;
NSDictionary *keyError = RCTMakeError(error.localizedDescription, error, nil);
RCTAppendError(keyError, &errors);
}
}
return errors;
}
static NSString *RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut)
{
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSError *error;
NSStringEncoding encoding;
NSString *entryString = [NSString stringWithContentsOfFile:filePath
usedEncoding:&encoding
error:&error];
NSDictionary *extraData = @{@"key": RCTNullIfNil(key)};
if (error) {
if (errorOut) {
*errorOut = RCTMakeError(@"Failed to read storage file.", error, extraData);
}
return nil;
}
if (encoding != NSUTF8StringEncoding) {
if (errorOut) {
*errorOut =
RCTMakeError(@"Incorrect encoding of storage file: ", @(encoding), extraData);
}
return nil;
}
return entryString;
}
return nil;
}
// DO NOT USE
// This is used internally to migrate data from the old file location to the new one.
// Please use `RCTCreateStorageDirectoryPath` instead
static NSString *RCTCreateStorageDirectoryPath_deprecated(NSString *storageDir)
{
NSString *storageDirectoryPath;
#if TARGET_OS_TV
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
#else
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
#endif
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir];
return storageDirectoryPath;
}
static NSString *RCTCreateStorageDirectoryPath(NSString *storageDir)
{
NSString *storageDirectoryPath = @"";
#if TARGET_OS_TV
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
#else
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)
.firstObject;
// We should use the "Application Support/[bundleID]" folder for persistent data storage that's
// hidden from users
storageDirectoryPath = [storageDirectoryPath
stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]];
#endif
// Per Apple's docs, all app content in Application Support must be within a subdirectory of the
// app's bundle identifier
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir];
return storageDirectoryPath;
}
static NSString *RCTGetStorageDirectory()
{
static NSString *storageDirectory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
storageDirectory = RCTCreateStorageDirectoryPath(RCTStorageDirectory);
});
return storageDirectory;
}
static NSString *RCTCreateManifestFilePath(NSString *storageDirectory)
{
return [storageDirectory stringByAppendingPathComponent:RCTManifestFileName];
}
static NSString *RCTGetManifestFilePath()
{
static NSString *manifestFilePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manifestFilePath = RCTCreateManifestFilePath(RCTStorageDirectory);
});
return manifestFilePath;
}
// Only merges objects - all other types are just clobbered (including arrays)
static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
{
BOOL modified = NO;
for (NSString *key in source) {
id sourceValue = source[key];
id destinationValue = destination[key];
if ([sourceValue isKindOfClass:[NSDictionary class]]) {
if ([destinationValue isKindOfClass:[NSDictionary class]]) {
if ([destinationValue classForCoder] != [NSMutableDictionary class]) {
destinationValue = [destinationValue mutableCopy];
}
if (RCTMergeRecursive(destinationValue, sourceValue)) {
destination[key] = destinationValue;
modified = YES;
}
} else {
destination[key] = [sourceValue copy];
modified = YES;
}
} else if (![source isEqual:destinationValue]) {
destination[key] = [sourceValue copy];
modified = YES;
}
}
return modified;
}
static dispatch_queue_t RCTGetMethodQueue()
{
// We want all instances to share the same queue since they will be reading/writing the same
// files.
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue =
dispatch_queue_create("com.facebook.react.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
static NSCache *RCTGetCache()
{
// We want all instances to share the same cache since they will be reading/writing the same
// files.
static NSCache *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [NSCache new];
cache.totalCostLimit = 2 * 1024 * 1024; // 2MB
#if !TARGET_OS_OSX
// Clear cache in the event of a memory warning
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
object:nil
queue:nil
usingBlock:^(__unused NSNotification *note) {
[cache removeAllObjects];
}];
#endif // !TARGET_OS_OSX
});
return cache;
}
static BOOL RCTHasCreatedStorageDirectory = NO;
static NSDictionary *RCTDeleteStorageDirectory()
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error];
RCTHasCreatedStorageDirectory = NO;
return error ? RCTMakeError(@"Failed to delete storage directory.", error, nil) : nil;
}
static NSDate *RCTManifestModificationDate(NSString *manifestFilePath)
{
NSDictionary *attributes =
[[NSFileManager defaultManager] attributesOfItemAtPath:manifestFilePath error:nil];
return [attributes fileModificationDate];
}
/**
* Creates an NSException used during Storage Directory Migration.
*/
static void RCTStorageDirectoryMigrationLogError(NSString *reason, NSError *error)
{
RCTLogWarn(@"%@: %@", reason, error ? error.description : @"");
}
static void RCTStorageDirectoryCleanupOld(NSString *oldDirectoryPath)
{
NSError *error;
if (![[NSFileManager defaultManager] removeItemAtPath:oldDirectoryPath error:&error]) {
RCTStorageDirectoryMigrationLogError(
@"Failed to remove old storage directory during migration", error);
}
}
static void _createStorageDirectory(NSString *storageDirectory, NSError **error)
{
[[NSFileManager defaultManager] createDirectoryAtPath:storageDirectory
withIntermediateDirectories:YES
attributes:nil
error:error];
}
static void RCTStorageDirectoryMigrate(NSString *oldDirectoryPath,
NSString *newDirectoryPath,
BOOL shouldCleanupOldDirectory)
{
NSError *error;
// Migrate data by copying old storage directory to new storage directory location
if (![[NSFileManager defaultManager] copyItemAtPath:oldDirectoryPath
toPath:newDirectoryPath
error:&error]) {
// the new storage directory "Application Support/[bundleID]/RCTAsyncLocalStorage_V1" seems
// unable to migrate because folder "Application Support/[bundleID]" doesn't exist.. create
// this folder and attempt folder copying again
if (error != nil && error.code == 4 &&
[newDirectoryPath isEqualToString:RCTGetStorageDirectory()]) {
NSError *error = nil;
_createStorageDirectory(RCTCreateStorageDirectoryPath(@""), &error);
if (error == nil) {
RCTStorageDirectoryMigrate(
oldDirectoryPath, newDirectoryPath, shouldCleanupOldDirectory);
} else {
RCTStorageDirectoryMigrationLogError(
@"Failed to create storage directory during migration.", error);
}
} else {
RCTStorageDirectoryMigrationLogError(
@"Failed to copy old storage directory to new storage directory location during "
@"migration",
error);
}
} else if (shouldCleanupOldDirectory) {
// If copying succeeds, remove old storage directory
RCTStorageDirectoryCleanupOld(oldDirectoryPath);
}
}
/**
* Determine which of RCTOldStorageDirectory or RCTExpoStorageDirectory needs to migrated.
* If both exist, we remove the least recently modified and return the most recently modified.
* Otherwise, this will return the path to whichever directory exists.
* If no directory exists, then return nil.
*/
static NSString *RCTGetStoragePathForMigration()
{
BOOL isDir;
NSString *oldStoragePath = RCTCreateStorageDirectoryPath_deprecated(RCTOldStorageDirectory);
NSString *expoStoragePath = RCTCreateStorageDirectoryPath_deprecated(RCTExpoStorageDirectory);
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL oldStorageDirectoryExists =
[fileManager fileExistsAtPath:oldStoragePath isDirectory:&isDir] && isDir;
BOOL expoStorageDirectoryExists =
[fileManager fileExistsAtPath:expoStoragePath isDirectory:&isDir] && isDir;
// Check if both the old storage directory and Expo storage directory exist
if (oldStorageDirectoryExists && expoStorageDirectoryExists) {
// If the old storage has been modified more recently than Expo storage, then clear Expo
// storage. Otherwise, clear the old storage.
if ([RCTManifestModificationDate(RCTCreateManifestFilePath(oldStoragePath))
compare:RCTManifestModificationDate(RCTCreateManifestFilePath(expoStoragePath))] ==
NSOrderedDescending) {
RCTStorageDirectoryCleanupOld(expoStoragePath);
return oldStoragePath;
} else {
RCTStorageDirectoryCleanupOld(oldStoragePath);
return expoStoragePath;
}
} else if (oldStorageDirectoryExists) {
return oldStoragePath;
} else if (expoStorageDirectoryExists) {
return expoStoragePath;
} else {
return nil;
}
}
/**
* This check is added to make sure that anyone coming from pre-1.2.2 does not lose cached data.
* Check that data is migrated from the old location to the new location
* fromStorageDirectory: the directory where the older data lives
* toStorageDirectory: the directory where the new data should live
* shouldCleanupOldDirectoryAndOverwriteNewDirectory: YES if we should delete the old directory's
* contents and overwrite the new directory's contents during the migration to the new directory
*/
static void
RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory,
NSString *toStorageDirectory,
BOOL shouldCleanupOldDirectoryAndOverwriteNewDirectory)
{
NSError *error;
BOOL isDir;
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the old directory exists, it means we may need to migrate old data to the new directory
if ([fileManager fileExistsAtPath:fromStorageDirectory isDirectory:&isDir] && isDir) {
// Check if the new storage directory location already exists
if ([fileManager fileExistsAtPath:toStorageDirectory]) {
// If new storage location exists, check if the new storage has been modified sooner in
// which case we may want to cleanup the old location
if ([RCTManifestModificationDate(RCTCreateManifestFilePath(toStorageDirectory))
compare:RCTManifestModificationDate(
RCTCreateManifestFilePath(fromStorageDirectory))] == 1) {
// If new location has been modified more recently, simply clean out old data
if (shouldCleanupOldDirectoryAndOverwriteNewDirectory) {
RCTStorageDirectoryCleanupOld(fromStorageDirectory);
}
} else if (shouldCleanupOldDirectoryAndOverwriteNewDirectory) {
// If old location has been modified more recently, remove new storage and migrate
if (![fileManager removeItemAtPath:toStorageDirectory error:&error]) {
RCTStorageDirectoryMigrationLogError(
@"Failed to remove new storage directory during migration", error);
} else {
RCTStorageDirectoryMigrate(fromStorageDirectory,
toStorageDirectory,
shouldCleanupOldDirectoryAndOverwriteNewDirectory);
}
}
} else {
// If new storage location doesn't exist, migrate data
RCTStorageDirectoryMigrate(fromStorageDirectory,
toStorageDirectory,
shouldCleanupOldDirectoryAndOverwriteNewDirectory);
}
}
}
#pragma mark - RNCAsyncStorage
@implementation RNCAsyncStorage {
BOOL _haveSetup;
// The manifest is a dictionary of all keys with small values inlined. Null values indicate
// values that are stored in separate files (as opposed to nil values which don't exist). The
// manifest is read off disk at startup, and written to disk after all mutations.
NSMutableDictionary<NSString *, NSString *> *_manifest;
}
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
// Get the path to any old storage directory that needs to be migrated. If multiple exist,
// the oldest are removed and the most recently modified is returned.
NSString *oldStoragePath = RCTGetStoragePathForMigration();
if (oldStoragePath != nil) {
// Migrate our deprecated path "Documents/.../RNCAsyncLocalStorage_V1" or
// "Documents/.../RCTAsyncLocalStorage" to "Documents/.../RCTAsyncLocalStorage_V1"
RCTStorageDirectoryMigrationCheck(
oldStoragePath, RCTCreateStorageDirectoryPath_deprecated(RCTStorageDirectory), YES);
}
// Migrate what's in "Documents/.../RCTAsyncLocalStorage_V1" to
// "Application Support/[bundleID]/RCTAsyncLocalStorage_V1"
RCTStorageDirectoryMigrationCheck(RCTCreateStorageDirectoryPath_deprecated(RCTStorageDirectory),
RCTCreateStorageDirectoryPath(RCTStorageDirectory),
NO);
return self;
}
RCT_EXPORT_MODULE()
- (dispatch_queue_t)methodQueue
{
return RCTGetMethodQueue();
}
- (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
[self->_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
}
+ (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
}
- (void)invalidate
{
if (_clearOnInvalidate) {
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
}
_clearOnInvalidate = NO;
[_manifest removeAllObjects];
_haveSetup = NO;
}
- (BOOL)isValid
{
return _haveSetup;
}
- (void)dealloc
{
[self invalidate];
}
- (NSString *)_filePathForKey:(NSString *)key
{
NSString *safeFileName = RCTMD5Hash(key);
return [RCTGetStorageDirectory() stringByAppendingPathComponent:safeFileName];
}
- (NSDictionary *)_ensureSetup
{
RCTAssertThread(RCTGetMethodQueue(), @"Must be executed on storage thread");
#if TARGET_OS_TV
RCTLogWarn(
@"Persistent storage is not supported on tvOS, your data may be removed at any point.");
#endif
NSError *error = nil;
if (!RCTHasCreatedStorageDirectory) {
_createStorageDirectory(RCTGetStorageDirectory(), &error);
if (error) {
return RCTMakeError(@"Failed to create storage directory.", error, nil);
}
RCTHasCreatedStorageDirectory = YES;
}
if (!_haveSetup) {
// iCloud backup exclusion
NSNumber *isExcludedFromBackup =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTAsyncStorageExcludeFromBackup"];
if (isExcludedFromBackup == nil) {
// by default, we want to exclude AsyncStorage data from backup
isExcludedFromBackup = @YES;
}
RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory),
isExcludedFromBackup);
NSDictionary *errorOut = nil;
NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()),
RCTManifestFileName,
&errorOut);
if (!serialized) {
if (errorOut) {
// We cannot simply create a new manifest in case the file does exist but we have no
// access to it. This can happen when data protection is enabled for the app and we
// are trying to read the manifest while the device is locked. (The app can be
// started by the system even if the device is locked due to e.g. a geofence event.)
RCTLogError(
@"Could not open the existing manifest, perhaps data protection is "
@"enabled?\n\n%@",
errorOut);
return errorOut;
} else {
// We can get nil without errors only when the file does not exist.
RCTLogTrace(@"Manifest does not exist - creating a new one.\n\n%@", errorOut);
_manifest = [NSMutableDictionary new];
}
} else {
_manifest = RCTJSONParseMutable(serialized, &error);
if (!_manifest) {
RCTLogError(@"Failed to parse manifest - creating a new one.\n\n%@", error);
_manifest = [NSMutableDictionary new];
}
}
_haveSetup = YES;
}
return nil;
}
- (NSDictionary *)_writeManifest:(NSMutableArray<NSDictionary *> **)errors
{
NSError *error;
NSString *serialized = RCTJSONStringify(_manifest, &error);
[serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath())
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
NSDictionary *errorOut;
if (error) {
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);
RCTAppendError(errorOut, errors);
}
return errorOut;
}
- (NSDictionary *)_appendItemForKey:(NSString *)key
toArray:(NSMutableArray<NSArray<NSString *> *> *)result
{
NSDictionary *errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
NSString *value = [self _getValueForKey:key errorOut:&errorOut];
[result addObject:@[key, RCTNullIfNil(value)]]; // Insert null if missing or failure.
return errorOut;
}
- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
{
NSString *value =
_manifest[key]; // nil means missing, null means there may be a data file, else: NSString
if (value == (id)kCFNull) {
value = [RCTGetCache() objectForKey:key];
if (!value) {
NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, errorOut);
if (value) {
[RCTGetCache() setObject:value forKey:key cost:value.length];
} else {
// file does not exist after all, so remove from manifest (no need to save
// manifest immediately though, as cost of checking again next time is negligible)
[_manifest removeObjectForKey:key];
}
}
}
return value;
}
- (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL *)changedManifest
{
if (entry.count != 2) {
return RCTMakeAndLogError(
@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
}
NSString *key = entry[0];
NSDictionary *errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
NSString *value = entry[1];
NSString *filePath = [self _filePathForKey:key];
NSError *error;
if (value.length <= RCTInlineValueThreshold) {
if (_manifest[key] == (id)kCFNull) {
// If the value already existed but wasn't inlined, remove the old file.
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
}
*changedManifest = YES;
_manifest[key] = value;
return nil;
}
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
[RCTGetCache() setObject:value forKey:key cost:value.length];
if (error) {
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
} else if (_manifest[key] != (id)kCFNull) {
*changedManifest = YES;
_manifest[key] = (id)kCFNull;
}
return errorOut;
}
- (void)_multiGet:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback
getter:(NSString * (^)(NSUInteger i, NSString *key, NSDictionary **errorOut))getValue
{
NSMutableArray<NSDictionary *> *errors;
NSMutableArray<NSArray<NSString *> *> *result = [NSMutableArray arrayWithCapacity:keys.count];
for (NSUInteger i = 0; i < keys.count; ++i) {
NSString *key = keys[i];
id keyError;
id value = getValue(i, key, &keyError);
[result addObject:@[key, RCTNullIfNil(value)]];
RCTAppendError(keyError, &errors);
}
callback(@[RCTNullIfNil(errors), result]);
}
- (BOOL)_passthroughDelegate
{
return
[self.delegate respondsToSelector:@selector(isPassthrough)] && self.delegate.isPassthrough;
}
#pragma mark - Exported JS Functions
// clang-format off
RCT_EXPORT_METHOD(multiGet:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate
valuesForKeys:keys
completion:^(NSArray<id<NSObject>> *valuesOrErrors) {
[self _multiGet:keys
callback:callback
getter:^NSString *(NSUInteger i, NSString *key, NSDictionary **errorOut) {
id valueOrError = valuesOrErrors[i];
if ([valueOrError isKindOfClass:[NSError class]]) {
NSError *error = (NSError *)valueOrError;
NSDictionary *extraData = @{@"key": RCTNullIfNil(key)};
*errorOut =
RCTMakeError(error.localizedDescription, error, extraData);
return nil;
} else {
return [valueOrError isKindOfClass:[NSString class]]
? (NSString *)valueOrError
: nil;
}
}];
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut], (id)kCFNull]);
return;
}
[self _multiGet:keys
callback:callback
getter:^(NSUInteger i, NSString *key, NSDictionary **errorOut) {
return [self _getValueForKey:key errorOut:errorOut];
}];
}
// clang-format off
RCT_EXPORT_METHOD(multiSet:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
NSMutableArray<NSString *> *keys = [NSMutableArray arrayWithCapacity:kvPairs.count];
NSMutableArray<NSString *> *values = [NSMutableArray arrayWithCapacity:kvPairs.count];
for (NSArray<NSString *> *entry in kvPairs) {
[keys addObject:entry[0]];
[values addObject:entry[1]];
}
[self.delegate setValues:values
forKeys:keys
completion:^(NSArray<id<NSObject>> *results) {
NSArray<NSDictionary *> *errors = RCTMakeErrors(results);
callback(@[RCTNullIfNil(errors)]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
BOOL changedManifest = NO;
NSMutableArray<NSDictionary *> *errors;
for (NSArray<NSString *> *entry in kvPairs) {
NSDictionary *keyError = [self _writeEntry:entry changedManifest:&changedManifest];
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}
// clang-format off
RCT_EXPORT_METHOD(multiMerge:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
NSMutableArray<NSString *> *keys = [NSMutableArray arrayWithCapacity:kvPairs.count];
NSMutableArray<NSString *> *values = [NSMutableArray arrayWithCapacity:kvPairs.count];
for (NSArray<NSString *> *entry in kvPairs) {
[keys addObject:entry[0]];
[values addObject:entry[1]];
}
[self.delegate mergeValues:values
forKeys:keys
completion:^(NSArray<id<NSObject>> *results) {
NSArray<NSDictionary *> *errors = RCTMakeErrors(results);
callback(@[RCTNullIfNil(errors)]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
BOOL changedManifest = NO;
NSMutableArray<NSDictionary *> *errors;
for (__strong NSArray<NSString *> *entry in kvPairs) {
NSDictionary *keyError;
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
if (!keyError) {
if (value) {
NSError *jsonError;
NSMutableDictionary *mergedVal = RCTJSONParseMutable(value, &jsonError);
if (RCTMergeRecursive(mergedVal, RCTJSONParse(entry[1], &jsonError))) {
entry = @[entry[0], RCTNullIfNil(RCTJSONStringify(mergedVal, NULL))];
}
if (jsonError) {
keyError = RCTJSErrorFromNSError(jsonError);
}
}
if (!keyError) {
keyError = [self _writeEntry:entry changedManifest:&changedManifest];
}
}
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}
// clang-format off
RCT_EXPORT_METHOD(multiRemove:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate removeValuesForKeys:keys
completion:^(NSArray<id<NSObject>> *results) {
NSArray<NSDictionary *> *errors = RCTMakeErrors(results);
callback(@[RCTNullIfNil(errors)]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
NSMutableArray<NSDictionary *> *errors;
BOOL changedManifest = NO;
for (NSString *key in keys) {
NSDictionary *keyError = RCTErrorForKey(key);
if (!keyError) {
if (_manifest[key] == (id)kCFNull) {
NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
}
if (_manifest[key]) {
changedManifest = YES;
[_manifest removeObjectForKey:key];
}
}
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}
// clang-format off
RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate removeAllValues:^(NSError *error) {
NSDictionary *result = nil;
if (error != nil) {
result = RCTMakeError(error.localizedDescription, error, nil);
}
callback(@[RCTNullIfNil(result)]);
}];
return;
}
[_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
NSDictionary *error = RCTDeleteStorageDirectory();
callback(@[RCTNullIfNil(error)]);
}
// clang-format off
RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate allKeys:^(NSArray<id<NSObject>> *keys) {
callback(@[(id)kCFNull, keys]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[errorOut, (id)kCFNull]);
} else {
callback(@[(id)kCFNull, _manifest.allKeys]);
}
}
@end

@ -0,0 +1,283 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
747102F0243FFB7400D4F466 /* RNCAsyncStorage.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
747102F1243FFB7400D4F466 /* RNCAsyncStorageDelegate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
B3E7B58A1CC2AC0600A0062D /* RNCAsyncStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
747102F0243FFB7400D4F466 /* RNCAsyncStorage.h in CopyFiles */,
747102F1243FFB7400D4F466 /* RNCAsyncStorageDelegate.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCAsyncStorage.a; sourceTree = BUILT_PRODUCTS_DIR; };
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCAsyncStorageDelegate.h; sourceTree = "<group>"; };
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCAsyncStorage.h; sourceTree = "<group>"; };
B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNCAsyncStorage.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */,
B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */,
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
19F94B1D2239A948006921A9 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */,
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */;
buildPhases = (
19F94B1D2239A948006921A9 /* Headers */,
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNCAsyncStorage;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B3E7B58A1CC2AC0600A0062D /* RNCAsyncStorage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

@ -0,0 +1,73 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^RNCAsyncStorageCompletion)(NSError *_Nullable error);
typedef void (^RNCAsyncStorageResultCallback)(NSArray<id<NSObject>> *valuesOrErrors);
@protocol RNCAsyncStorageDelegate <NSObject>
/*!
* Returns all keys currently stored. If none, an empty array is returned.
* @param block Block to call with result.
*/
- (void)allKeys:(RNCAsyncStorageResultCallback)block;
/*!
* Merges values with the corresponding values stored at specified keys.
* @param values Values to merge.
* @param keys Keys to the values that should be merged with.
* @param block Block to call with merged result.
*/
- (void)mergeValues:(NSArray<NSString *> *)values
forKeys:(NSArray<NSString *> *)keys
completion:(RNCAsyncStorageResultCallback)block;
/*!
* Removes all values from the store.
* @param block Block to call with result.
*/
- (void)removeAllValues:(RNCAsyncStorageCompletion)block;
/*!
* Removes all values associated with specified keys.
* @param keys Keys of values to remove.
* @param block Block to call with result.
*/
- (void)removeValuesForKeys:(NSArray<NSString *> *)keys
completion:(RNCAsyncStorageResultCallback)block;
/*!
* Sets specified key-value pairs.
* @param values Values to set.
* @param keys Keys of specified values to set.
* @param block Block to call with result.
*/
- (void)setValues:(NSArray<NSString *> *)values
forKeys:(NSArray<NSString *> *)keys
completion:(RNCAsyncStorageResultCallback)block;
/*!
* Returns values associated with specified keys.
* @param keys Keys of values to return.
* @param block Block to call with result.
*/
- (void)valuesForKeys:(NSArray<NSString *> *)keys completion:(RNCAsyncStorageResultCallback)block;
@optional
/*!
* Returns whether the delegate should be treated as a passthrough.
*/
@property (nonatomic, readonly, getter=isPassthrough) BOOL passthrough;
@end
NS_ASSUME_NONNULL_END

@ -0,0 +1,9 @@
import type {
AsyncStorageHook,
AsyncStorageStatic,
} from '../lib/typescript/types';
export function useAsyncStorage(key: string): AsyncStorageHook;
declare const AsyncStorage: AsyncStorageStatic;
export default AsyncStorage;

@ -0,0 +1,109 @@
/**
* @format
*/
const merge = require('merge-options').bind({
concatArrays: true,
ignoreUndefined: true,
});
const asMock = {
__INTERNAL_MOCK_STORAGE__: {},
setItem: jest.fn(async (key, value, callback) => {
const setResult = await asMock.multiSet([[key, value]], undefined);
callback && callback(setResult);
return setResult;
}),
getItem: jest.fn(async (key, callback) => {
const getResult = await asMock.multiGet([key], undefined);
const result = getResult[0] ? getResult[0][1] : null;
callback && callback(null, result);
return result;
}),
removeItem: jest.fn((key, callback) => asMock.multiRemove([key], callback)),
mergeItem: jest.fn((key, value, callback) =>
asMock.multiMerge([[key, value]], callback)
),
clear: jest.fn(_clear),
getAllKeys: jest.fn(_getAllKeys),
flushGetRequests: jest.fn(),
multiGet: jest.fn(_multiGet),
multiSet: jest.fn(_multiSet),
multiRemove: jest.fn(_multiRemove),
multiMerge: jest.fn(_multiMerge),
useAsyncStorage: jest.fn((key) => {
return {
getItem: (...args) => asMock.getItem(key, ...args),
setItem: (...args) => asMock.setItem(key, ...args),
mergeItem: (...args) => asMock.mergeItem(key, ...args),
removeItem: (...args) => asMock.removeItem(key, ...args),
};
}),
};
async function _multiSet(keyValuePairs, callback) {
keyValuePairs.forEach((keyValue) => {
const key = keyValue[0];
asMock.__INTERNAL_MOCK_STORAGE__[key] = keyValue[1];
});
callback && callback(null);
return null;
}
async function _multiGet(keys, callback) {
const values = keys.map((key) => [
key,
asMock.__INTERNAL_MOCK_STORAGE__[key] || null,
]);
callback && callback(null, values);
return values;
}
async function _multiRemove(keys, callback) {
keys.forEach((key) => {
if (asMock.__INTERNAL_MOCK_STORAGE__[key]) {
delete asMock.__INTERNAL_MOCK_STORAGE__[key];
}
});
callback && callback(null);
return null;
}
async function _clear(callback) {
asMock.__INTERNAL_MOCK_STORAGE__ = {};
callback && callback(null);
return null;
}
async function _getAllKeys() {
return Object.keys(asMock.__INTERNAL_MOCK_STORAGE__);
}
async function _multiMerge(keyValuePairs, callback) {
keyValuePairs.forEach((keyValue) => {
const [key, value] = keyValue;
const oldValue = asMock.__INTERNAL_MOCK_STORAGE__[key];
asMock.__INTERNAL_MOCK_STORAGE__[key] =
oldValue != null
? JSON.stringify(merge(JSON.parse(oldValue), JSON.parse(value)))
: value;
});
callback && callback(null);
return null;
}
module.exports = asMock;

@ -0,0 +1,164 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _mergeOptions = _interopRequireDefault(require("merge-options"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// @ts-ignore Cannot find module 'merge-options' or its corresponding type declarations
const merge = _mergeOptions.default.bind({
concatArrays: true,
ignoreUndefined: true
});
function mergeLocalStorageItem(key, value) {
const oldValue = window.localStorage.getItem(key);
if (oldValue) {
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify(merge(oldObject, newObject));
window.localStorage.setItem(key, nextValue);
} else {
window.localStorage.setItem(key, value);
}
}
function createPromise(getValue, callback) {
return new Promise((resolve, reject) => {
try {
const value = getValue();
callback === null || callback === void 0 ? void 0 : callback(null, value);
resolve(value);
} catch (err) {
callback === null || callback === void 0 ? void 0 : callback(err);
reject(err);
}
});
}
function createPromiseAll(promises, callback, processResult) {
return Promise.all(promises).then(result => {
const value = (processResult === null || processResult === void 0 ? void 0 : processResult(result)) ?? null;
callback === null || callback === void 0 ? void 0 : callback(null, value);
return Promise.resolve(value);
}, errors => {
callback === null || callback === void 0 ? void 0 : callback(errors);
return Promise.reject(errors);
});
}
const AsyncStorage = {
/**
* Fetches `key` value.
*/
getItem: (key, callback) => {
return createPromise(() => window.localStorage.getItem(key), callback);
},
/**
* Sets `value` for `key`.
*/
setItem: (key, value, callback) => {
return createPromise(() => window.localStorage.setItem(key, value), callback);
},
/**
* Removes a `key`
*/
removeItem: (key, callback) => {
return createPromise(() => window.localStorage.removeItem(key), callback);
},
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
mergeItem: (key, value, callback) => {
return createPromise(() => mergeLocalStorageItem(key, value), callback);
},
/**
* Erases *all* AsyncStorage for the domain.
*/
clear: callback => {
return createPromise(() => window.localStorage.clear(), callback);
},
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
getAllKeys: callback => {
return createPromise(() => {
const numberOfKeys = window.localStorage.length;
const keys = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i) || '';
keys.push(key);
}
return keys;
}, callback);
},
/**
* (stub) Flushes any pending requests using a single batch call to get the data.
*/
flushGetRequests: () => undefined,
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
multiGet: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.getItem(key));
const processResult = result => result.map((value, i) => [keys[i], value]);
return createPromiseAll(promises, callback, processResult);
},
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
multiSet: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.setItem(item[0], item[1]));
return createPromiseAll(promises, callback);
},
/**
* Delete all the keys in the `keys` array.
*/
multiRemove: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.removeItem(key));
return createPromiseAll(promises, callback);
},
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
multiMerge: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.mergeItem(item[0], item[1]));
return createPromiseAll(promises, callback);
}
};
var _default = AsyncStorage;
exports.default = _default;
//# sourceMappingURL=AsyncStorage.js.map

File diff suppressed because one or more lines are too long

@ -0,0 +1,366 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _helpers = require("./helpers");
var _RCTAsyncStorage = _interopRequireDefault(require("./RCTAsyncStorage"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
if (!_RCTAsyncStorage.default) {
throw new Error(`[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
Rebuild and restart the app.
Run the packager with \`--reset-cache\` flag.
If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory and then rebuild and re-run the app.
If this happens while testing with Jest, check out docs how to integrate AsyncStorage with it: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the Github repository: https://github.com/react-native-async-storage/async-storage/issues
`);
}
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
const AsyncStorage = (() => {
let _getRequests = [];
let _getKeys = [];
let _immediate = null;
return {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key);
_RCTAsyncStorage.default.multiGet([key], (errors, result) => {
var _result$;
// Unpack result to get value from [[key,value]]
const value = result !== null && result !== void 0 && (_result$ = result[0]) !== null && _result$ !== void 0 && _result$[1] ? result[0][1] : null;
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0], value);
if (errs) {
reject(errs[0]);
} else {
resolve(value);
}
});
});
},
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key, value);
_RCTAsyncStorage.default.multiSet([[key, value]], errors => {
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key);
_RCTAsyncStorage.default.multiRemove([key], errors => {
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key, value);
_RCTAsyncStorage.default.multiMerge([[key, value]], errors => {
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: callback => {
return new Promise((resolve, reject) => {
_RCTAsyncStorage.default.clear(error => {
const err = (0, _helpers.convertError)(error);
callback === null || callback === void 0 ? void 0 : callback(err);
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: callback => {
return new Promise((resolve, reject) => {
_RCTAsyncStorage.default.getAllKeys((error, keys) => {
const err = (0, _helpers.convertError)(error);
callback === null || callback === void 0 ? void 0 : callback(err, keys);
if (keys) {
resolve(keys);
} else {
reject(err);
}
});
});
},
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => {
const getRequests = _getRequests;
const getKeys = _getKeys;
_getRequests = [];
_getKeys = [];
_RCTAsyncStorage.default.multiGet(getKeys, (errors, result) => {
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
// it's much, much faster in practice for the data sets we deal with (we avoid
// allocating result pair arrays). This was heavily benchmarked.
//
// Is there a way to avoid using the map but fix the bug in this breaking test?
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
const map = {};
result === null || result === void 0 ? void 0 : result.forEach(_ref => {
let [key, value] = _ref;
map[key] = value;
return value;
});
const reqLength = getRequests.length;
/**
* As mentioned few lines above, this method could be called with the array of potential error,
* in case of anything goes wrong. The problem is, if any of the batched calls fails
* the rest of them would fail too, but the error would be consumed by just one. The rest
* would simply return `undefined` as their result, rendering false negatives.
*
* In order to avoid this situation, in case of any call failing,
* the rest of them will be rejected as well (with the same error).
*/
const errorList = (0, _helpers.convertErrors)(errors);
const error = errorList !== null && errorList !== void 0 && errorList.length ? errorList[0] : null;
for (let i = 0; i < reqLength; i++) {
var _request$callback2, _request$resolve;
const request = getRequests[i];
if (error) {
var _request$callback, _request$reject;
(_request$callback = request.callback) === null || _request$callback === void 0 ? void 0 : _request$callback.call(request, errorList);
(_request$reject = request.reject) === null || _request$reject === void 0 ? void 0 : _request$reject.call(request, error);
continue;
}
const requestResult = request.keys.map(key => [key, map[key]]);
(_request$callback2 = request.callback) === null || _request$callback2 === void 0 ? void 0 : _request$callback2.call(request, null, requestResult);
(_request$resolve = request.resolve) === null || _request$resolve === void 0 ? void 0 : _request$resolve.call(request, requestResult);
}
});
},
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys, callback) => {
if (!_immediate) {
_immediate = setImmediate(() => {
_immediate = null;
AsyncStorage.flushGetRequests();
});
}
const getRequest = {
keys: keys,
callback: callback,
// do we need this?
keyIndex: _getKeys.length,
resolve: null,
reject: null
};
const promiseResult = new Promise((resolve, reject) => {
getRequest.resolve = resolve;
getRequest.reject = reject;
});
_getRequests.push(getRequest); // avoid fetching duplicates
keys.forEach(key => {
if (_getKeys.indexOf(key) === -1) {
_getKeys.push(key);
}
});
return promiseResult;
},
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs, callback) => {
(0, _helpers.checkValidArgs)(keyValuePairs, callback);
return new Promise((resolve, reject) => {
keyValuePairs.forEach(_ref2 => {
let [key, value] = _ref2;
(0, _helpers.checkValidInput)(key, value);
});
_RCTAsyncStorage.default.multiSet(keyValuePairs, errors => {
const error = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys, callback) => {
return new Promise((resolve, reject) => {
keys.forEach(key => (0, _helpers.checkValidInput)(key));
_RCTAsyncStorage.default.multiRemove(keys, errors => {
const error = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs, callback) => {
return new Promise((resolve, reject) => {
_RCTAsyncStorage.default.multiMerge(keyValuePairs, errors => {
const error = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
};
})();
var _default = AsyncStorage;
exports.default = _default;
//# sourceMappingURL=AsyncStorage.native.js.map

File diff suppressed because one or more lines are too long

@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _reactNative = require("react-native");
var _shouldFallbackToLegacyNativeModule = require("./shouldFallbackToLegacyNativeModule");
// @ts-ignore Module '"react-native"' has no exported member 'TurboModuleRegistry'.
let RCTAsyncStorage = _reactNative.NativeModules['PlatformLocalStorage'] || // Support for external modules, like react-native-windows
_reactNative.NativeModules['RNC_AsyncSQLiteDBStorage'] || _reactNative.NativeModules['RNCAsyncStorage'];
if (!RCTAsyncStorage && (0, _shouldFallbackToLegacyNativeModule.shouldFallbackToLegacyNativeModule)()) {
// TurboModuleRegistry falls back to NativeModules so we don't have to try go
// assign NativeModules' counterparts if TurboModuleRegistry would resolve
// with undefined.
if (_reactNative.TurboModuleRegistry) {
RCTAsyncStorage = _reactNative.TurboModuleRegistry.get('AsyncSQLiteDBStorage') || _reactNative.TurboModuleRegistry.get('AsyncLocalStorage');
} else {
RCTAsyncStorage = _reactNative.NativeModules['AsyncSQLiteDBStorage'] || _reactNative.NativeModules['AsyncLocalStorage'];
}
}
var _default = RCTAsyncStorage;
exports.default = _default;
//# sourceMappingURL=RCTAsyncStorage.js.map

@ -0,0 +1 @@
{"version":3,"names":["RCTAsyncStorage","NativeModules","shouldFallbackToLegacyNativeModule","TurboModuleRegistry","get"],"sources":["RCTAsyncStorage.ts"],"sourcesContent":["// @ts-ignore Module '\"react-native\"' has no exported member 'TurboModuleRegistry'.\nimport { NativeModules, TurboModuleRegistry } from 'react-native';\nimport { shouldFallbackToLegacyNativeModule } from './shouldFallbackToLegacyNativeModule';\n\nlet RCTAsyncStorage =\n NativeModules['PlatformLocalStorage'] || // Support for external modules, like react-native-windows\n NativeModules['RNC_AsyncSQLiteDBStorage'] ||\n NativeModules['RNCAsyncStorage'];\n\nif (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {\n // TurboModuleRegistry falls back to NativeModules so we don't have to try go\n // assign NativeModules' counterparts if TurboModuleRegistry would resolve\n // with undefined.\n if (TurboModuleRegistry) {\n RCTAsyncStorage =\n TurboModuleRegistry.get('AsyncSQLiteDBStorage') ||\n TurboModuleRegistry.get('AsyncLocalStorage');\n } else {\n RCTAsyncStorage =\n NativeModules['AsyncSQLiteDBStorage'] ||\n NativeModules['AsyncLocalStorage'];\n }\n}\n\nexport default RCTAsyncStorage;\n"],"mappings":";;;;;;;AACA;;AACA;;AAFA;AAIA,IAAIA,eAAe,GACjBC,0BAAA,CAAc,sBAAd,KAAyC;AACzCA,0BAAA,CAAc,0BAAd,CADA,IAEAA,0BAAA,CAAc,iBAAd,CAHF;;AAKA,IAAI,CAACD,eAAD,IAAoB,IAAAE,sEAAA,GAAxB,EAA8D;EAC5D;EACA;EACA;EACA,IAAIC,gCAAJ,EAAyB;IACvBH,eAAe,GACbG,gCAAA,CAAoBC,GAApB,CAAwB,sBAAxB,KACAD,gCAAA,CAAoBC,GAApB,CAAwB,mBAAxB,CAFF;EAGD,CAJD,MAIO;IACLJ,eAAe,GACbC,0BAAA,CAAc,sBAAd,KACAA,0BAAA,CAAc,mBAAd,CAFF;EAGD;AACF;;eAEcD,e"}

@ -0,0 +1,69 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.checkValidArgs = checkValidArgs;
exports.checkValidInput = checkValidInput;
exports.convertError = convertError;
exports.convertErrors = convertErrors;
function checkValidArgs(keyValuePairs, callback) {
if (!Array.isArray(keyValuePairs) || keyValuePairs.length === 0 || !Array.isArray(keyValuePairs[0])) {
throw new Error('[AsyncStorage] Expected array of key-value pairs as first argument to multiSet');
}
if (callback && typeof callback !== 'function') {
if (Array.isArray(callback)) {
throw new Error('[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?');
}
throw new Error('[AsyncStorage] Expected function as second argument to multiSet');
}
}
function checkValidInput() {
for (var _len = arguments.length, input = new Array(_len), _key = 0; _key < _len; _key++) {
input[_key] = arguments[_key];
}
const [key, value] = input;
if (typeof key !== 'string') {
console.warn(`[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${key}\n`);
}
if (input.length > 1 && typeof value !== 'string') {
if (value == null) {
throw new Error(`[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\nPassed value: ${value}\nPassed key: ${key}\n`);
} else {
console.warn(`[AsyncStorage] The value for key "${key}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${key}\n`);
}
}
}
function convertError(error) {
if (!error) {
return null;
}
const out = new Error(error.message);
out.key = error.key;
return out;
}
function convertErrors(errs) {
const errors = ensureArray(errs);
return errors ? errors.map(e => convertError(e)) : null;
}
function ensureArray(e) {
if (Array.isArray(e)) {
return e.length === 0 ? null : e;
} else if (e) {
return [e];
} else {
return null;
}
}
//# sourceMappingURL=helpers.js.map

@ -0,0 +1 @@
{"version":3,"names":["checkValidArgs","keyValuePairs","callback","Array","isArray","length","Error","checkValidInput","input","key","value","console","warn","convertError","error","out","message","convertErrors","errs","errors","ensureArray","map","e"],"sources":["helpers.ts"],"sourcesContent":["import type { ErrorLike } from './types';\n\nexport function checkValidArgs(keyValuePairs: unknown[], callback: unknown) {\n if (\n !Array.isArray(keyValuePairs) ||\n keyValuePairs.length === 0 ||\n !Array.isArray(keyValuePairs[0])\n ) {\n throw new Error(\n '[AsyncStorage] Expected array of key-value pairs as first argument to multiSet'\n );\n }\n\n if (callback && typeof callback !== 'function') {\n if (Array.isArray(callback)) {\n throw new Error(\n '[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?'\n );\n }\n\n throw new Error(\n '[AsyncStorage] Expected function as second argument to multiSet'\n );\n }\n}\n\nexport function checkValidInput(...input: unknown[]) {\n const [key, value] = input;\n\n if (typeof key !== 'string') {\n console.warn(\n `[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\\nKey passed: ${key}\\n`\n );\n }\n\n if (input.length > 1 && typeof value !== 'string') {\n if (value == null) {\n throw new Error(\n `[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n } else {\n console.warn(\n `[AsyncStorage] The value for key \"${key}\" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n }\n }\n}\n\nexport function convertError(error?: ErrorLike): Error | null {\n if (!error) {\n return null;\n }\n\n const out = new Error(error.message);\n (out as any).key = error.key;\n return out;\n}\n\nexport function convertErrors(\n errs?: ErrorLike[]\n): ReadonlyArray<Error | null> | null {\n const errors = ensureArray(errs);\n return errors ? errors.map((e) => convertError(e)) : null;\n}\n\nfunction ensureArray(e?: ErrorLike | ErrorLike[]): ErrorLike[] | null {\n if (Array.isArray(e)) {\n return e.length === 0 ? null : e;\n } else if (e) {\n return [e];\n } else {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;AAEO,SAASA,cAAT,CAAwBC,aAAxB,EAAkDC,QAAlD,EAAqE;EAC1E,IACE,CAACC,KAAK,CAACC,OAAN,CAAcH,aAAd,CAAD,IACAA,aAAa,CAACI,MAAd,KAAyB,CADzB,IAEA,CAACF,KAAK,CAACC,OAAN,CAAcH,aAAa,CAAC,CAAD,CAA3B,CAHH,EAIE;IACA,MAAM,IAAIK,KAAJ,CACJ,gFADI,CAAN;EAGD;;EAED,IAAIJ,QAAQ,IAAI,OAAOA,QAAP,KAAoB,UAApC,EAAgD;IAC9C,IAAIC,KAAK,CAACC,OAAN,CAAcF,QAAd,CAAJ,EAA6B;MAC3B,MAAM,IAAII,KAAJ,CACJ,6IADI,CAAN;IAGD;;IAED,MAAM,IAAIA,KAAJ,CACJ,iEADI,CAAN;EAGD;AACF;;AAEM,SAASC,eAAT,GAA8C;EAAA,kCAAlBC,KAAkB;IAAlBA,KAAkB;EAAA;;EACnD,MAAM,CAACC,GAAD,EAAMC,KAAN,IAAeF,KAArB;;EAEA,IAAI,OAAOC,GAAP,KAAe,QAAnB,EAA6B;IAC3BE,OAAO,CAACC,IAAR,CACG,wBAAuB,OAAOH,GAAI,iHAAgHA,GAAI,IADzJ;EAGD;;EAED,IAAID,KAAK,CAACH,MAAN,GAAe,CAAf,IAAoB,OAAOK,KAAP,KAAiB,QAAzC,EAAmD;IACjD,IAAIA,KAAK,IAAI,IAAb,EAAmB;MACjB,MAAM,IAAIJ,KAAJ,CACH,gJAA+II,KAAM,iBAAgBD,GAAI,IADtK,CAAN;IAGD,CAJD,MAIO;MACLE,OAAO,CAACC,IAAR,CACG,qCAAoCH,GAAI,4GAA2GC,KAAM,iBAAgBD,GAAI,IADhL;IAGD;EACF;AACF;;AAEM,SAASI,YAAT,CAAsBC,KAAtB,EAAuD;EAC5D,IAAI,CAACA,KAAL,EAAY;IACV,OAAO,IAAP;EACD;;EAED,MAAMC,GAAG,GAAG,IAAIT,KAAJ,CAAUQ,KAAK,CAACE,OAAhB,CAAZ;EACCD,GAAD,CAAaN,GAAb,GAAmBK,KAAK,CAACL,GAAzB;EACA,OAAOM,GAAP;AACD;;AAEM,SAASE,aAAT,CACLC,IADK,EAE+B;EACpC,MAAMC,MAAM,GAAGC,WAAW,CAACF,IAAD,CAA1B;EACA,OAAOC,MAAM,GAAGA,MAAM,CAACE,GAAP,CAAYC,CAAD,IAAOT,YAAY,CAACS,CAAD,CAA9B,CAAH,GAAwC,IAArD;AACD;;AAED,SAASF,WAAT,CAAqBE,CAArB,EAAsE;EACpE,IAAInB,KAAK,CAACC,OAAN,CAAckB,CAAd,CAAJ,EAAsB;IACpB,OAAOA,CAAC,CAACjB,MAAF,KAAa,CAAb,GAAiB,IAAjB,GAAwBiB,CAA/B;EACD,CAFD,MAEO,IAAIA,CAAJ,EAAO;IACZ,OAAO,CAACA,CAAD,CAAP;EACD,CAFM,MAEA;IACL,OAAO,IAAP;EACD;AACF"}

@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useAsyncStorage = useAsyncStorage;
var _AsyncStorage = _interopRequireDefault(require("./AsyncStorage"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function useAsyncStorage(key) {
return {
getItem: function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _AsyncStorage.default.getItem(key, ...args);
},
setItem: function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _AsyncStorage.default.setItem(key, ...args);
},
mergeItem: function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return _AsyncStorage.default.mergeItem(key, ...args);
},
removeItem: function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return _AsyncStorage.default.removeItem(key, ...args);
}
};
}
//# sourceMappingURL=hooks.js.map

@ -0,0 +1 @@
{"version":3,"names":["useAsyncStorage","key","getItem","args","AsyncStorage","setItem","mergeItem","removeItem"],"sources":["hooks.ts"],"sourcesContent":["import AsyncStorage from './AsyncStorage';\nimport type { AsyncStorageHook } from './types';\n\nexport function useAsyncStorage(key: string): AsyncStorageHook {\n return {\n getItem: (...args) => AsyncStorage.getItem(key, ...args),\n setItem: (...args) => AsyncStorage.setItem(key, ...args),\n mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),\n removeItem: (...args) => AsyncStorage.removeItem(key, ...args),\n };\n}\n"],"mappings":";;;;;;;AAAA;;;;AAGO,SAASA,eAAT,CAAyBC,GAAzB,EAAwD;EAC7D,OAAO;IACLC,OAAO,EAAE;MAAA,kCAAIC,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaC,qBAAA,CAAaF,OAAb,CAAqBD,GAArB,EAA0B,GAAGE,IAA7B,CAAb;IAAA,CADJ;IAELE,OAAO,EAAE;MAAA,mCAAIF,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaC,qBAAA,CAAaC,OAAb,CAAqBJ,GAArB,EAA0B,GAAGE,IAA7B,CAAb;IAAA,CAFJ;IAGLG,SAAS,EAAE;MAAA,mCAAIH,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaC,qBAAA,CAAaE,SAAb,CAAuBL,GAAvB,EAA4B,GAAGE,IAA/B,CAAb;IAAA,CAHN;IAILI,UAAU,EAAE;MAAA,mCAAIJ,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaC,qBAAA,CAAaG,UAAb,CAAwBN,GAAxB,EAA6B,GAAGE,IAAhC,CAAb;IAAA;EAJP,CAAP;AAMD"}

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
Object.defineProperty(exports, "useAsyncStorage", {
enumerable: true,
get: function () {
return _hooks.useAsyncStorage;
}
});
var _AsyncStorage = _interopRequireDefault(require("./AsyncStorage"));
var _hooks = require("./hooks");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = _AsyncStorage.default;
exports.default = _default;
//# sourceMappingURL=index.js.map

@ -0,0 +1 @@
{"version":3,"names":["AsyncStorage"],"sources":["index.ts"],"sourcesContent":["import AsyncStorage from './AsyncStorage';\n\nexport { useAsyncStorage } from './hooks';\n\nexport type { AsyncStorageStatic } from './types';\n\nexport default AsyncStorage;\n"],"mappings":";;;;;;;;;;;;;AAAA;;AAEA;;;;eAIeA,qB"}

@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.shouldFallbackToLegacyNativeModule = shouldFallbackToLegacyNativeModule;
var _reactNative = require("react-native");
function shouldFallbackToLegacyNativeModule() {
var _NativeModules$Native, _NativeModules$Native2;
const expoConstants = (_NativeModules$Native = _reactNative.NativeModules['NativeUnimoduleProxy']) === null || _NativeModules$Native === void 0 ? void 0 : (_NativeModules$Native2 = _NativeModules$Native.modulesConstants) === null || _NativeModules$Native2 === void 0 ? void 0 : _NativeModules$Native2.ExponentConstants;
if (expoConstants) {
/**
* In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.
* In bare React Native apps using expo-constants, appOwnership is never defined, so
* isLegacySdkVersion will be false in that context.
*/
const isLegacySdkVersion = expoConstants.appOwnership && !expoConstants.executionEnvironment;
/**
* Expo managed apps don't include the @react-native-async-storage/async-storage
* native modules yet, but the API interface is the same, so we can use the version
* exported from React Native still.
*
* If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this
* will likely not be valid anymore, and the package will need to be included in the Expo SDK
* to continue to work.
*/
if (isLegacySdkVersion || ['storeClient', 'standalone'].includes(expoConstants.executionEnvironment)) {
return true;
}
}
return false;
}
//# sourceMappingURL=shouldFallbackToLegacyNativeModule.js.map

@ -0,0 +1 @@
{"version":3,"names":["shouldFallbackToLegacyNativeModule","expoConstants","NativeModules","modulesConstants","ExponentConstants","isLegacySdkVersion","appOwnership","executionEnvironment","includes"],"sources":["shouldFallbackToLegacyNativeModule.ts"],"sourcesContent":["import { NativeModules } from 'react-native';\n\nexport function shouldFallbackToLegacyNativeModule(): boolean {\n const expoConstants =\n NativeModules['NativeUnimoduleProxy']?.modulesConstants?.ExponentConstants;\n\n if (expoConstants) {\n /**\n * In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.\n * In bare React Native apps using expo-constants, appOwnership is never defined, so\n * isLegacySdkVersion will be false in that context.\n */\n const isLegacySdkVersion =\n expoConstants.appOwnership && !expoConstants.executionEnvironment;\n\n /**\n * Expo managed apps don't include the @react-native-async-storage/async-storage\n * native modules yet, but the API interface is the same, so we can use the version\n * exported from React Native still.\n *\n * If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this\n * will likely not be valid anymore, and the package will need to be included in the Expo SDK\n * to continue to work.\n */\n if (\n isLegacySdkVersion ||\n ['storeClient', 'standalone'].includes(expoConstants.executionEnvironment)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":";;;;;;;AAAA;;AAEO,SAASA,kCAAT,GAAuD;EAAA;;EAC5D,MAAMC,aAAa,4BACjBC,0BAAA,CAAc,sBAAd,CADiB,oFACjB,sBAAuCC,gBADtB,2DACjB,uBAAyDC,iBAD3D;;EAGA,IAAIH,aAAJ,EAAmB;IACjB;AACJ;AACA;AACA;AACA;IACI,MAAMI,kBAAkB,GACtBJ,aAAa,CAACK,YAAd,IAA8B,CAACL,aAAa,CAACM,oBAD/C;IAGA;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;IACI,IACEF,kBAAkB,IAClB,CAAC,aAAD,EAAgB,YAAhB,EAA8BG,QAA9B,CAAuCP,aAAa,CAACM,oBAArD,CAFF,EAGE;MACA,OAAO,IAAP;IACD;EACF;;EAED,OAAO,KAAP;AACD"}

@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=types.js.map

File diff suppressed because one or more lines are too long

@ -0,0 +1,153 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// @ts-ignore Cannot find module 'merge-options' or its corresponding type declarations
import mergeOptions from 'merge-options';
const merge = mergeOptions.bind({
concatArrays: true,
ignoreUndefined: true
});
function mergeLocalStorageItem(key, value) {
const oldValue = window.localStorage.getItem(key);
if (oldValue) {
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify(merge(oldObject, newObject));
window.localStorage.setItem(key, nextValue);
} else {
window.localStorage.setItem(key, value);
}
}
function createPromise(getValue, callback) {
return new Promise((resolve, reject) => {
try {
const value = getValue();
callback === null || callback === void 0 ? void 0 : callback(null, value);
resolve(value);
} catch (err) {
callback === null || callback === void 0 ? void 0 : callback(err);
reject(err);
}
});
}
function createPromiseAll(promises, callback, processResult) {
return Promise.all(promises).then(result => {
const value = (processResult === null || processResult === void 0 ? void 0 : processResult(result)) ?? null;
callback === null || callback === void 0 ? void 0 : callback(null, value);
return Promise.resolve(value);
}, errors => {
callback === null || callback === void 0 ? void 0 : callback(errors);
return Promise.reject(errors);
});
}
const AsyncStorage = {
/**
* Fetches `key` value.
*/
getItem: (key, callback) => {
return createPromise(() => window.localStorage.getItem(key), callback);
},
/**
* Sets `value` for `key`.
*/
setItem: (key, value, callback) => {
return createPromise(() => window.localStorage.setItem(key, value), callback);
},
/**
* Removes a `key`
*/
removeItem: (key, callback) => {
return createPromise(() => window.localStorage.removeItem(key), callback);
},
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
mergeItem: (key, value, callback) => {
return createPromise(() => mergeLocalStorageItem(key, value), callback);
},
/**
* Erases *all* AsyncStorage for the domain.
*/
clear: callback => {
return createPromise(() => window.localStorage.clear(), callback);
},
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
getAllKeys: callback => {
return createPromise(() => {
const numberOfKeys = window.localStorage.length;
const keys = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i) || '';
keys.push(key);
}
return keys;
}, callback);
},
/**
* (stub) Flushes any pending requests using a single batch call to get the data.
*/
flushGetRequests: () => undefined,
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
multiGet: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.getItem(key));
const processResult = result => result.map((value, i) => [keys[i], value]);
return createPromiseAll(promises, callback, processResult);
},
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
multiSet: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.setItem(item[0], item[1]));
return createPromiseAll(promises, callback);
},
/**
* Delete all the keys in the `keys` array.
*/
multiRemove: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.removeItem(key));
return createPromiseAll(promises, callback);
},
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
multiMerge: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.mergeItem(item[0], item[1]));
return createPromiseAll(promises, callback);
}
};
export default AsyncStorage;
//# sourceMappingURL=AsyncStorage.js.map

File diff suppressed because one or more lines are too long

@ -0,0 +1,348 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { checkValidArgs, checkValidInput, convertError, convertErrors } from './helpers';
import RCTAsyncStorage from './RCTAsyncStorage';
if (!RCTAsyncStorage) {
throw new Error(`[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
Rebuild and restart the app.
Run the packager with \`--reset-cache\` flag.
If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory and then rebuild and re-run the app.
If this happens while testing with Jest, check out docs how to integrate AsyncStorage with it: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the Github repository: https://github.com/react-native-async-storage/async-storage/issues
`);
}
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
const AsyncStorage = (() => {
let _getRequests = [];
let _getKeys = [];
let _immediate = null;
return {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiGet([key], (errors, result) => {
var _result$;
// Unpack result to get value from [[key,value]]
const value = result !== null && result !== void 0 && (_result$ = result[0]) !== null && _result$ !== void 0 && _result$[1] ? result[0][1] : null;
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0], value);
if (errs) {
reject(errs[0]);
} else {
resolve(value);
}
});
});
},
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiSet([[key, value]], errors => {
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiRemove([key], errors => {
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiMerge([[key, value]], errors => {
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: callback => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.clear(error => {
const err = convertError(error);
callback === null || callback === void 0 ? void 0 : callback(err);
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: callback => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.getAllKeys((error, keys) => {
const err = convertError(error);
callback === null || callback === void 0 ? void 0 : callback(err, keys);
if (keys) {
resolve(keys);
} else {
reject(err);
}
});
});
},
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => {
const getRequests = _getRequests;
const getKeys = _getKeys;
_getRequests = [];
_getKeys = [];
RCTAsyncStorage.multiGet(getKeys, (errors, result) => {
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
// it's much, much faster in practice for the data sets we deal with (we avoid
// allocating result pair arrays). This was heavily benchmarked.
//
// Is there a way to avoid using the map but fix the bug in this breaking test?
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
const map = {};
result === null || result === void 0 ? void 0 : result.forEach(_ref => {
let [key, value] = _ref;
map[key] = value;
return value;
});
const reqLength = getRequests.length;
/**
* As mentioned few lines above, this method could be called with the array of potential error,
* in case of anything goes wrong. The problem is, if any of the batched calls fails
* the rest of them would fail too, but the error would be consumed by just one. The rest
* would simply return `undefined` as their result, rendering false negatives.
*
* In order to avoid this situation, in case of any call failing,
* the rest of them will be rejected as well (with the same error).
*/
const errorList = convertErrors(errors);
const error = errorList !== null && errorList !== void 0 && errorList.length ? errorList[0] : null;
for (let i = 0; i < reqLength; i++) {
var _request$callback2, _request$resolve;
const request = getRequests[i];
if (error) {
var _request$callback, _request$reject;
(_request$callback = request.callback) === null || _request$callback === void 0 ? void 0 : _request$callback.call(request, errorList);
(_request$reject = request.reject) === null || _request$reject === void 0 ? void 0 : _request$reject.call(request, error);
continue;
}
const requestResult = request.keys.map(key => [key, map[key]]);
(_request$callback2 = request.callback) === null || _request$callback2 === void 0 ? void 0 : _request$callback2.call(request, null, requestResult);
(_request$resolve = request.resolve) === null || _request$resolve === void 0 ? void 0 : _request$resolve.call(request, requestResult);
}
});
},
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys, callback) => {
if (!_immediate) {
_immediate = setImmediate(() => {
_immediate = null;
AsyncStorage.flushGetRequests();
});
}
const getRequest = {
keys: keys,
callback: callback,
// do we need this?
keyIndex: _getKeys.length,
resolve: null,
reject: null
};
const promiseResult = new Promise((resolve, reject) => {
getRequest.resolve = resolve;
getRequest.reject = reject;
});
_getRequests.push(getRequest); // avoid fetching duplicates
keys.forEach(key => {
if (_getKeys.indexOf(key) === -1) {
_getKeys.push(key);
}
});
return promiseResult;
},
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs, callback) => {
checkValidArgs(keyValuePairs, callback);
return new Promise((resolve, reject) => {
keyValuePairs.forEach(_ref2 => {
let [key, value] = _ref2;
checkValidInput(key, value);
});
RCTAsyncStorage.multiSet(keyValuePairs, errors => {
const error = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys, callback) => {
return new Promise((resolve, reject) => {
keys.forEach(key => checkValidInput(key));
RCTAsyncStorage.multiRemove(keys, errors => {
const error = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs, callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge(keyValuePairs, errors => {
const error = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
};
})();
export default AsyncStorage;
//# sourceMappingURL=AsyncStorage.native.js.map

File diff suppressed because one or more lines are too long

@ -0,0 +1,19 @@
// @ts-ignore Module '"react-native"' has no exported member 'TurboModuleRegistry'.
import { NativeModules, TurboModuleRegistry } from 'react-native';
import { shouldFallbackToLegacyNativeModule } from './shouldFallbackToLegacyNativeModule';
let RCTAsyncStorage = NativeModules['PlatformLocalStorage'] || // Support for external modules, like react-native-windows
NativeModules['RNC_AsyncSQLiteDBStorage'] || NativeModules['RNCAsyncStorage'];
if (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {
// TurboModuleRegistry falls back to NativeModules so we don't have to try go
// assign NativeModules' counterparts if TurboModuleRegistry would resolve
// with undefined.
if (TurboModuleRegistry) {
RCTAsyncStorage = TurboModuleRegistry.get('AsyncSQLiteDBStorage') || TurboModuleRegistry.get('AsyncLocalStorage');
} else {
RCTAsyncStorage = NativeModules['AsyncSQLiteDBStorage'] || NativeModules['AsyncLocalStorage'];
}
}
export default RCTAsyncStorage;
//# sourceMappingURL=RCTAsyncStorage.js.map

@ -0,0 +1 @@
{"version":3,"names":["NativeModules","TurboModuleRegistry","shouldFallbackToLegacyNativeModule","RCTAsyncStorage","get"],"sources":["RCTAsyncStorage.ts"],"sourcesContent":["// @ts-ignore Module '\"react-native\"' has no exported member 'TurboModuleRegistry'.\nimport { NativeModules, TurboModuleRegistry } from 'react-native';\nimport { shouldFallbackToLegacyNativeModule } from './shouldFallbackToLegacyNativeModule';\n\nlet RCTAsyncStorage =\n NativeModules['PlatformLocalStorage'] || // Support for external modules, like react-native-windows\n NativeModules['RNC_AsyncSQLiteDBStorage'] ||\n NativeModules['RNCAsyncStorage'];\n\nif (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {\n // TurboModuleRegistry falls back to NativeModules so we don't have to try go\n // assign NativeModules' counterparts if TurboModuleRegistry would resolve\n // with undefined.\n if (TurboModuleRegistry) {\n RCTAsyncStorage =\n TurboModuleRegistry.get('AsyncSQLiteDBStorage') ||\n TurboModuleRegistry.get('AsyncLocalStorage');\n } else {\n RCTAsyncStorage =\n NativeModules['AsyncSQLiteDBStorage'] ||\n NativeModules['AsyncLocalStorage'];\n }\n}\n\nexport default RCTAsyncStorage;\n"],"mappings":"AAAA;AACA,SAASA,aAAT,EAAwBC,mBAAxB,QAAmD,cAAnD;AACA,SAASC,kCAAT,QAAmD,sCAAnD;AAEA,IAAIC,eAAe,GACjBH,aAAa,CAAC,sBAAD,CAAb,IAAyC;AACzCA,aAAa,CAAC,0BAAD,CADb,IAEAA,aAAa,CAAC,iBAAD,CAHf;;AAKA,IAAI,CAACG,eAAD,IAAoBD,kCAAkC,EAA1D,EAA8D;EAC5D;EACA;EACA;EACA,IAAID,mBAAJ,EAAyB;IACvBE,eAAe,GACbF,mBAAmB,CAACG,GAApB,CAAwB,sBAAxB,KACAH,mBAAmB,CAACG,GAApB,CAAwB,mBAAxB,CAFF;EAGD,CAJD,MAIO;IACLD,eAAe,GACbH,aAAa,CAAC,sBAAD,CAAb,IACAA,aAAa,CAAC,mBAAD,CAFf;EAGD;AACF;;AAED,eAAeG,eAAf"}

@ -0,0 +1,56 @@
export function checkValidArgs(keyValuePairs, callback) {
if (!Array.isArray(keyValuePairs) || keyValuePairs.length === 0 || !Array.isArray(keyValuePairs[0])) {
throw new Error('[AsyncStorage] Expected array of key-value pairs as first argument to multiSet');
}
if (callback && typeof callback !== 'function') {
if (Array.isArray(callback)) {
throw new Error('[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?');
}
throw new Error('[AsyncStorage] Expected function as second argument to multiSet');
}
}
export function checkValidInput() {
for (var _len = arguments.length, input = new Array(_len), _key = 0; _key < _len; _key++) {
input[_key] = arguments[_key];
}
const [key, value] = input;
if (typeof key !== 'string') {
console.warn(`[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${key}\n`);
}
if (input.length > 1 && typeof value !== 'string') {
if (value == null) {
throw new Error(`[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\nPassed value: ${value}\nPassed key: ${key}\n`);
} else {
console.warn(`[AsyncStorage] The value for key "${key}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${key}\n`);
}
}
}
export function convertError(error) {
if (!error) {
return null;
}
const out = new Error(error.message);
out.key = error.key;
return out;
}
export function convertErrors(errs) {
const errors = ensureArray(errs);
return errors ? errors.map(e => convertError(e)) : null;
}
function ensureArray(e) {
if (Array.isArray(e)) {
return e.length === 0 ? null : e;
} else if (e) {
return [e];
} else {
return null;
}
}
//# sourceMappingURL=helpers.js.map

@ -0,0 +1 @@
{"version":3,"names":["checkValidArgs","keyValuePairs","callback","Array","isArray","length","Error","checkValidInput","input","key","value","console","warn","convertError","error","out","message","convertErrors","errs","errors","ensureArray","map","e"],"sources":["helpers.ts"],"sourcesContent":["import type { ErrorLike } from './types';\n\nexport function checkValidArgs(keyValuePairs: unknown[], callback: unknown) {\n if (\n !Array.isArray(keyValuePairs) ||\n keyValuePairs.length === 0 ||\n !Array.isArray(keyValuePairs[0])\n ) {\n throw new Error(\n '[AsyncStorage] Expected array of key-value pairs as first argument to multiSet'\n );\n }\n\n if (callback && typeof callback !== 'function') {\n if (Array.isArray(callback)) {\n throw new Error(\n '[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?'\n );\n }\n\n throw new Error(\n '[AsyncStorage] Expected function as second argument to multiSet'\n );\n }\n}\n\nexport function checkValidInput(...input: unknown[]) {\n const [key, value] = input;\n\n if (typeof key !== 'string') {\n console.warn(\n `[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\\nKey passed: ${key}\\n`\n );\n }\n\n if (input.length > 1 && typeof value !== 'string') {\n if (value == null) {\n throw new Error(\n `[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n } else {\n console.warn(\n `[AsyncStorage] The value for key \"${key}\" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n }\n }\n}\n\nexport function convertError(error?: ErrorLike): Error | null {\n if (!error) {\n return null;\n }\n\n const out = new Error(error.message);\n (out as any).key = error.key;\n return out;\n}\n\nexport function convertErrors(\n errs?: ErrorLike[]\n): ReadonlyArray<Error | null> | null {\n const errors = ensureArray(errs);\n return errors ? errors.map((e) => convertError(e)) : null;\n}\n\nfunction ensureArray(e?: ErrorLike | ErrorLike[]): ErrorLike[] | null {\n if (Array.isArray(e)) {\n return e.length === 0 ? null : e;\n } else if (e) {\n return [e];\n } else {\n return null;\n }\n}\n"],"mappings":"AAEA,OAAO,SAASA,cAAT,CAAwBC,aAAxB,EAAkDC,QAAlD,EAAqE;EAC1E,IACE,CAACC,KAAK,CAACC,OAAN,CAAcH,aAAd,CAAD,IACAA,aAAa,CAACI,MAAd,KAAyB,CADzB,IAEA,CAACF,KAAK,CAACC,OAAN,CAAcH,aAAa,CAAC,CAAD,CAA3B,CAHH,EAIE;IACA,MAAM,IAAIK,KAAJ,CACJ,gFADI,CAAN;EAGD;;EAED,IAAIJ,QAAQ,IAAI,OAAOA,QAAP,KAAoB,UAApC,EAAgD;IAC9C,IAAIC,KAAK,CAACC,OAAN,CAAcF,QAAd,CAAJ,EAA6B;MAC3B,MAAM,IAAII,KAAJ,CACJ,6IADI,CAAN;IAGD;;IAED,MAAM,IAAIA,KAAJ,CACJ,iEADI,CAAN;EAGD;AACF;AAED,OAAO,SAASC,eAAT,GAA8C;EAAA,kCAAlBC,KAAkB;IAAlBA,KAAkB;EAAA;;EACnD,MAAM,CAACC,GAAD,EAAMC,KAAN,IAAeF,KAArB;;EAEA,IAAI,OAAOC,GAAP,KAAe,QAAnB,EAA6B;IAC3BE,OAAO,CAACC,IAAR,CACG,wBAAuB,OAAOH,GAAI,iHAAgHA,GAAI,IADzJ;EAGD;;EAED,IAAID,KAAK,CAACH,MAAN,GAAe,CAAf,IAAoB,OAAOK,KAAP,KAAiB,QAAzC,EAAmD;IACjD,IAAIA,KAAK,IAAI,IAAb,EAAmB;MACjB,MAAM,IAAIJ,KAAJ,CACH,gJAA+II,KAAM,iBAAgBD,GAAI,IADtK,CAAN;IAGD,CAJD,MAIO;MACLE,OAAO,CAACC,IAAR,CACG,qCAAoCH,GAAI,4GAA2GC,KAAM,iBAAgBD,GAAI,IADhL;IAGD;EACF;AACF;AAED,OAAO,SAASI,YAAT,CAAsBC,KAAtB,EAAuD;EAC5D,IAAI,CAACA,KAAL,EAAY;IACV,OAAO,IAAP;EACD;;EAED,MAAMC,GAAG,GAAG,IAAIT,KAAJ,CAAUQ,KAAK,CAACE,OAAhB,CAAZ;EACCD,GAAD,CAAaN,GAAb,GAAmBK,KAAK,CAACL,GAAzB;EACA,OAAOM,GAAP;AACD;AAED,OAAO,SAASE,aAAT,CACLC,IADK,EAE+B;EACpC,MAAMC,MAAM,GAAGC,WAAW,CAACF,IAAD,CAA1B;EACA,OAAOC,MAAM,GAAGA,MAAM,CAACE,GAAP,CAAYC,CAAD,IAAOT,YAAY,CAACS,CAAD,CAA9B,CAAH,GAAwC,IAArD;AACD;;AAED,SAASF,WAAT,CAAqBE,CAArB,EAAsE;EACpE,IAAInB,KAAK,CAACC,OAAN,CAAckB,CAAd,CAAJ,EAAsB;IACpB,OAAOA,CAAC,CAACjB,MAAF,KAAa,CAAb,GAAiB,IAAjB,GAAwBiB,CAA/B;EACD,CAFD,MAEO,IAAIA,CAAJ,EAAO;IACZ,OAAO,CAACA,CAAD,CAAP;EACD,CAFM,MAEA;IACL,OAAO,IAAP;EACD;AACF"}

@ -0,0 +1,34 @@
import AsyncStorage from './AsyncStorage';
export function useAsyncStorage(key) {
return {
getItem: function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return AsyncStorage.getItem(key, ...args);
},
setItem: function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return AsyncStorage.setItem(key, ...args);
},
mergeItem: function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return AsyncStorage.mergeItem(key, ...args);
},
removeItem: function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return AsyncStorage.removeItem(key, ...args);
}
};
}
//# sourceMappingURL=hooks.js.map

@ -0,0 +1 @@
{"version":3,"names":["AsyncStorage","useAsyncStorage","key","getItem","args","setItem","mergeItem","removeItem"],"sources":["hooks.ts"],"sourcesContent":["import AsyncStorage from './AsyncStorage';\nimport type { AsyncStorageHook } from './types';\n\nexport function useAsyncStorage(key: string): AsyncStorageHook {\n return {\n getItem: (...args) => AsyncStorage.getItem(key, ...args),\n setItem: (...args) => AsyncStorage.setItem(key, ...args),\n mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),\n removeItem: (...args) => AsyncStorage.removeItem(key, ...args),\n };\n}\n"],"mappings":"AAAA,OAAOA,YAAP,MAAyB,gBAAzB;AAGA,OAAO,SAASC,eAAT,CAAyBC,GAAzB,EAAwD;EAC7D,OAAO;IACLC,OAAO,EAAE;MAAA,kCAAIC,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaJ,YAAY,CAACG,OAAb,CAAqBD,GAArB,EAA0B,GAAGE,IAA7B,CAAb;IAAA,CADJ;IAELC,OAAO,EAAE;MAAA,mCAAID,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaJ,YAAY,CAACK,OAAb,CAAqBH,GAArB,EAA0B,GAAGE,IAA7B,CAAb;IAAA,CAFJ;IAGLE,SAAS,EAAE;MAAA,mCAAIF,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaJ,YAAY,CAACM,SAAb,CAAuBJ,GAAvB,EAA4B,GAAGE,IAA/B,CAAb;IAAA,CAHN;IAILG,UAAU,EAAE;MAAA,mCAAIH,IAAJ;QAAIA,IAAJ;MAAA;;MAAA,OAAaJ,YAAY,CAACO,UAAb,CAAwBL,GAAxB,EAA6B,GAAGE,IAAhC,CAAb;IAAA;EAJP,CAAP;AAMD"}

@ -0,0 +1,4 @@
import AsyncStorage from './AsyncStorage';
export { useAsyncStorage } from './hooks';
export default AsyncStorage;
//# sourceMappingURL=index.js.map

@ -0,0 +1 @@
{"version":3,"names":["AsyncStorage","useAsyncStorage"],"sources":["index.ts"],"sourcesContent":["import AsyncStorage from './AsyncStorage';\n\nexport { useAsyncStorage } from './hooks';\n\nexport type { AsyncStorageStatic } from './types';\n\nexport default AsyncStorage;\n"],"mappings":"AAAA,OAAOA,YAAP,MAAyB,gBAAzB;AAEA,SAASC,eAAT,QAAgC,SAAhC;AAIA,eAAeD,YAAf"}

@ -0,0 +1,31 @@
import { NativeModules } from 'react-native';
export function shouldFallbackToLegacyNativeModule() {
var _NativeModules$Native, _NativeModules$Native2;
const expoConstants = (_NativeModules$Native = NativeModules['NativeUnimoduleProxy']) === null || _NativeModules$Native === void 0 ? void 0 : (_NativeModules$Native2 = _NativeModules$Native.modulesConstants) === null || _NativeModules$Native2 === void 0 ? void 0 : _NativeModules$Native2.ExponentConstants;
if (expoConstants) {
/**
* In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.
* In bare React Native apps using expo-constants, appOwnership is never defined, so
* isLegacySdkVersion will be false in that context.
*/
const isLegacySdkVersion = expoConstants.appOwnership && !expoConstants.executionEnvironment;
/**
* Expo managed apps don't include the @react-native-async-storage/async-storage
* native modules yet, but the API interface is the same, so we can use the version
* exported from React Native still.
*
* If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this
* will likely not be valid anymore, and the package will need to be included in the Expo SDK
* to continue to work.
*/
if (isLegacySdkVersion || ['storeClient', 'standalone'].includes(expoConstants.executionEnvironment)) {
return true;
}
}
return false;
}
//# sourceMappingURL=shouldFallbackToLegacyNativeModule.js.map

@ -0,0 +1 @@
{"version":3,"names":["NativeModules","shouldFallbackToLegacyNativeModule","expoConstants","modulesConstants","ExponentConstants","isLegacySdkVersion","appOwnership","executionEnvironment","includes"],"sources":["shouldFallbackToLegacyNativeModule.ts"],"sourcesContent":["import { NativeModules } from 'react-native';\n\nexport function shouldFallbackToLegacyNativeModule(): boolean {\n const expoConstants =\n NativeModules['NativeUnimoduleProxy']?.modulesConstants?.ExponentConstants;\n\n if (expoConstants) {\n /**\n * In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.\n * In bare React Native apps using expo-constants, appOwnership is never defined, so\n * isLegacySdkVersion will be false in that context.\n */\n const isLegacySdkVersion =\n expoConstants.appOwnership && !expoConstants.executionEnvironment;\n\n /**\n * Expo managed apps don't include the @react-native-async-storage/async-storage\n * native modules yet, but the API interface is the same, so we can use the version\n * exported from React Native still.\n *\n * If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this\n * will likely not be valid anymore, and the package will need to be included in the Expo SDK\n * to continue to work.\n */\n if (\n isLegacySdkVersion ||\n ['storeClient', 'standalone'].includes(expoConstants.executionEnvironment)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":"AAAA,SAASA,aAAT,QAA8B,cAA9B;AAEA,OAAO,SAASC,kCAAT,GAAuD;EAAA;;EAC5D,MAAMC,aAAa,4BACjBF,aAAa,CAAC,sBAAD,CADI,oFACjB,sBAAuCG,gBADtB,2DACjB,uBAAyDC,iBAD3D;;EAGA,IAAIF,aAAJ,EAAmB;IACjB;AACJ;AACA;AACA;AACA;IACI,MAAMG,kBAAkB,GACtBH,aAAa,CAACI,YAAd,IAA8B,CAACJ,aAAa,CAACK,oBAD/C;IAGA;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;IACI,IACEF,kBAAkB,IAClB,CAAC,aAAD,EAAgB,YAAhB,EAA8BG,QAA9B,CAAuCN,aAAa,CAACK,oBAArD,CAFF,EAGE;MACA,OAAO,IAAP;IACD;EACF;;EAED,OAAO,KAAP;AACD"}

File diff suppressed because one or more lines are too long

@ -0,0 +1,10 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { AsyncStorageStatic } from './types';
declare const AsyncStorage: AsyncStorageStatic;
export default AsyncStorage;

@ -0,0 +1,16 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { AsyncStorageStatic } from './types';
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
declare const AsyncStorage: AsyncStorageStatic;
export default AsyncStorage;

@ -0,0 +1,2 @@
declare let RCTAsyncStorage: any;
export default RCTAsyncStorage;

@ -0,0 +1,5 @@
import type { ErrorLike } from './types';
export declare function checkValidArgs(keyValuePairs: unknown[], callback: unknown): void;
export declare function checkValidInput(...input: unknown[]): void;
export declare function convertError(error?: ErrorLike): Error | null;
export declare function convertErrors(errs?: ErrorLike[]): ReadonlyArray<Error | null> | null;

@ -0,0 +1,2 @@
import type { AsyncStorageHook } from './types';
export declare function useAsyncStorage(key: string): AsyncStorageHook;

@ -0,0 +1,4 @@
import AsyncStorage from './AsyncStorage';
export { useAsyncStorage } from './hooks';
export type { AsyncStorageStatic } from './types';
export default AsyncStorage;

@ -0,0 +1 @@
export declare function shouldFallbackToLegacyNativeModule(): boolean;

@ -0,0 +1,113 @@
export declare type ErrorLike = {
message: string;
key: string;
};
export declare type Callback = (error?: Error | null) => void;
export declare type CallbackWithResult<T> = (error?: Error | null, result?: T | null) => void;
export declare type KeyValuePair = [string, string | null];
export declare type MultiCallback = (errors?: readonly (Error | null)[] | null) => void;
export declare type MultiGetCallback = (errors?: readonly (Error | null)[] | null, result?: readonly KeyValuePair[]) => void;
export declare type MultiRequest = {
keys: readonly string[];
callback?: MultiGetCallback;
keyIndex: number;
resolve?: (result: readonly KeyValuePair[]) => void;
reject?: (error?: any) => void;
};
export declare type AsyncStorageHook = {
getItem: (callback?: CallbackWithResult<string>) => Promise<string | null>;
setItem: (value: string, callback?: Callback) => Promise<void>;
mergeItem: (value: string, callback?: Callback) => Promise<void>;
removeItem: (callback?: Callback) => Promise<void>;
};
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
export declare type AsyncStorageStatic = {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key: string, callback?: CallbackWithResult<string>) => Promise<string | null>;
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key: string, callback?: Callback) => Promise<void>;
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: (callback?: Callback) => Promise<void>;
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: (callback?: CallbackWithResult<readonly string[]>) => Promise<readonly string[]>;
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => void;
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys: readonly string[], callback?: MultiGetCallback) => Promise<readonly KeyValuePair[]>;
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs: [string, string][], callback?: MultiCallback) => Promise<void>;
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys: readonly string[], callback?: MultiCallback) => Promise<void>;
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs: [string, string][], callback?: MultiCallback) => Promise<void>;
};

@ -0,0 +1,385 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
3893A2E123C509D1009200E3 /* RNCAsyncStorage.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
3893A2E223C509D1009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
3893A2E523C50AFE009200E3 /* RNCAsyncStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
3893A2E623C50AFE009200E3 /* RNCAsyncStorageDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
3893A2E823C50AFE009200E3 /* RNCAsyncStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */; };
3893A2EB23C50AFE009200E3 /* RNCAsyncStorage.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
3893A2EC23C50AFE009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
B3E7B58A1CC2AC0600A0062D /* RNCAsyncStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
3893A2EA23C50AFE009200E3 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = include/RNCAsyncStorage;
dstSubfolderSpec = 16;
files = (
3893A2EB23C50AFE009200E3 /* RNCAsyncStorage.h in CopyFiles */,
3893A2EC23C50AFE009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
3893A2E123C509D1009200E3 /* RNCAsyncStorage.h in CopyFiles */,
3893A2E223C509D1009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCAsyncStorage.a; sourceTree = BUILT_PRODUCTS_DIR; };
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCAsyncStorageDelegate.h; path = ../ios/RNCAsyncStorageDelegate.h; sourceTree = "<group>"; };
3893A2F023C50AFE009200E3 /* libRNCAsyncStorage-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNCAsyncStorage-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCAsyncStorage.h; path = ../ios/RNCAsyncStorage.h; sourceTree = "<group>"; };
B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCAsyncStorage.m; path = ../ios/RNCAsyncStorage.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
3893A2E923C50AFE009200E3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */,
B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */,
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */,
134814211AA4EA7D00B7C361 /* Products */,
3893A2F023C50AFE009200E3 /* libRNCAsyncStorage-macOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
19F94B1D2239A948006921A9 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */,
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
3893A2E423C50AFE009200E3 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
3893A2E523C50AFE009200E3 /* RNCAsyncStorage.h in Headers */,
3893A2E623C50AFE009200E3 /* RNCAsyncStorageDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
3893A2E323C50AFE009200E3 /* RNCAsyncStorage-macOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3893A2ED23C50AFE009200E3 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage-macOS" */;
buildPhases = (
3893A2E423C50AFE009200E3 /* Headers */,
3893A2E723C50AFE009200E3 /* Sources */,
3893A2E923C50AFE009200E3 /* Frameworks */,
3893A2EA23C50AFE009200E3 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNCAsyncStorage-macOS";
productName = RCTDataManager;
productReference = 3893A2F023C50AFE009200E3 /* libRNCAsyncStorage-macOS.a */;
productType = "com.apple.product-type.library.static";
};
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */;
buildPhases = (
19F94B1D2239A948006921A9 /* Headers */,
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNCAsyncStorage;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */,
3893A2E323C50AFE009200E3 /* RNCAsyncStorage-macOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
3893A2E723C50AFE009200E3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3893A2E823C50AFE009200E3 /* RNCAsyncStorage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B3E7B58A1CC2AC0600A0062D /* RNCAsyncStorage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
3893A2EE23C50AFE009200E3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Debug;
};
3893A2EF23C50AFE009200E3 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Release;
};
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
3893A2ED23C50AFE009200E3 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage-macOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3893A2EE23C50AFE009200E3 /* Debug */,
3893A2EF23C50AFE009200E3 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3893A2E323C50AFE009200E3"
BuildableName = "libRNCAsyncStorage-macOS.a"
BlueprintName = "RNCAsyncStorage-macOS"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3893A2E323C50AFE009200E3"
BuildableName = "libRNCAsyncStorage-macOS.a"
BlueprintName = "RNCAsyncStorage-macOS"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
BuildableName = "libRNCAsyncStorage.a"
BlueprintName = "RNCAsyncStorage"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
BuildableName = "libRNCAsyncStorage.a"
BlueprintName = "RNCAsyncStorage"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -0,0 +1,195 @@
{
"name": "@react-native-async-storage/async-storage",
"version": "1.17.12",
"description": "Asynchronous, persistent, key-value storage system for React Native.",
"main": "lib/commonjs/index.js",
"module": "lib/module/index.js",
"react-native": "src/index.ts",
"types": "lib/typescript/index.d.ts",
"files": [
"RNCAsyncStorage.podspec",
"android/build.gradle",
"android/src",
"android/testresults.gradle",
"ios/",
"jest/",
"lib/",
"macos/",
"src/",
"windows/"
],
"author": "Krzysztof Borowy <hello@krizzu.dev>",
"contributors": [
"Evan Bacon <bacon@expo.io> (https://github.com/evanbacon)",
"Tommy Nguyen <4123478+tido64@users.noreply.github.com> (https://github.com/tido64)"
],
"homepage": "https://github.com/react-native-async-storage/async-storage#readme",
"license": "MIT",
"keywords": [
"react-native",
"react native",
"async storage",
"asyncstorage",
"storage"
],
"repository": {
"type": "git",
"url": "https://github.com/react-native-async-storage/async-storage.git"
},
"scripts": {
"format": "concurrently yarn:format:*",
"format:c": "clang-format -i $(git ls-files '*.cpp' '*.h' '*.m' '*.mm')",
"format:js": "prettier --write $(git ls-files '*.js' '*.json' '*.md' '*.ts' '*.tsx' '*.yml')",
"prepare": "bob build",
"start": "react-native start",
"start:android": "react-native run-android",
"start:ios": "react-native run-ios --project-path example/ios",
"start:macos": "react-native run-macos --project-path example/macos --scheme AsyncStorageExample",
"start:web": "expo start:web",
"start:windows": "install-windows-test-app -p example/windows && react-native run-windows --root example --logging --no-packager --no-telemetry",
"build:e2e:android": "scripts/android_e2e.sh 'build'",
"build:e2e:ios": "scripts/ios_e2e.sh 'build'",
"build:e2e:macos": "scripts/macos_e2e.sh 'build'",
"bundle:android": "scripts/android_e2e.sh 'bundle'",
"bundle:ios": "scripts/ios_e2e.sh 'bundle'",
"bundle:macos": "react-native bundle --entry-file index.ts --platform macos --bundle-output example/index.macos.jsbundle",
"test": "concurrently -n lint,ts yarn:test:lint yarn:test:ts",
"test:lint": "eslint src/**/*.ts example/**/*.ts jest/*.js",
"test:ts": "tsc --project tsconfig.all.json",
"test:e2e:android": "detox test -c android.emu.release --maxConcurrency 1",
"test:e2e:ios": "detox test -c ios.sim.release --maxConcurrency 1",
"test:e2e:macos": "scripts/macos_e2e.sh 'test'"
},
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || 0.60 - 0.71 || 1000.0.0"
},
"devDependencies": {
"@babel/core": "^7.12.0",
"@babel/preset-env": "^7.1.6",
"@react-native-community/eslint-config": "^3.0.0",
"@semantic-release/changelog": "^6.0.0",
"@semantic-release/git": "^10.0.0",
"@types/lodash": "^4.14.184",
"@types/react": "^17.0.0",
"@types/react-native": "^0.68.0",
"concurrently": "^6.4.0",
"detox": "^19.4.5",
"eslint": "^8.0.0",
"expo": "^45.0.0",
"jest": "^26.6.3",
"jest-circus": "^26.6.1",
"lodash": "^4.17.21",
"prettier": "^2.5.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-native": "^0.68.0",
"react-native-builder-bob": "^0.18.0",
"react-native-macos": "^0.68.0",
"react-native-test-app": "^2.3.10",
"react-native-web": "^0.17.0",
"react-native-windows": "^0.68.0",
"react-test-renderer": "17.0.2",
"semantic-release": "^19.0.0",
"typescript": "^4.5.0"
},
"packageManager": "yarn@3.4.1",
"resolutions": {
"npm/chalk": "^4.1.2"
},
"jest": {
"preset": "react-native",
"setupFiles": [
"./example/jest.setup.js"
]
},
"detox": {
"test-runner": "jest",
"runner-config": "example/e2e/config.json",
"configurations": {
"ios.sim.release": {
"binaryPath": "example/ios/build/Build/Products/Release-iphonesimulator/ReactTestApp.app",
"type": "ios.simulator",
"device": {
"type": "iPhone 13"
}
},
"android.emu.release": {
"binaryPath": "example/android/app/build/outputs/apk/release/app-release.apk",
"testBinaryPath": "example/android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk",
"type": "android.emulator",
"device": {
"avdName": "E2E_API_30",
"utilBinaryPaths": [
"/var/tmp/test-butler.apk"
]
}
},
"android.emu.release.next": {
"binaryPath": "example/android/app/build/outputs/apk/next/app-next.apk",
"testBinaryPath": "example/android/app/build/outputs/apk/androidTest/release/app-release-androidTest.apk",
"type": "android.emulator",
"device": {
"avdName": "E2E_API_30",
"utilBinaryPaths": [
"/var/tmp/test-butler.apk"
]
}
}
}
},
"eslintConfig": {
"extends": [
"@react-native-community",
"plugin:jest/recommended"
],
"rules": {
"dot-notation": "off"
}
},
"prettier": {
"endOfLine": "auto",
"singleQuote": true,
"overrides": [
{
"files": "*.md",
"options": {
"proseWrap": "always"
}
}
]
},
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
"typescript"
]
},
"release": {
"branches": [
"master"
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"@semantic-release/npm",
"@semantic-release/github",
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md",
"package.json"
],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}
]
]
}
}

@ -0,0 +1,356 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
checkValidArgs,
checkValidInput,
convertError,
convertErrors,
} from './helpers';
import RCTAsyncStorage from './RCTAsyncStorage';
import type {
AsyncStorageStatic,
ErrorLike,
KeyValuePair,
MultiRequest,
} from './types';
if (!RCTAsyncStorage) {
throw new Error(`[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
Rebuild and restart the app.
Run the packager with \`--reset-cache\` flag.
If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory and then rebuild and re-run the app.
If this happens while testing with Jest, check out docs how to integrate AsyncStorage with it: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the Github repository: https://github.com/react-native-async-storage/async-storage/issues
`);
}
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
const AsyncStorage = ((): AsyncStorageStatic => {
let _getRequests: MultiRequest[] = [];
let _getKeys: string[] = [];
let _immediate: ReturnType<typeof setImmediate> | null = null;
return {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiGet(
[key],
(errors?: ErrorLike[], result?: string[][]) => {
// Unpack result to get value from [[key,value]]
const value = result?.[0]?.[1] ? result[0][1] : null;
const errs = convertErrors(errors);
callback?.(errs?.[0], value);
if (errs) {
reject(errs[0]);
} else {
resolve(value);
}
}
);
});
},
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiSet([[key, value]], (errors?: ErrorLike[]) => {
const errs = convertErrors(errors);
callback?.(errs?.[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiRemove([key], (errors?: ErrorLike[]) => {
const errs = convertErrors(errors);
callback?.(errs?.[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiMerge([[key, value]], (errors?: ErrorLike[]) => {
const errs = convertErrors(errors);
callback?.(errs?.[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: (callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.clear((error?: ErrorLike) => {
const err = convertError(error);
callback?.(err);
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: (callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.getAllKeys((error?: ErrorLike, keys?: string[]) => {
const err = convertError(error);
callback?.(err, keys);
if (keys) {
resolve(keys);
} else {
reject(err);
}
});
});
},
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => {
const getRequests = _getRequests;
const getKeys = _getKeys;
_getRequests = [];
_getKeys = [];
RCTAsyncStorage.multiGet(
getKeys,
(errors?: ErrorLike[], result?: string[][]) => {
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
// it's much, much faster in practice for the data sets we deal with (we avoid
// allocating result pair arrays). This was heavily benchmarked.
//
// Is there a way to avoid using the map but fix the bug in this breaking test?
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
const map: Record<string, string> = {};
result?.forEach(([key, value]) => {
map[key] = value;
return value;
});
const reqLength = getRequests.length;
/**
* As mentioned few lines above, this method could be called with the array of potential error,
* in case of anything goes wrong. The problem is, if any of the batched calls fails
* the rest of them would fail too, but the error would be consumed by just one. The rest
* would simply return `undefined` as their result, rendering false negatives.
*
* In order to avoid this situation, in case of any call failing,
* the rest of them will be rejected as well (with the same error).
*/
const errorList = convertErrors(errors);
const error = errorList?.length ? errorList[0] : null;
for (let i = 0; i < reqLength; i++) {
const request = getRequests[i];
if (error) {
request.callback?.(errorList);
request.reject?.(error);
continue;
}
const requestResult = request.keys.map<KeyValuePair>((key) => [
key,
map[key],
]);
request.callback?.(null, requestResult);
request.resolve?.(requestResult);
}
}
);
},
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys, callback) => {
if (!_immediate) {
_immediate = setImmediate(() => {
_immediate = null;
AsyncStorage.flushGetRequests();
});
}
const getRequest: MultiRequest = {
keys: keys,
callback: callback,
// do we need this?
keyIndex: _getKeys.length,
resolve: null as any,
reject: null as any,
};
const promiseResult = new Promise<readonly KeyValuePair[]>(
(resolve, reject) => {
getRequest.resolve = resolve;
getRequest.reject = reject;
}
);
_getRequests.push(getRequest);
// avoid fetching duplicates
keys.forEach((key) => {
if (_getKeys.indexOf(key) === -1) {
_getKeys.push(key);
}
});
return promiseResult;
},
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs, callback) => {
checkValidArgs(keyValuePairs, callback);
return new Promise((resolve, reject) => {
keyValuePairs.forEach(([key, value]) => {
checkValidInput(key, value);
});
RCTAsyncStorage.multiSet(keyValuePairs, (errors?: ErrorLike[]) => {
const error = convertErrors(errors);
callback?.(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys, callback) => {
return new Promise((resolve, reject) => {
keys.forEach((key) => checkValidInput(key));
RCTAsyncStorage.multiRemove(keys, (errors?: ErrorLike[]) => {
const error = convertErrors(errors);
callback?.(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs, callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge(keyValuePairs, (errors?: ErrorLike[]) => {
const error = convertErrors(errors);
callback?.(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
};
})();
export default AsyncStorage;

@ -0,0 +1,173 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// @ts-ignore Cannot find module 'merge-options' or its corresponding type declarations
import mergeOptions from 'merge-options';
import type {
AsyncStorageStatic,
MultiCallback,
MultiGetCallback,
} from './types';
const merge = mergeOptions.bind({
concatArrays: true,
ignoreUndefined: true,
});
function mergeLocalStorageItem(key: string, value: string) {
const oldValue = window.localStorage.getItem(key);
if (oldValue) {
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify(merge(oldObject, newObject));
window.localStorage.setItem(key, nextValue);
} else {
window.localStorage.setItem(key, value);
}
}
function createPromise<Result, Callback extends Function>(
getValue: () => Result,
callback?: Callback
): Promise<Result> {
return new Promise((resolve, reject) => {
try {
const value = getValue();
callback?.(null, value);
resolve(value);
} catch (err) {
callback?.(err);
reject(err);
}
});
}
function createPromiseAll<ReturnType, Result, ResultProcessor extends Function>(
promises: Promise<Result>[],
callback?: MultiCallback | MultiGetCallback,
processResult?: ResultProcessor
): Promise<ReturnType> {
return Promise.all(promises).then(
(result) => {
const value = processResult?.(result) ?? null;
callback?.(null, value);
return Promise.resolve(value);
},
(errors) => {
callback?.(errors);
return Promise.reject(errors);
}
);
}
const AsyncStorage: AsyncStorageStatic = {
/**
* Fetches `key` value.
*/
getItem: (key, callback) => {
return createPromise(() => window.localStorage.getItem(key), callback);
},
/**
* Sets `value` for `key`.
*/
setItem: (key, value, callback) => {
return createPromise(
() => window.localStorage.setItem(key, value),
callback
);
},
/**
* Removes a `key`
*/
removeItem: (key, callback) => {
return createPromise(() => window.localStorage.removeItem(key), callback);
},
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
mergeItem: (key, value, callback) => {
return createPromise(() => mergeLocalStorageItem(key, value), callback);
},
/**
* Erases *all* AsyncStorage for the domain.
*/
clear: (callback) => {
return createPromise(() => window.localStorage.clear(), callback);
},
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
getAllKeys: (callback) => {
return createPromise(() => {
const numberOfKeys = window.localStorage.length;
const keys: string[] = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i) || '';
keys.push(key);
}
return keys;
}, callback);
},
/**
* (stub) Flushes any pending requests using a single batch call to get the data.
*/
flushGetRequests: () => undefined,
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
multiGet: (keys, callback) => {
const promises = keys.map((key) => AsyncStorage.getItem(key));
const processResult = (result: string[]) =>
result.map((value, i) => [keys[i], value]);
return createPromiseAll(promises, callback, processResult);
},
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
multiSet: (keyValuePairs, callback) => {
const promises = keyValuePairs.map((item) =>
AsyncStorage.setItem(item[0], item[1])
);
return createPromiseAll(promises, callback);
},
/**
* Delete all the keys in the `keys` array.
*/
multiRemove: (keys, callback) => {
const promises = keys.map((key) => AsyncStorage.removeItem(key));
return createPromiseAll(promises, callback);
},
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
multiMerge: (keyValuePairs, callback) => {
const promises = keyValuePairs.map((item) =>
AsyncStorage.mergeItem(item[0], item[1])
);
return createPromiseAll(promises, callback);
},
};
export default AsyncStorage;

@ -0,0 +1,25 @@
// @ts-ignore Module '"react-native"' has no exported member 'TurboModuleRegistry'.
import { NativeModules, TurboModuleRegistry } from 'react-native';
import { shouldFallbackToLegacyNativeModule } from './shouldFallbackToLegacyNativeModule';
let RCTAsyncStorage =
NativeModules['PlatformLocalStorage'] || // Support for external modules, like react-native-windows
NativeModules['RNC_AsyncSQLiteDBStorage'] ||
NativeModules['RNCAsyncStorage'];
if (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {
// TurboModuleRegistry falls back to NativeModules so we don't have to try go
// assign NativeModules' counterparts if TurboModuleRegistry would resolve
// with undefined.
if (TurboModuleRegistry) {
RCTAsyncStorage =
TurboModuleRegistry.get('AsyncSQLiteDBStorage') ||
TurboModuleRegistry.get('AsyncLocalStorage');
} else {
RCTAsyncStorage =
NativeModules['AsyncSQLiteDBStorage'] ||
NativeModules['AsyncLocalStorage'];
}
}
export default RCTAsyncStorage;

@ -0,0 +1,74 @@
import type { ErrorLike } from './types';
export function checkValidArgs(keyValuePairs: unknown[], callback: unknown) {
if (
!Array.isArray(keyValuePairs) ||
keyValuePairs.length === 0 ||
!Array.isArray(keyValuePairs[0])
) {
throw new Error(
'[AsyncStorage] Expected array of key-value pairs as first argument to multiSet'
);
}
if (callback && typeof callback !== 'function') {
if (Array.isArray(callback)) {
throw new Error(
'[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?'
);
}
throw new Error(
'[AsyncStorage] Expected function as second argument to multiSet'
);
}
}
export function checkValidInput(...input: unknown[]) {
const [key, value] = input;
if (typeof key !== 'string') {
console.warn(
`[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${key}\n`
);
}
if (input.length > 1 && typeof value !== 'string') {
if (value == null) {
throw new Error(
`[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\nPassed value: ${value}\nPassed key: ${key}\n`
);
} else {
console.warn(
`[AsyncStorage] The value for key "${key}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${key}\n`
);
}
}
}
export function convertError(error?: ErrorLike): Error | null {
if (!error) {
return null;
}
const out = new Error(error.message);
(out as any).key = error.key;
return out;
}
export function convertErrors(
errs?: ErrorLike[]
): ReadonlyArray<Error | null> | null {
const errors = ensureArray(errs);
return errors ? errors.map((e) => convertError(e)) : null;
}
function ensureArray(e?: ErrorLike | ErrorLike[]): ErrorLike[] | null {
if (Array.isArray(e)) {
return e.length === 0 ? null : e;
} else if (e) {
return [e];
} else {
return null;
}
}

@ -0,0 +1,11 @@
import AsyncStorage from './AsyncStorage';
import type { AsyncStorageHook } from './types';
export function useAsyncStorage(key: string): AsyncStorageHook {
return {
getItem: (...args) => AsyncStorage.getItem(key, ...args),
setItem: (...args) => AsyncStorage.setItem(key, ...args),
mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),
removeItem: (...args) => AsyncStorage.removeItem(key, ...args),
};
}

@ -0,0 +1,7 @@
import AsyncStorage from './AsyncStorage';
export { useAsyncStorage } from './hooks';
export type { AsyncStorageStatic } from './types';
export default AsyncStorage;

@ -0,0 +1,34 @@
import { NativeModules } from 'react-native';
export function shouldFallbackToLegacyNativeModule(): boolean {
const expoConstants =
NativeModules['NativeUnimoduleProxy']?.modulesConstants?.ExponentConstants;
if (expoConstants) {
/**
* In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.
* In bare React Native apps using expo-constants, appOwnership is never defined, so
* isLegacySdkVersion will be false in that context.
*/
const isLegacySdkVersion =
expoConstants.appOwnership && !expoConstants.executionEnvironment;
/**
* Expo managed apps don't include the @react-native-async-storage/async-storage
* native modules yet, but the API interface is the same, so we can use the version
* exported from React Native still.
*
* If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this
* will likely not be valid anymore, and the package will need to be included in the Expo SDK
* to continue to work.
*/
if (
isLegacySdkVersion ||
['storeClient', 'standalone'].includes(expoConstants.executionEnvironment)
) {
return true;
}
}
return false;
}

@ -0,0 +1,155 @@
export type ErrorLike = {
message: string;
key: string;
};
export type Callback = (error?: Error | null) => void;
export type CallbackWithResult<T> = (
error?: Error | null,
result?: T | null
) => void;
export type KeyValuePair = [string, string | null];
export type MultiCallback = (errors?: readonly (Error | null)[] | null) => void;
export type MultiGetCallback = (
errors?: readonly (Error | null)[] | null,
result?: readonly KeyValuePair[]
) => void;
export type MultiRequest = {
keys: readonly string[];
callback?: MultiGetCallback;
keyIndex: number;
resolve?: (result: readonly KeyValuePair[]) => void;
reject?: (error?: any) => void;
};
export type AsyncStorageHook = {
getItem: (callback?: CallbackWithResult<string>) => Promise<string | null>;
setItem: (value: string, callback?: Callback) => Promise<void>;
mergeItem: (value: string, callback?: Callback) => Promise<void>;
removeItem: (callback?: Callback) => Promise<void>;
};
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
export type AsyncStorageStatic = {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (
key: string,
callback?: CallbackWithResult<string>
) => Promise<string | null>;
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key: string, callback?: Callback) => Promise<void>;
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: (callback?: Callback) => Promise<void>;
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: (
callback?: CallbackWithResult<readonly string[]>
) => Promise<readonly string[]>;
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => void;
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (
keys: readonly string[],
callback?: MultiGetCallback
) => Promise<readonly KeyValuePair[]>;
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (
keyValuePairs: [string, string][],
callback?: MultiCallback
) => Promise<void>;
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (
keys: readonly string[],
callback?: MultiCallback
) => Promise<void>;
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (
keyValuePairs: [string, string][],
callback?: MultiCallback
) => Promise<void>;
};

@ -0,0 +1,172 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactNativeAsyncStorage", "ReactNativeAsyncStorage\ReactNativeAsyncStorage.vcxproj", "{4855D892-E16C-404D-8286-0089E0F7F9C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{4F6E56C3-12C5-4457-9239-0ACF0B7150A8}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9
..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.ActiveCfg = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.Build.0 = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM64.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.ActiveCfg = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.Build.0 = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.Build.0 = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.ActiveCfg = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.Build.0 = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM64.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.ActiveCfg = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.Build.0 = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.Build.0 = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{0CC28589-39E4-4288-B162-97B959F8B843} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F02BFA9-97C8-4ACF-A348-B3166C3BC7EA}
EndGlobalSection
EndGlobal

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>

@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<MinimalCoreWin>true</MinimalCoreWin>
<ProjectGuid>{4855D892-E16C-404D-8286-0089E0F7F9C4}</ProjectGuid>
<ProjectName>ReactNativeAsyncStorage</ProjectName>
<RootNamespace>ReactNativeAsyncStorage</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.16299.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="ReactNativeWindowsProps">
<ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<ImportGroup Label="ReactNativeWindowsPropertySheets">
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props')" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
<!--Temporarily disable cppwinrt heap enforcement to work around xaml compiler generated std::shared_ptr use -->
<AdditionalOptions Condition="'$(CppWinRTHeapEnforcement)'==''">/DWINRT_NO_MAKE_DETECTION %(AdditionalOptions)</AdditionalOptions>
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>true</GenerateWindowsMetadata>
<ModuleDefinitionFile>..\code\ReactNativeAsyncStorage.def</ModuleDefinitionFile>
<AdditionalDependencies>winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winsqlite3.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\code\pch.h" />
<ClInclude Include="..\code\ReactPackageProvider.h">
<DependentUpon>..\code\ReactPackageProvider.idl</DependentUpon>
</ClInclude>
<ClInclude Include="..\code\DBStorage.h" />
<ClInclude Include="..\code\RNCAsyncStorage.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\code\pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="..\code\ReactPackageProvider.cpp">
<DependentUpon>..\code\ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="..\code\DBStorage.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\code\ReactPackageProvider.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="..\code\ReactNativeAsyncStorage.def" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ReactNativeWindowsDir)\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj">
<Project>{f7d32bd0-2749-483e-9a0d-1635ef7e3136}</Project>
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ReactNativeWindowsTargets">
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets')" />
</ImportGroup>
<Target Name="EnsureReactNativeWindowsTargets" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references targets in your node_modules\react-native-windows folder. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props')" Text="$([System.String]::Format('$(ErrorText)', '$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props'))" />
<Error Condition="!Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets'))" />
</Target>
<ImportGroup Label="ExtensionTargets">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="Deploy" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Resources">
<UniqueIdentifier>accd3aa8-1ba0-4223-9bbe-0c431709210b</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{926ab91d-31b4-48c3-b9a4-e681349f27f0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\code\pch.h" />
<ClInclude Include="..\code\ReactPackageProvider.h" />
<ClInclude Include="..\code\DBStorage.h" />
<ClInclude Include="..\code\RNCAsyncStorage.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\code\pch.cpp" />
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="..\code\ReactPackageProvider.cpp" />
<ClCompile Include="..\code\DBStorage.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\code\ReactNativeAsyncStorage.def" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\code\ReactPackageProvider.idl" />
</ItemGroup>
</Project>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.200316.3" targetFramework="native" />
</packages>

@ -0,0 +1,195 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29215.179
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}"
ProjectSection(ProjectDependencies) = postProject
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "..\node_modules\react-native-windows\ReactWindowsCore\ReactWindowsCore.vcxproj", "{11C084A3-A57C-4296-A679-CAC17B603144}"
ProjectSection(ProjectDependencies) = postProject
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {A990658C-CE31-4BCC-976F-0FC6B1AF693D}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}"
EndProject
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Microsoft.ReactNative.SharedManaged", "..\node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.shproj", "{67A1076F-7790-4203-86EA-4402CCB5E782}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactNativeAsyncStorage61", "ReactNativeAsyncStorage61\ReactNativeAsyncStorage61.vcxproj", "{4855D892-E16C-404D-8286-0089E0F7F9C4}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{4855d892-e16c-404d-8286-0089e0f7f9c4}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.SharedManaged\Microsoft.ReactNative.SharedManaged.projitems*{67a1076f-7790-4203-86ea-4402ccb5e782}*SharedItemsImports = 13
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.ActiveCfg = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.Build.0 = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM64.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.ActiveCfg = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.Build.0 = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.Build.0 = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.ActiveCfg = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.Build.0 = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM64.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.ActiveCfg = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.Build.0 = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.Build.0 = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM.ActiveCfg = Debug|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM.Build.0 = Debug|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM64.ActiveCfg = Debug|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM64.Build.0 = Debug|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x64.ActiveCfg = Debug|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x64.Build.0 = Debug|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x86.ActiveCfg = Debug|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x86.Build.0 = Debug|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM.ActiveCfg = Release|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM.Build.0 = Release|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM64.ActiveCfg = Release|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM64.Build.0 = Release|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x64.ActiveCfg = Release|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x64.Build.0 = Release|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x86.ActiveCfg = Release|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{0CC28589-39E4-4288-B162-97B959F8B843} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{67A1076F-7790-4203-86EA-4402CCB5E782} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
{11C084A3-A57C-4296-A679-CAC17B603144} = {5EA20F54-880A-49F3-99FA-4B3FE54E8AB1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F02BFA9-97C8-4ACF-A348-B3166C3BC7EA}
EndGlobalSection
EndGlobal

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<MinimalCoreWin>true</MinimalCoreWin>
<ProjectGuid>{4855D892-E16C-404D-8286-0089E0F7F9C4}</ProjectGuid>
<ProjectName>ReactNativeAsyncStorage61</ProjectName>
<RootNamespace>ReactNativeAsyncStorage</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0.18362.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.15063.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="ReactNativeWindowsProps">
<ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
<Import Project="$(ReactNativeWindowsDir)\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems" Label="Shared" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
<!--Temporarily disable cppwinrt heap enforcement to work around xaml compiler generated std::shared_ptr use -->
<AdditionalOptions Condition="'$(CppWinRTHeapEnforcement)'==''">/DWINRT_NO_MAKE_DETECTION %(AdditionalOptions)</AdditionalOptions>
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
<PreprocessorDefinitions>_WINRT_DLL;RNW_61;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>true</GenerateWindowsMetadata>
<ModuleDefinitionFile>..\code\ReactNativeAsyncStorage.def</ModuleDefinitionFile>
<AdditionalDependencies>winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winsqlite3.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\code\pch.h" />
<ClInclude Include="..\code\ReactPackageProvider.h">
<DependentUpon>..\code\ReactPackageProvider.idl</DependentUpon>
</ClInclude>
<ClInclude Include="..\code\DBStorage.h" />
<ClInclude Include="..\code\RNCAsyncStorage.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\code\pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="..\code\ReactPackageProvider.cpp">
<DependentUpon>..\code\ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="..\code\DBStorage.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\code\ReactPackageProvider.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="..\code\ReactNativeAsyncStorage.def" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ReactNativeWindowsDir)\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj">
<Project>{f7d32bd0-2749-483e-9a0d-1635ef7e3136}</Project>
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="Deploy"/>
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.190730.2\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Resources">
<UniqueIdentifier>accd3aa8-1ba0-4223-9bbe-0c431709210b</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{926ab91d-31b4-48c3-b9a4-e681349f27f0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\code\pch.h" />
<ClInclude Include="..\code\ReactPackageProvider.h" />
<ClInclude Include="..\code\RNCAsyncStorage.h" />
<ClInclude Include="..\code\DBStorage.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\code\pch.cpp" />
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="..\code\ReactPackageProvider.cpp" />
<ClCompile Include="..\code\DBStorage.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="..\code\ReactNativeAsyncStorage.def" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\code\ReactPackageProvider.idl" />
</ItemGroup>
</Project>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Microsoft.Windows.CppWinRT" version="2.0.190730.2" targetFramework="native" />
</packages>

@ -0,0 +1,192 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactNativeAsyncStorage", "ReactNativeAsyncStorage\ReactNativeAsyncStorage.vcxproj", "{4855D892-E16C-404D-8286-0089E0F7F9C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{4F6E56C3-12C5-4457-9239-0ACF0B7150A8}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactWindowsCore", "..\node_modules\react-native-windows\ReactWindowsCore\ReactWindowsCore.vcxproj", "{11C084A3-A57C-4296-A679-CAC17B603144}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9
..\node_modules\react-native-windows\ReactWindowsCore\ReactWindowsCore.vcxitems*{11c084a3-a57c-4296-a679-cac17b603144}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9
..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.ActiveCfg = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.Build.0 = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM64.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.ActiveCfg = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.Build.0 = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.Build.0 = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.ActiveCfg = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.Build.0 = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM64.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.ActiveCfg = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.Build.0 = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.Build.0 = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM.ActiveCfg = Debug|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM.Build.0 = Debug|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM64.ActiveCfg = Debug|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|ARM64.Build.0 = Debug|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x64.ActiveCfg = Debug|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x64.Build.0 = Debug|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x86.ActiveCfg = Debug|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Debug|x86.Build.0 = Debug|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM.ActiveCfg = Release|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM.Build.0 = Release|ARM
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM64.ActiveCfg = Release|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|ARM64.Build.0 = Release|ARM64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x64.ActiveCfg = Release|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x64.Build.0 = Release|x64
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x86.ActiveCfg = Release|Win32
{11C084A3-A57C-4296-A679-CAC17B603144}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{0CC28589-39E4-4288-B162-97B959F8B843} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{11C084A3-A57C-4296-A679-CAC17B603144} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F02BFA9-97C8-4ACF-A348-B3166C3BC7EA}
EndGlobalSection
EndGlobal

@ -0,0 +1,599 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "DBStorage.h"
#include <unordered_map>
namespace winrt
{
using namespace Microsoft::ReactNative;
using namespace Windows::ApplicationModel::Core;
using namespace Windows::Data::Json;
using namespace Windows::Foundation;
using namespace Windows::Storage;
} // namespace winrt
// All functions below return std::nullopt on error.
#define CHECK(expr) \
if (!(expr)) { \
return std::nullopt; \
}
// Convenience macro to call CheckSQLiteResult.
#define CHECK_SQL_OK(expr) CHECK(CheckSQLiteResult(db, m_errorManager, (expr)))
namespace
{
// To implement safe operator& for unique_ptr.
template <typename T, typename TDeleter>
struct UniquePtrSetter {
UniquePtrSetter(std::unique_ptr<T, TDeleter> &ptr) noexcept : m_ptr(ptr)
{
}
~UniquePtrSetter()
{
m_ptr = {m_rawPtr, m_ptr.get_deleter()};
}
operator T **() noexcept
{
return &m_rawPtr;
}
private:
T *m_rawPtr{};
std::unique_ptr<T, TDeleter> &m_ptr;
};
template <typename T, typename TDeleter>
UniquePtrSetter<T, TDeleter> operator&(std::unique_ptr<T, TDeleter> &ptr) noexcept
{
return UniquePtrSetter<T, TDeleter>(ptr);
}
using ExecCallback = int(SQLITE_CALLBACK *)(void *callbackData,
int columnCount,
char **columnTexts,
char **columnNames);
// Execute the provided SQLite statement (and optional execCallback & user data
// in callbackData). On error, report it to the errorManager and return std::nullopt.
std::optional<bool> Exec(sqlite3 *db,
DBStorage::ErrorManager &errorManager,
const char *statement,
ExecCallback execCallback = nullptr,
void *callbackData = nullptr) noexcept
{
auto errMsg = std::unique_ptr<char, decltype(&sqlite3_free)>{nullptr, &sqlite3_free};
int rc = sqlite3_exec(db, statement, execCallback, callbackData, &errMsg);
if (errMsg) {
return errorManager.AddError(errMsg.get());
}
if (rc != SQLITE_OK) {
return errorManager.AddError(sqlite3_errmsg(db));
}
return true;
}
// Convenience wrapper for using Exec with lambda expressions.
template <typename Fn>
std::optional<bool>
Exec(sqlite3 *db, DBStorage::ErrorManager &errorManager, const char *statement, Fn &fn) noexcept
{
return Exec(
db,
errorManager,
statement,
[](void *callbackData, int columnCount, char **columnTexts, char **columnNames) {
return (*static_cast<Fn *>(callbackData))(columnCount, columnTexts, columnNames);
},
&fn);
}
// Check that the args collection size is less than SQLITE_LIMIT_VARIABLE_NUMBER, and that
// every member of args is not an empty string.
// On error, report it to the errorManager and return std::nullopt.
std::optional<bool> CheckArgs(sqlite3 *db,
DBStorage::ErrorManager &errorManager,
const std::vector<std::string> &args) noexcept
{
int varLimit = sqlite3_limit(db, SQLITE_LIMIT_VARIABLE_NUMBER, -1);
auto argCount = args.size();
if (argCount > static_cast<size_t>(std::numeric_limits<int>::max()) ||
static_cast<int>(argCount) > varLimit) {
char errorMsg[60];
sprintf_s(errorMsg, "Too many keys. Maximum supported keys :%d.", varLimit);
return errorManager.AddError(errorMsg);
}
for (int i = 0; i < static_cast<int>(argCount); i++) {
if (args[i].empty()) {
return errorManager.AddError("The key must be a non-empty string.");
}
}
return true;
}
// RAII object to manage SQLite transaction. On destruction, if
// Commit() has not been called, rolls back the transactions.
// The provided SQLite connection handle & errorManager must outlive
// the Sqlite3Transaction object.
struct Sqlite3Transaction {
Sqlite3Transaction(sqlite3 *db, DBStorage::ErrorManager &errorManager) noexcept
: m_db(db), m_errorManager(errorManager)
{
if (!Exec(m_db, m_errorManager, "BEGIN TRANSACTION")) {
m_db = nullptr;
}
}
Sqlite3Transaction(const Sqlite3Transaction &) = delete;
Sqlite3Transaction &operator=(const Sqlite3Transaction &) = delete;
~Sqlite3Transaction()
{
Rollback();
}
explicit operator bool() const noexcept
{
return m_db != nullptr;
}
std::optional<bool> Commit() noexcept
{
if (m_db) {
return Exec(std::exchange(m_db, nullptr), m_errorManager, "COMMIT");
}
return std::nullopt;
}
std::optional<bool> Rollback() noexcept
{
if (m_db) {
return Exec(std::exchange(m_db, nullptr), m_errorManager, "ROLLBACK");
}
return std::nullopt;
}
private:
sqlite3 *m_db{};
DBStorage::ErrorManager &m_errorManager;
};
// Append argCount variables to prefix in a comma-separated list.
std::string MakeSQLiteParameterizedStatement(const char *prefix, int argCount) noexcept
{
assert(argCount != 0);
std::string result(prefix);
result.reserve(result.size() + (argCount * 2) + 1);
result += '(';
for (int x = 0; x < argCount - 1; x++) {
result += "?,";
}
result += "?)";
return result;
}
// Check if sqliteResult is SQLITE_OK.
// If not, report the error to the errorManager and return std::nullopt.
std::optional<bool> CheckSQLiteResult(sqlite3 *db,
DBStorage::ErrorManager &errorManager,
int sqliteResult) noexcept
{
if (sqliteResult == SQLITE_OK) {
return true;
} else {
return errorManager.AddError(sqlite3_errmsg(db));
}
}
using StatementPtr = std::unique_ptr<sqlite3_stmt, decltype(&sqlite3_finalize)>;
// A convenience wrapper for sqlite3_prepare_v2 function.
int PrepareStatement(sqlite3 *db,
const std::string &statementText,
sqlite3_stmt **statement) noexcept
{
return sqlite3_prepare_v2(db, statementText.c_str(), -1, statement, nullptr);
}
// A convenience wrapper for sqlite3_bind_text function.
int BindString(StatementPtr &statement, int index, const std::string &str) noexcept
{
return sqlite3_bind_text(statement.get(), index, str.c_str(), -1, SQLITE_TRANSIENT);
}
// Merge source into destination.
// It only merges objects - all other types are just clobbered (including arrays).
void MergeJsonObjects(winrt::JsonObject const &destination,
winrt::JsonObject const &source) noexcept
{
for (auto keyValue : source) {
auto key = keyValue.Key();
auto sourceValue = keyValue.Value();
if (destination.HasKey(key)) {
auto destinationValue = destination.GetNamedValue(key);
if (destinationValue.ValueType() == winrt::JsonValueType::Object &&
sourceValue.ValueType() == winrt::JsonValueType::Object) {
MergeJsonObjects(destinationValue.GetObject(), sourceValue.GetObject());
continue;
}
}
destination.SetNamedValue(key, sourceValue);
}
}
} // namespace
// Initialize storage. On error, report it to the errorManager and return std::nullopt.
std::optional<sqlite3 *>
DBStorage::InitializeStorage(DBStorage::ErrorManager &errorManager) noexcept
{
winrt::slim_lock_guard guard{m_lock};
if (m_db) {
return m_db.get();
}
std::string path;
try {
if (auto pathInspectable =
winrt::CoreApplication::Properties().TryLookup(s_dbPathProperty)) {
path = winrt::to_string(winrt::unbox_value<winrt::hstring>(pathInspectable));
} else {
auto const localAppDataPath = winrt::ApplicationData::Current().LocalFolder().Path();
path = winrt::to_string(localAppDataPath) + "\\AsyncStorage.db";
}
} catch (const winrt::hresult_error &error) {
errorManager.AddError(winrt::to_string(error.message()));
return errorManager.AddError(
"Please specify 'React-Native-Community-Async-Storage-Database-Path' in "
"CoreApplication::Properties");
}
auto db = DatabasePtr{nullptr, &sqlite3_close};
if (sqlite3_open_v2(path.c_str(),
&db,
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX,
nullptr) != SQLITE_OK) {
if (db) {
return errorManager.AddError(sqlite3_errmsg(db.get()));
} else {
return errorManager.AddError("Storage database cannot be opened.");
}
}
int userVersion = 0;
auto getUserVersionCallback =
[](void *callbackData, int colomnCount, char **columnTexts, char ** /*columnNames*/) {
if (colomnCount < 1) {
return 1;
}
*static_cast<int *>(callbackData) = atoi(columnTexts[0]);
return SQLITE_OK;
};
CHECK(
Exec(db.get(), errorManager, "PRAGMA user_version", getUserVersionCallback, &userVersion));
if (userVersion == 0) {
CHECK(Exec(db.get(),
errorManager,
"CREATE TABLE IF NOT EXISTS AsyncLocalStorage(key TEXT PRIMARY KEY, value TEXT "
"NOT NULL); PRAGMA user_version=1"));
}
m_db = std::move(db);
return m_db.get();
}
DBStorage::~DBStorage()
{
decltype(m_tasks) tasks;
{
// If there is an in-progress async task, cancel it and wait on the condition_variable for
// the async task to acknowledge cancellation by nulling out m_action. Once m_action is
// null, it is safe to proceed with closing the DB connection. The DB connection is closed
// by the m_db destructor.
winrt::slim_lock_guard guard{m_lock};
swap(tasks, m_tasks);
if (m_action) {
m_action.Cancel();
m_cv.wait(m_lock, [this]() { return m_action == nullptr; });
}
}
}
// Under the lock, add a task to m_tasks and, if no async task is in progress schedule it.
void DBStorage::AddTask(ErrorManager &errorManager,
std::function<void(DBStorage::DBTask &task, sqlite3 *db)> &&onRun) noexcept
{
winrt::slim_lock_guard guard(m_lock);
m_tasks.push_back(std::make_unique<DBTask>(errorManager, std::move(onRun)));
if (!m_action) {
m_action = RunTasks();
}
}
// On a background thread, while the async task has not been canceled and
// there are more tasks to do, run the tasks. When there are either no more
// tasks or cancellation has been requested, set m_action to null to report
// that and complete the coroutine. N.B., it is important that detecting that
// m_tasks is empty and acknowledging completion is done atomically; otherwise
// there would be a race between the background task detecting m_tasks.empty()
// and AddTask checking the coroutine is running.
winrt::Windows::Foundation::IAsyncAction DBStorage::RunTasks() noexcept
{
auto cancellationToken = co_await winrt::get_cancellation_token();
co_await winrt::resume_background();
for (;;) {
decltype(m_tasks) tasks;
sqlite3 *db{nullptr};
{
winrt::slim_lock_guard guard(m_lock);
if (m_tasks.empty()) {
m_action = nullptr;
m_cv.notify_all();
co_return;
}
std::swap(tasks, m_tasks);
db = m_db.get();
}
for (auto &task : tasks) {
if (!cancellationToken()) {
task->Run(*this, db);
} else {
task->Cancel();
}
}
}
}
// Add new Error to the error list.
// Return std::nullopt for convenience to other methods that use std::nullopt to indicate error
// result.
std::nullopt_t DBStorage::ErrorManager::AddError(std::string &&message) noexcept
{
m_errors.push_back(Error{std::move(message)});
return std::nullopt;
}
bool DBStorage::ErrorManager::HasErrors() const noexcept
{
return !m_errors.empty();
}
const std::vector<DBStorage::Error> &DBStorage::ErrorManager::GetErrorList() const noexcept
{
if (HasErrors()) {
return m_errors;
}
static std::vector<DBStorage::Error> s_unknownError{Error{"Unknown error."}};
return s_unknownError;
}
DBStorage::Error DBStorage::ErrorManager::GetCombinedError() const noexcept
{
auto &errors = GetErrorList();
if (errors.size() == 1) {
return errors[0];
}
std::string combinedMessage;
for (const auto &error : errors) {
combinedMessage += error.Message + '\n';
}
return Error{std::move(combinedMessage)};
}
DBStorage::DBTask::DBTask(DBStorage::ErrorManager &errorManager,
std::function<void(DBTask &task, sqlite3 *db)> &&onRun) noexcept
: m_errorManager(errorManager), m_onRun(std::move(onRun))
{
}
void DBStorage::DBTask::Run(DBStorage &storage, sqlite3 *db) noexcept
{
if (!db) {
// We initialize DB handler on demand to report errors in the task context.
if (auto res = storage.InitializeStorage(m_errorManager)) {
db = *res;
}
}
if (db) {
m_onRun(*this, db);
}
}
void DBStorage::DBTask::Cancel() noexcept
{
m_errorManager.AddError("Task is canceled.");
}
std::optional<std::vector<DBStorage::KeyValue>>
DBStorage::DBTask::MultiGet(sqlite3 *db, const std::vector<std::string> &keys) noexcept
{
CHECK(!m_errorManager.HasErrors());
CHECK(CheckArgs(db, m_errorManager, keys));
auto argCount = static_cast<int>(keys.size());
auto sql = MakeSQLiteParameterizedStatement(
"SELECT key, value FROM AsyncLocalStorage WHERE key IN ", argCount);
auto statement = StatementPtr{nullptr, &sqlite3_finalize};
CHECK_SQL_OK(PrepareStatement(db, sql, &statement));
for (int i = 0; i < argCount; i++) {
CHECK_SQL_OK(BindString(statement, i + 1, keys[i]));
}
std::vector<DBStorage::KeyValue> result;
for (;;) {
auto stepResult = sqlite3_step(statement.get());
if (stepResult == SQLITE_DONE) {
break;
}
if (stepResult != SQLITE_ROW) {
return m_errorManager.AddError(sqlite3_errmsg(db));
}
auto key = reinterpret_cast<const char *>(sqlite3_column_text(statement.get(), 0));
if (!key) {
return m_errorManager.AddError(sqlite3_errmsg(db));
}
auto value = reinterpret_cast<const char *>(sqlite3_column_text(statement.get(), 1));
if (!value) {
return m_errorManager.AddError(sqlite3_errmsg(db));
}
result.push_back(KeyValue{key, value});
}
return result;
}
std::optional<bool> DBStorage::DBTask::MultiSet(sqlite3 *db,
const std::vector<KeyValue> &keyValues) noexcept
{
CHECK(!m_errorManager.HasErrors());
if (keyValues.empty()) {
return true; // nothing to do
}
Sqlite3Transaction transaction(db, m_errorManager);
CHECK(transaction);
auto statement = StatementPtr{nullptr, &sqlite3_finalize};
CHECK_SQL_OK(
PrepareStatement(db, "INSERT OR REPLACE INTO AsyncLocalStorage VALUES(?, ?)", &statement));
for (const auto &keyValue : keyValues) {
CHECK_SQL_OK(BindString(statement, 1, keyValue.Key));
CHECK_SQL_OK(BindString(statement, 2, keyValue.Value));
auto rc = sqlite3_step(statement.get());
CHECK(rc == SQLITE_DONE || CheckSQLiteResult(db, m_errorManager, rc));
CHECK_SQL_OK(sqlite3_reset(statement.get()));
}
CHECK(transaction.Commit());
return true;
}
std::optional<bool> DBStorage::DBTask::MultiMerge(sqlite3 *db,
const std::vector<KeyValue> &keyValues) noexcept
{
CHECK(!m_errorManager.HasErrors());
std::vector<std::string> keys;
std::unordered_map<std::string, std::string> newValues;
keys.reserve(keyValues.size());
for (const auto &keyValue : keyValues) {
keys.push_back(keyValue.Key);
newValues.try_emplace(keyValue.Key, keyValue.Value);
}
auto oldValues = MultiGet(db, keys);
CHECK(oldValues);
std::vector<KeyValue> mergedResults;
for (size_t i = 0; i < oldValues->size(); i++) {
auto &key = oldValues->at(i).Key;
auto &oldValue = oldValues->at(i).Value;
auto &newValue = newValues[key];
winrt::JsonObject oldJson;
winrt::JsonObject newJson;
if (winrt::JsonObject::TryParse(winrt::to_hstring(oldValue), oldJson) &&
winrt::JsonObject::TryParse(winrt::to_hstring(newValue), newJson)) {
MergeJsonObjects(oldJson, newJson);
mergedResults.push_back(KeyValue{key, winrt::to_string(oldJson.ToString())});
} else {
return m_errorManager.AddError("Values must be valid JSON object strings");
}
}
return MultiSet(db, mergedResults);
}
std::optional<bool> DBStorage::DBTask::MultiRemove(sqlite3 *db,
const std::vector<std::string> &keys) noexcept
{
CHECK(!m_errorManager.HasErrors());
CHECK(CheckArgs(db, m_errorManager, keys));
auto argCount = static_cast<int>(keys.size());
auto sql =
MakeSQLiteParameterizedStatement("DELETE FROM AsyncLocalStorage WHERE key IN ", argCount);
auto statement = StatementPtr{nullptr, &sqlite3_finalize};
CHECK_SQL_OK(PrepareStatement(db, sql, &statement));
for (int i = 0; i < argCount; i++) {
CHECK_SQL_OK(BindString(statement, i + 1, keys[i]));
}
for (;;) {
auto stepResult = sqlite3_step(statement.get());
if (stepResult == SQLITE_DONE) {
break;
}
if (stepResult != SQLITE_ROW) {
return m_errorManager.AddError(sqlite3_errmsg(db));
}
}
return true;
}
std::optional<std::vector<std::string>> DBStorage::DBTask::GetAllKeys(sqlite3 *db) noexcept
{
CHECK(!m_errorManager.HasErrors());
std::vector<std::string> result;
auto getAllKeysCallback = [&](int columnCount, char **columnTexts, char **) {
if (columnCount > 0) {
result.emplace_back(columnTexts[0]);
}
return SQLITE_OK;
};
CHECK(Exec(db, m_errorManager, "SELECT key FROM AsyncLocalStorage", getAllKeysCallback));
return result;
}
std::optional<bool> DBStorage::DBTask::RemoveAll(sqlite3 *db) noexcept
{
CHECK(!m_errorManager.HasErrors());
CHECK(Exec(db, m_errorManager, "DELETE FROM AsyncLocalStorage"));
return true;
}
// Read KeyValue from a JSON array.
void ReadValue(const winrt::IJSValueReader &reader,
/*out*/ DBStorage::KeyValue &value) noexcept
{
if (reader.ValueType() == winrt::JSValueType::Array) {
int index = 0;
while (reader.GetNextArrayItem()) {
if (index == 0) {
ReadValue(reader, value.Key);
} else if (index == 1) {
ReadValue(reader, value.Value);
} else {
// Read the array till the end to keep reader in a good state.
winrt::SkipValue<winrt::JSValue>(reader);
}
++index;
}
} else {
// To keep reader in a good state.
winrt::SkipValue<winrt::JSValue>(reader);
}
}
// Write KeyValue to a JSON array.
void WriteValue(const winrt::Microsoft::ReactNative::IJSValueWriter &writer,
const DBStorage::KeyValue &value) noexcept
{
writer.WriteArrayBegin();
WriteValue(writer, value.Key);
WriteValue(writer, value.Value);
writer.WriteArrayEnd();
}
// Write Error object to JSON.
void WriteValue(const winrt::IJSValueWriter &writer, const DBStorage::Error &value) noexcept
{
writer.WriteObjectBegin();
winrt::WriteProperty(writer, L"message", value.Message);
writer.WriteObjectEnd();
}

@ -0,0 +1,162 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include <optional>
#include <winsqlite/winsqlite3.h>
#include "NativeModules.h"
struct DBStorage {
// To pass KeyValue pairs in the native module API.
// It has custom ReadValue and WriteValue to read/write to/from JSON.
struct KeyValue {
std::string Key;
std::string Value;
};
// An Error object for the native module API.
// It has a custom WriteValue to write to JSON.
struct Error {
std::string Message;
};
// An error list shared between Promise and DBTask.
struct ErrorManager {
std::nullopt_t AddError(std::string &&message) noexcept;
bool HasErrors() const noexcept;
const std::vector<Error> &GetErrorList() const noexcept;
Error GetCombinedError() const noexcept;
private:
std::vector<Error> m_errors;
};
// Ensure that only one result onResolve or onReject callback is called once.
template <typename TOnResolve, typename TOnReject>
struct Promise {
Promise(TOnResolve &&onResolve, TOnReject &&onReject) noexcept
: m_onResolve(std::move(onResolve)), m_onReject(std::move(onReject))
{
}
~Promise()
{
Reject();
}
Promise(const Promise &other) = delete;
Promise &operator=(const Promise &other) = delete;
ErrorManager &GetErrorManager() noexcept
{
return m_errorManager;
}
template <typename TValue>
void Resolve(const TValue &value) noexcept
{
Complete([&] { m_onResolve(value); });
}
void Reject() noexcept
{
Complete([&] {
// Ensure that we have at least one error on rejection.
if (!m_errorManager.HasErrors()) {
m_errorManager.AddError("Promise is rejected.");
}
m_onReject(m_errorManager);
});
}
template <typename TValue>
void ResolveOrReject(const std::optional<TValue> &value) noexcept
{
if (value) {
Resolve(*value);
} else {
Reject();
}
}
private:
template <typename Fn>
void Complete(Fn &&fn)
{
if (m_isCompleted.test_and_set() == false) {
fn();
}
}
private:
ErrorManager m_errorManager;
std::atomic_flag m_isCompleted = ATOMIC_FLAG_INIT;
TOnResolve m_onResolve;
TOnReject m_onReject;
};
// An asynchronous task that run in a background thread.
struct DBTask {
DBTask(ErrorManager &errorManager,
std::function<void(DBTask &task, sqlite3 *db)> &&onRun) noexcept;
DBTask() = default;
DBTask(const DBTask &) = delete;
DBTask &operator=(const DBTask &) = delete;
void Run(DBStorage &storage, sqlite3 *db) noexcept;
void Cancel() noexcept;
std::optional<std::vector<KeyValue>>
MultiGet(sqlite3 *db, const std::vector<std::string> &keys) noexcept;
std::optional<bool> MultiSet(sqlite3 *db, const std::vector<KeyValue> &keyValues) noexcept;
std::optional<bool> MultiMerge(sqlite3 *db,
const std::vector<KeyValue> &keyValues) noexcept;
std::optional<bool> MultiRemove(sqlite3 *db, const std::vector<std::string> &keys) noexcept;
std::optional<std::vector<std::string>> GetAllKeys(sqlite3 *db) noexcept;
std::optional<bool> RemoveAll(sqlite3 *db) noexcept;
private:
std::function<void(DBTask &task, sqlite3 *db)> m_onRun;
ErrorManager &m_errorManager;
};
using DatabasePtr = std::unique_ptr<sqlite3, decltype(&sqlite3_close)>;
std::optional<sqlite3 *> InitializeStorage(ErrorManager &errorManager) noexcept;
~DBStorage();
template <typename TOnResolve, typename TOnReject>
static auto CreatePromise(TOnResolve &&onResolve, TOnReject &&onReject) noexcept
{
using PromiseType = Promise<std::decay_t<TOnResolve>, std::decay_t<TOnReject>>;
return std::make_shared<PromiseType>(std::forward<TOnResolve>(onResolve),
std::forward<TOnReject>(onReject));
}
void AddTask(ErrorManager &errorManager,
std::function<void(DBTask &task, sqlite3 *db)> &&onRun) noexcept;
winrt::Windows::Foundation::IAsyncAction RunTasks() noexcept;
private:
static constexpr auto s_dbPathProperty = L"React-Native-Community-Async-Storage-Database-Path";
DatabasePtr m_db{nullptr, &sqlite3_close};
winrt::slim_mutex m_lock;
winrt::slim_condition_variable m_cv;
winrt::Windows::Foundation::IAsyncAction m_action{nullptr};
std::vector<std::unique_ptr<DBTask>> m_tasks;
};
void ReadValue(const winrt::Microsoft::ReactNative::IJSValueReader &reader,
/*out*/ DBStorage::KeyValue &value) noexcept;
void WriteValue(const winrt::Microsoft::ReactNative::IJSValueWriter &writer,
const DBStorage::KeyValue &value) noexcept;
void WriteValue(const winrt::Microsoft::ReactNative::IJSValueWriter &writer,
const DBStorage::Error &value) noexcept;

@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "DBStorage.h"
#include "NativeModules.h"
namespace winrt::ReactNativeAsyncStorage::implementation
{
REACT_MODULE(RNCAsyncStorage)
struct RNCAsyncStorage {
REACT_METHOD(multiGet)
void multiGet(
std::vector<std::string> &&keys,
std::function<void(const std::vector<DBStorage::Error> &errors,
const std::vector<DBStorage::KeyValue> &result)> &&callback) noexcept
{
auto promise = DBStorage::CreatePromise(
[callback](const std::vector<DBStorage::KeyValue> &result) {
callback({}, result);
},
[callback](const DBStorage::ErrorManager &errorManager) {
callback(errorManager.GetErrorList(), {});
});
m_dbStorage.AddTask(
promise->GetErrorManager(),
[promise, keys = std::move(keys)](DBStorage::DBTask &task, sqlite3 *db) noexcept {
promise->ResolveOrReject(task.MultiGet(db, keys));
});
}
REACT_METHOD(multiSet)
void multiSet(
std::vector<DBStorage::KeyValue> &&keyValues,
std::function<void(const std::vector<DBStorage::Error> &errors)> &&callback) noexcept
{
auto promise =
DBStorage::CreatePromise([callback](bool /*value*/) { callback({}); },
[callback](const DBStorage::ErrorManager &errorManager) {
callback(errorManager.GetErrorList());
});
m_dbStorage.AddTask(promise->GetErrorManager(),
[promise, keyValues = std::move(keyValues)](DBStorage::DBTask &task,
sqlite3 *db) noexcept {
promise->ResolveOrReject(task.MultiSet(db, keyValues));
});
}
REACT_METHOD(multiMerge)
void multiMerge(
std::vector<DBStorage::KeyValue> &&keyValues,
std::function<void(const std::vector<DBStorage::Error> &errors)> &&callback) noexcept
{
auto promise =
DBStorage::CreatePromise([callback](bool /*value*/) { callback({}); },
[callback](const DBStorage::ErrorManager &errorManager) {
callback(errorManager.GetErrorList());
});
m_dbStorage.AddTask(promise->GetErrorManager(),
[promise, keyValues = std::move(keyValues)](DBStorage::DBTask &task,
sqlite3 *db) noexcept {
promise->ResolveOrReject(task.MultiMerge(db, keyValues));
});
}
REACT_METHOD(multiRemove)
void multiRemove(
std::vector<std::string> &&keys,
std::function<void(const std::vector<DBStorage::Error> &errors)> &&callback) noexcept
{
auto promise =
DBStorage::CreatePromise([callback](bool /*value*/) { callback({}); },
[callback](const DBStorage::ErrorManager &errorManager) {
callback(errorManager.GetErrorList());
});
m_dbStorage.AddTask(
promise->GetErrorManager(),
[promise, keys = std::move(keys)](DBStorage::DBTask &task, sqlite3 *db) noexcept {
promise->ResolveOrReject(task.MultiRemove(db, keys));
});
}
REACT_METHOD(getAllKeys)
void
getAllKeys(std::function<void(const std::optional<DBStorage::Error> &error,
const std::vector<std::string> &keys)> &&callback) noexcept
{
auto promise = DBStorage::CreatePromise(
[callback](const std::vector<std::string> &keys) { callback(std::nullopt, keys); },
[callback](const DBStorage::ErrorManager &errorManager) {
callback(errorManager.GetCombinedError(), {});
});
m_dbStorage.AddTask(promise->GetErrorManager(),
[promise](DBStorage::DBTask &task, sqlite3 *db) noexcept {
promise->ResolveOrReject(task.GetAllKeys(db));
});
}
REACT_METHOD(clear)
void
clear(std::function<void(const std::optional<DBStorage::Error> &error)> &&callback) noexcept
{
auto promise =
DBStorage::CreatePromise([callback](bool /*value*/) { callback(std::nullopt); },
[callback](const DBStorage::ErrorManager &errorManager) {
callback(errorManager.GetCombinedError());
});
m_dbStorage.AddTask(promise->GetErrorManager(),
[promise](DBStorage::DBTask &task, sqlite3 *db) noexcept {
promise->ResolveOrReject(task.RemoveAll(db));
});
}
private:
DBStorage m_dbStorage;
};
} // namespace winrt::ReactNativeAsyncStorage::implementation

@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "pch.h"
#include "ReactPackageProvider.h"
#include "RNCAsyncStorage.h"
#include "ReactPackageProvider.g.cpp"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ReactNativeAsyncStorage::implementation
{
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept
{
AddAttributedModules(packageBuilder);
}
} // namespace winrt::ReactNativeAsyncStorage::implementation

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#pragma once
#include "ReactPackageProvider.g.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ReactNativeAsyncStorage::implementation
{
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider> {
ReactPackageProvider() = default;
void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept;
};
} // namespace winrt::ReactNativeAsyncStorage::implementation
namespace winrt::ReactNativeAsyncStorage::factory_implementation
{
struct ReactPackageProvider
: ReactPackageProviderT<ReactPackageProvider, implementation::ReactPackageProvider> {
};
} // namespace winrt::ReactNativeAsyncStorage::factory_implementation

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save