@react-native-async-storage/async-storage
Version:
Asynchronous, persistent, key-value storage system for React Native.
244 lines (223 loc) • 6.69 kB
text/typescript
import type { AsyncStorage } from "./AsyncStorage";
import { AsyncStorageError } from "./AsyncStorageError";
import { TurboModuleRegistry, type TurboModule } from "react-native";
/**
* Due to Windows native target not being supported by Room KMP,
* custom storages are not available. Instead, this module falls back
* to the legacy v2 AsyncStorage implementation.
*/
let warned = false;
/**
* Creates an AsyncStorage instance.
* On Windows, always falls back to legacy storage.
*/
export function createAsyncStorage(_: string): AsyncStorage {
if (!warned && __DEV__) {
warned = true;
// eslint-disable-next-line
console.warn(
"[AsyncStorage] Warning: Creating custom storages is not supported on Windows. Falling back to the legacy implementation."
);
}
return getLegacyStorage();
}
/** Returns a singleton instance of the legacy (v2) AsyncStorage implementation. */
export function getLegacyStorage(): AsyncStorage {
return LegacyAsyncStorageImpl.instance;
}
class LegacyAsyncStorageImpl implements AsyncStorage {
private constructor() {}
static instance = new LegacyAsyncStorageImpl();
private get db(): NativeAsyncStorageSpec {
const mod = RNCAsyncStorage;
if (!mod) {
throw AsyncStorageError.jsError(
`Native module is null, cannot access storage.`,
AsyncStorageError.Type.NativeModuleError
);
}
return mod;
}
getItem = async (key: string): Promise<string | null> => {
try {
return await new Promise<string | null>((resolve, reject) => {
this.db.multiGet([key], (errors, result) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
resolve(result?.[0]?.[1] ?? null);
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
setItem = async (key: string, value: string): Promise<void> => {
try {
await new Promise<void>((resolve, reject) => {
this.db.multiSet([[key, value]], (errors) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
resolve();
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
removeItem = async (key: string): Promise<void> => {
try {
await new Promise<void>((resolve, reject) => {
this.db.multiRemove([key], (errors) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
resolve();
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
getMany = async (keys: string[]): Promise<Record<string, string | null>> => {
try {
return await new Promise((resolve, reject) => {
this.db.multiGet(keys, (errors, result) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
const resultMap = new Map(result);
const entries: Record<string, string | null> = {};
for (const key of keys) {
entries[key] = resultMap.get(key) ?? null;
}
resolve(entries);
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
setMany = async (entries: Record<string, string>): Promise<void> => {
try {
await new Promise<void>((resolve, reject) => {
this.db.multiSet(Object.entries(entries), (errors) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
resolve();
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
removeMany = async (keys: string[]): Promise<void> => {
try {
await new Promise<void>((resolve, reject) => {
this.db.multiRemove(keys, (errors) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
resolve();
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
getAllKeys = async (): Promise<string[]> => {
try {
return await new Promise((resolve, reject) => {
this.db.getAllKeys((errors, result) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
if (!result) {
return reject(
AsyncStorageError.jsError(
"Invalid state, no error and no values returned",
AsyncStorageError.Type.UnknownError
)
);
}
resolve(result);
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
clear = async (): Promise<void> => {
try {
await new Promise<void>((resolve, reject) => {
this.db.clear((errors) => {
const error = this.getError(errors);
if (error) {
return reject(error);
}
resolve();
});
});
} catch (e) {
throw AsyncStorageError.nativeError(e);
}
};
private getError = (potentialError: any): ErrorLike | null => {
if (!potentialError) {
return null;
}
if (Array.isArray(potentialError) && potentialError.length) {
const firstError = potentialError.find((e): e is ErrorLike => e?.message);
if (firstError) {
return firstError;
}
return null;
} else if (potentialError?.message) {
return potentialError;
}
return null;
};
}
/**
* Backward compatibility layer to support the Windows target in v3.
* Room KMP does not currently support the Windows native target,
* so shared-storage cannot be used on Windows.
* See: https://issuetracker.google.com/issues/363195546
*/
const RNCAsyncStorage =
TurboModuleRegistry.get<NativeAsyncStorageSpec>("RNCAsyncStorage");
type ErrorLike = {
message: string;
key?: string;
};
interface NativeAsyncStorageSpec extends TurboModule {
multiGet: (
keys: string[],
callback: (error?: ErrorLike[], result?: [string, string][]) => void
) => void;
multiSet: (
kvPairs: [string, string][],
callback: (error?: ErrorLike[]) => void
) => void;
multiRemove: (
keys: readonly string[],
callback: (error?: ErrorLike[]) => void
) => void;
multiMerge: (
kvPairs: [string, string][],
callback: (error?: ErrorLike[]) => void
) => void;
getAllKeys: (
callback: (error?: ErrorLike[], result?: string[]) => void
) => void;
clear: (callback: (error?: ErrorLike[]) => void) => void;
}