Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 1 addition & 51 deletions docs/api/brownfield.md
Original file line number Diff line number Diff line change
@@ -1,51 +1 @@
!!! info

Brownfield integration is supported on **Android**, **iOS**, and **macOS**.

`AsyncStorage` is built on a shared storage layer (`SharedStorage`) that can also be accessed directly from native
code.
This is especially useful in brownfield scenarios, where your app combines React Native and native code, allowing both
layers to read from and write to the same storage consistently.

All platforms provide a thread-safe singleton registry called `StorageRegistry` to manage storage instances.

### Android

On Android, `StorageRegistry` is a public singleton, which is used to share `SharedStorage` instances with the native module.
Multiple calls with the same name return the same singleton instance, ensuring consistent access.

```kotlin
import android.content.Context
import kotlinx.coroutines.runBlocking
import org.asyncstorage.shared_storage.Entry
import org.asyncstorage.shared_storage.SharedStorage
import org.asyncstorage.storage.StorageRegistry

// access shared storage via StorageRegistry
val storage: SharedStorage = StorageRegistry.getStorage(ctx, "my-users")

// runBlocking only for a demonstration
runBlocking {
storage.setValues(listOf(Entry("email", "john@example.com")))
val values = storage.getValues(listOf("email"))
println("Stored email: ${values.firstOrNull()?.value}")
}
```

### iOS / macOS

On iOS and macOS, the `StorageRegistry` singleton provides the same functionality in Swift and Objective-C.

```swift
import AsyncStorage
import SharedAsyncStorage

// access shared storage via StorageRegistry
let storage: SharedStorage = StorageRegistry.shared.getStorage(dbName: "my-users")

Task {
storage.setValues([Entry(key: "email", value: "john@example.com")])
let values = storage.getValues(keys: ["email"])
print("Stored email: \(values.first?.value ?? "none")")
}
```
Documentation moved to [integrations/brownfield](../integrations/brownfield.md)
57 changes: 57 additions & 0 deletions docs/integrations/brownfield.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: Brownfield integration
---

# Brownfield integration

!!! info

Brownfield integration is supported on **Android**, **iOS**, and **macOS**.

`AsyncStorage` is built on a shared storage layer (`SharedStorage`) that can also be accessed directly from native
code.
This is especially useful in brownfield scenarios, where your app combines React Native and native code, allowing both
layers to read from and write to the same storage consistently.

All platforms provide a thread-safe singleton registry called `StorageRegistry` to manage storage instances.

### Android

On Android, `StorageRegistry` is a public singleton, which is used to share `SharedStorage` instances with the native module.
Multiple calls with the same name return the same singleton instance, ensuring consistent access.

```kotlin
import android.content.Context
import kotlinx.coroutines.runBlocking
import org.asyncstorage.shared_storage.Entry
import org.asyncstorage.shared_storage.SharedStorage
import org.asyncstorage.storage.StorageRegistry

// access shared storage via StorageRegistry
val storage: SharedStorage = StorageRegistry.getStorage(ctx, "my-users")

// runBlocking only for a demonstration
runBlocking {
storage.setValues(listOf(Entry("email", "john@example.com")))
val values = storage.getValues(listOf("email"))
println("Stored email: ${values.firstOrNull()?.value}")
}
```

### iOS / macOS

On iOS and macOS, the `StorageRegistry` singleton provides the same functionality in Swift and Objective-C.

```swift
import AsyncStorage
import SharedAsyncStorage

// access shared storage via StorageRegistry
let storage: SharedStorage = StorageRegistry.shared.getStorage(dbName: "my-users")

Task {
storage.setValues([Entry(key: "email", value: "john@example.com")])
let values = storage.getValues(keys: ["email"])
print("Stored email: \(values.first?.value ?? "none")")
}
```
63 changes: 63 additions & 0 deletions docs/integrations/jest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: Jest integration
---

# Jest integration

