-
Notifications
You must be signed in to change notification settings - Fork 200
feat(drivers): add React Native AsyncStorage driver #825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| --- | ||
| icon: simple-icons:react | ||
| --- | ||
|
|
||
| # React Native AsyncStorage | ||
|
|
||
| Use [React Native AsyncStorage](https://github.com/react-native-async-storage/async-storage) as an Unstorage driver. | ||
|
|
||
| ## Usage | ||
|
|
||
| ```ts | ||
| import asyncStorageDriver from "unstorage/drivers/react-native-async-storage"; | ||
|
|
||
| const storage = createStorage({ | ||
| driver: asyncStorageDriver({ | ||
| base: "app", | ||
| }), | ||
| }); | ||
| ``` | ||
|
|
||
| Install the peer dependency with `:pm-install{name="@react-native-async-storage/async-storage"}`. | ||
|
|
||
| ## Options | ||
|
|
||
| - `base`: Optional namespace prefix for stored keys. | ||
| - `lib`: Optional AsyncStorage module (or loader) for bundlers and dependency injection. | ||
|
|
||
| AsyncStorage does not provide change notifications, so this driver does not support `watch`. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,77 @@ | ||||||
| import type AsyncStorage from "@react-native-async-storage/async-storage"; | ||||||
|
|
||||||
| import { | ||||||
| type DriverFactory, | ||||||
| type DriverDependencies, | ||||||
| importLib, | ||||||
| joinKeys, | ||||||
| type LibImport, | ||||||
| normalizeKey, | ||||||
| } from "./utils/index.ts"; | ||||||
|
|
||||||
| export const DRIVER_DEPENDENCIES: DriverDependencies = { | ||||||
| lib: { name: "@react-native-async-storage/async-storage", version: "^2 || ^3" }, | ||||||
| }; | ||||||
|
|
||||||
| const DRIVER_NAME = "react-native-async-storage"; | ||||||
|
|
||||||
| export interface ReactNativeAsyncStorageOptions { | ||||||
| base?: string; | ||||||
| /** Optionally provide AsyncStorage to avoid dynamically importing it. */ | ||||||
| lib?: LibImport<typeof AsyncStorage>; | ||||||
| } | ||||||
|
|
||||||
| const driver: DriverFactory<ReactNativeAsyncStorageOptions, Promise<typeof AsyncStorage>> = (opts) => { | ||||||
| const base = normalizeKey(opts?.base); | ||||||
| const resolveKey = (key: string) => joinKeys(base, key); | ||||||
| let storage: Promise<typeof AsyncStorage> | undefined; | ||||||
| const getStorage = () => | ||||||
| (storage ??= importLib( | ||||||
| DRIVER_NAME, | ||||||
| "@react-native-async-storage/async-storage", | ||||||
| opts?.lib, | ||||||
| () => import("@react-native-async-storage/async-storage"), | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- driver ---'
cat -n src/drivers/react-native-async-storage.ts | sed -n '1,110p'
printf '%s\n' '--- importLib and related utilities ---'
rg -n -A25 -B8 'function importLib|const importLib|export .*importLib' src
printf '%s\n' '--- package and tests ---'
rg -n -A8 -B8 'react-native-async-storage|AsyncStorage|react-native-async' package.json src test tests 2>/dev/null || trueRepository: unjs/unstorage Length of output: 18600 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tracked files ---'
git ls-files | rg '(^|/)(react-native-async-storage|utils/index|package\.json|.*async-storage.*)'
printf '%s\n' '--- exact importLib references ---'
rg -n -A20 -B10 'importLib' src/drivers src 2>/dev/null | head -240Repository: unjs/unstorage Length of output: 13890 Unwrap the default export from the lazy import. When Proposed fix- () => import("`@react-native-async-storage/async-storage`"),
+ () => import("`@react-native-async-storage/async-storage`").then((mod) => mod.default),📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| )); | ||||||
|
|
||||||
| return { | ||||||
| name: DRIVER_NAME, | ||||||
| options: opts, | ||||||
| getInstance: () => getStorage(), | ||||||
| async hasItem(key) { | ||||||
| return (await (await getStorage()).getItem(resolveKey(key))) !== null; | ||||||
| }, | ||||||
| async getItem(key) { | ||||||
| return (await getStorage()).getItem(resolveKey(key)); | ||||||
| }, | ||||||
| async getItemRaw(key) { | ||||||
| return (await getStorage()).getItem(resolveKey(key)); | ||||||
| }, | ||||||
| async setItem(key, value) { | ||||||
| await (await getStorage()).setItem(resolveKey(key), value); | ||||||
| }, | ||||||
| async setItemRaw(key, value) { | ||||||
| await (await getStorage()).setItem(resolveKey(key), value); | ||||||
|
Comment on lines
+47
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- changed file ---'
cat -n src/drivers/react-native-async-storage.ts
printf '%s\n' '--- raw method definitions and call sites ---'
rg -n -C 4 'setItemRaw|getItemRaw|serializeRaw|deserializeRaw|createStorage' src test tests 2>/dev/null || true
printf '%s\n' '--- package and AsyncStorage declarations ---'
rg -n -C 3 'react-native-async-storage|AsyncStorage|setItem\\(|getItem\\(' package.json package-lock.json yarn.lock pnpm-lock.yaml src 2>/dev/null || trueRepository: unjs/unstorage Length of output: 50370 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tracked relevant files ---'
git ls-files | rg '(^|/)(react-native-async-storage|storage|async-storage|package(-lock)?|yarn.lock|pnpm-lock)' | head -80
printf '%s\n' '--- driver outline ---'
ast-grep outline src/drivers/react-native-async-storage.ts || trueRepository: unjs/unstorage Length of output: 831 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- storage raw contract ---'
sed -n '185,270p' src/storage.ts
printf '%s\n' '--- raw serialization helpers ---'
sed -n '1,90p' src/_utils.ts
printf '%s\n' '--- importLib implementation ---'
rg -n -C 8 'function importLib|const importLib|export .*importLib' src/drivers src
printf '%s\n' '--- React Native driver tests ---'
cat -n test/drivers/react-native-async-storage.test.ts
printf '%s\n' '--- driver documentation ---'
cat -n docs/2.drivers/react-native-async-storage.mdRepository: unjs/unstorage Length of output: 9962 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- package versions and local declarations ---'
rg -n -C 4 '`@react-native-async-storage/async-storage`|AsyncStorage' package.json pnpm-lock.yaml src/react-native-async-storage.d.ts docs/2.drivers/react-native-async-storage.md
printf '%s\n' '--- direct driver API contract references ---'
rg -n -C 3 'setItemRaw|getItemRaw|serializeRaw|deserializeRaw' docs src/drivers test/drivers/react-native-async-storage.test.ts | head -240Repository: unjs/unstorage Length of output: 16430 Let Unstorage handle raw-value serialization.
🤖 Prompt for AI Agents |
||||||
| }, | ||||||
| async removeItem(key) { | ||||||
| await (await getStorage()).removeItem(resolveKey(key)); | ||||||
| }, | ||||||
| async getKeys() { | ||||||
| const prefix = base ? `${base}:` : ""; | ||||||
| return (await (await getStorage()).getAllKeys()) | ||||||
| .filter((key) => !prefix || key.startsWith(prefix)) | ||||||
| .map((key) => (prefix ? key.slice(prefix.length) : key)); | ||||||
|
Comment on lines
+58
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- driver ---'
cat -n src/drivers/react-native-async-storage.ts | sed -n '1,100p'
printf '%s\n' '--- createStorage/getKeys definitions and callers ---'
rg -n -C 5 'getKeys\(|createStorage' src test tests 2>/dev/null | head -240Repository: unjs/unstorage Length of output: 16031 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- tracked files matching storage contracts/tests ---'
git ls-files | rg '(^|/)(storage|react-native-async-storage|.*async-storage.*|.*driver.*)' | head -200
printf '%s\n' '--- exact getKeys references ---'
rg -n -C 8 '\bgetKeys\b' . --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' | head -300Repository: unjs/unstorage Length of output: 22023 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- createStorage getKeys implementation ---'
cat -n src/storage.ts | sed -n '300,360p'
printf '%s\n' '--- React Native driver tests ---'
cat -n test/drivers/react-native-async-storage.test.ts | sed -n '1,240p'
printf '%s\n' '--- driver getKeys contract ---'
rg -n -C 6 'getKeys\s*\(' src/types.ts src/drivers/utils src/drivers/memory.tsRepository: unjs/unstorage Length of output: 5291 Honor the
🤖 Prompt for AI Agents |
||||||
| }, | ||||||
| async clear(prefix) { | ||||||
| const storage = await getStorage(); | ||||||
| const keyPrefix = resolveKey(prefix || ""); | ||||||
| if (!keyPrefix) { | ||||||
| await storage.clear(); | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target file ---'
sed -n '1,110p' src/drivers/react-native-async-storage.ts
printf '%s\n' '--- related references ---'
rg -n --glob '!node_modules' 'react-native-async-storage|AsyncStorage|\\.clear\\(\\)' package.json package-lock.json yarn.lock pnpm-lock.yaml src test tests 2>/dev/null | head -200Repository: unjs/unstorage Length of output: 6244 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- tracked files ---'
git ls-files | rg '(^|/)(package.json|.*async-storage.*|.*react-native.*|README.*)$' | head -200
printf '%s\n' '--- dependency declarations ---'
rg -n -C 3 'async-storage|react-native' package.json package-lock.json yarn.lock pnpm-lock.yaml 2>/dev/null | head -160Repository: unjs/unstorage Length of output: 3152 🌐 Web query:
💡 Result: Yes, the clear method in Citations:
Avoid an unscoped global clear. When 🤖 Prompt for AI Agents |
||||||
| return; | ||||||
| } | ||||||
| const keys = await storage.getAllKeys(); | ||||||
| await storage.multiRemove(keys.filter((key) => key === keyPrefix || key.startsWith(`${keyPrefix}:`))); | ||||||
| }, | ||||||
| }; | ||||||
| }; | ||||||
|
|
||||||
| export default driver; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| declare module "@react-native-async-storage/async-storage" { | ||
| interface AsyncStorageStatic { | ||
| getItem(key: string): Promise<string | null>; | ||
| setItem(key: string, value: string): Promise<void>; | ||
| removeItem(key: string): Promise<void>; | ||
| getAllKeys(): Promise<readonly string[]>; | ||
| multiRemove(keys: readonly string[]): Promise<void>; | ||
| clear(): Promise<void>; | ||
| } | ||
| const AsyncStorage: AsyncStorageStatic; | ||
| export default AsyncStorage; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import { describe, expect, it, vi } from "vitest"; | ||
| import { createStorage } from "../../src/index.ts"; | ||
| import { testDriver } from "./utils.ts"; | ||
| import driver, { type ReactNativeAsyncStorageOptions } from "../../src/drivers/react-native-async-storage.ts"; | ||
|
|
||
| function createMock() { | ||
| const data = new Map<string, string>(); | ||
| const lib = { | ||
| getItem: vi.fn(async (key: string) => data.get(key) ?? null), | ||
| setItem: vi.fn(async (key: string, value: string) => void data.set(key, value)), | ||
| removeItem: vi.fn(async (key: string) => void data.delete(key)), | ||
| getAllKeys: vi.fn(async () => [...data.keys()]), | ||
| multiRemove: vi.fn(async (keys: readonly string[]) => keys.forEach((key) => data.delete(key))), | ||
| clear: vi.fn(async () => void data.clear()), | ||
| }; | ||
| return lib; | ||
| } | ||
|
|
||
| const mock = createMock(); | ||
|
|
||
| function createDriver(base?: string) { | ||
| return { driver: driver({ base, lib: mock } as ReactNativeAsyncStorageOptions), lib: mock }; | ||
| } | ||
|
|
||
| describe("react-native-async-storage", () => { | ||
| testDriver({ driver: () => { | ||
| mock.clear(); | ||
| return createDriver().driver; | ||
| } }); | ||
|
|
||
| it("supports base namespaces", async () => { | ||
| const { driver: instance, lib } = createDriver("app"); | ||
| const storage = createStorage({ driver: instance }); | ||
| await storage.setItem("foo", "bar"); | ||
| expect(lib.setItem).toHaveBeenCalledWith("app:foo", '"bar"'); | ||
| expect(await storage.getKeys()).toEqual(["foo"]); | ||
| await storage.clear(); | ||
| expect(lib.clear).toHaveBeenCalled(); | ||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Assert scoped removal instead of global clear. With 🤖 Prompt for AI Agents |
||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Import
createStoragein the usage snippet.The example calls
createStoragebut does not import it. Addimport { createStorage } from "unstorage";so the example is copyable.🤖 Prompt for AI Agents