AsyncStorage requires a native module that is not available in a Jest environment. The package ships a built-in
in-memory mock at `@react-native-async-storage/async-storage/jest` that replaces the native implementation during
tests.

## Setup

### Transform configuration

The package ships as ESM source, so Jest must be configured to transform it. Add it to the `transformIgnorePatterns` in
your Jest config:

```js
transformIgnorePatterns: [
'node_modules/(?!@react-native-async-storage/)',
],
```

### Automatic mock file

Create a manual mock file that Jest will pick up automatically for every test:

```
__mocks__/@react-native-async-storage/async-storage.js
```

With content:

```js
module.exports = require("@react-native-async-storage/async-storage/jest");
```

Jest resolves files under `__mocks__/` automatically when the module is imported, so no additional configuration is
needed.

### Inline mock

To mock the module for a specific test file, call `jest.mock` at the top of the file:

```js
jest.mock("@react-native-async-storage/async-storage", () =>
require("@react-native-async-storage/async-storage/jest")
);
```

## What the mock provides

The mock is a full in-memory implementation of the `AsyncStorage` interface. Additionally, it exports
`clearAllMockStorages` to clear all in-memory storages.
Example usage:

```ts
import { clearAllMockStorages } from "@react-native-async-storage/async-storage/jest";

beforeEach(() => {
clearAllMockStorages();
});
```
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ nav:
- "Brownfield integration": api/brownfield.md
- Integrations:
- Expo: integrations/expo.md
- Jest: integrations/jest.md
- Brownfield: integrations/brownfield.md
- "Migration to v3": migration-to-3.md
- FAQ: faq.md
- Contributing: contributing.md
Expand Down
7 changes: 6 additions & 1 deletion packages/async-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@
"types": "./lib/typescript/index.d.ts",
"default": "./lib/module/index.js"
},
"./package.json": "./package.json"
"./package.json": "./package.json",
"./jest": {
"source": "./src/jest/AsyncStorageMock.ts",
"types": "./lib/typescript/jest/AsyncStorageMock.d.ts",
"default": "./lib/module/jest/AsyncStorageMock.js"
}
},
"scripts": {
"prepare": "yarn build",
Expand Down
59 changes: 59 additions & 0 deletions packages/async-storage/src/jest/AsyncStorageMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { AsyncStorage } from "@react-native-async-storage/async-storage";

class AsyncStorageMemoryImpl implements AsyncStorage {
private store = new Map<string, string>();

getItem = async (key: string): Promise<string | null> => {
return this.store.get(key) ?? null;
};

setItem = async (key: string, value: string): Promise<void> => {
this.store.set(key, value);
};

removeItem = async (key: string): Promise<void> => {
this.store.delete(key);
};

getMany = async (keys: string[]): Promise<Record<string, string | null>> => {
return keys.reduce<Record<string, string | null>>((result, key) => {
result[key] = this.store.get(key) ?? null;
return result;
}, {});
};

setMany = async (entries: Record<string, string>): Promise<void> => {
for (const [key, value] of Object.entries(entries)) {
this.store.set(key, value);
}
};

removeMany = async (keys: string[]): Promise<void> => {
for (const key of keys) {
this.store.delete(key);
}
};

getAllKeys = async (): Promise<string[]> => {
return Array.from(this.store.keys());
};

clear = async (): Promise<void> => {
this.store.clear();
};
}

const inMemoryDbRegistry = new Map<string, AsyncStorageMemoryImpl>();

export function createAsyncStorage(databaseName: string): AsyncStorage {
if (!inMemoryDbRegistry.has(databaseName)) {
inMemoryDbRegistry.set(databaseName, new AsyncStorageMemoryImpl());
}
return inMemoryDbRegistry.get(databaseName)!;
}

export function clearAllMockStorages(): void {
inMemoryDbRegistry.clear();
}

export default createAsyncStorage("legacy");
Loading