@yoroi/common
Version:
The Common package of Yoroi SDK
145 lines (133 loc) • 4.69 kB
text/typescript
import AsyncStorage from '@react-native-async-storage/async-storage'
import {App} from '@yoroi/types'
import {parseSafe} from '../../utils/parsers'
import {isFolderKey} from '../helpers/is-folder-key'
import {isFileKey} from '../helpers/is-file-key'
export const mountAsyncStorage = ({
path,
}: {
path: App.StorageFolderName
}): App.Storage => {
const withPath = (key: string) =>
`${path}${key}` as `${App.StorageFolderName}${string}`
const withoutPath = (value: string) => value.slice(path.length)
function getItem<T>(
key: string,
parse: (item: string | null) => T,
): Promise<T>
function getItem<T = unknown>(key: string): Promise<T>
async function getItem(key: string, parse = parseSafe) {
const item = await AsyncStorage.getItem(withPath(key))
return parse(item)
}
function multiGet<T>(
keys: ReadonlyArray<string>,
parse: (item: string | null) => T,
): Promise<Array<[string, T]>>
function multiGet<T = unknown>(
keys: ReadonlyArray<string>,
): Promise<Array<[string, T]>>
async function multiGet(keys: ReadonlyArray<string>, parse = parseSafe) {
const absolutePaths = keys.map((key) => withPath(key))
const items = await AsyncStorage.multiGet(absolutePaths)
return items.map(
([key, value]) => [withoutPath(key), parse(value)] as const,
)
}
return {
join: (folderName: App.StorageFolderName) =>
mountAsyncStorage({path: `${path}${folderName}`}),
getItem,
multiGet,
setItem: async <T = unknown>(
key: string,
value: T,
stringify: (data: T) => string = JSON.stringify,
) => {
const item = stringify(value)
await AsyncStorage.setItem(withPath(key), item)
},
multiSet: async <T = unknown>(
tuples: ReadonlyArray<[key: string, value: T]>,
stringify: (data: T) => string = JSON.stringify,
) => {
const items: Array<[string, string]> = tuples.map(([key, value]) => [
withPath(key),
stringify(value),
])
await AsyncStorage.multiSet(items)
},
removeItem: async (key: string) => {
await AsyncStorage.removeItem(withPath(key))
},
removeFolder: async (folderName: App.StorageFolderName) => {
const keys = await AsyncStorage.getAllKeys()
const filteredKeys = keys.filter(
(key) =>
key.startsWith(path) &&
withoutPath(key).startsWith(folderName) &&
isFolderKey({key, path}),
)
await AsyncStorage.multiRemove(filteredKeys)
},
multiRemove: async (keys: ReadonlyArray<string>) => {
await AsyncStorage.multiRemove(keys.map((key) => withPath(key)))
},
getAllKeys: () => {
return AsyncStorage.getAllKeys()
.then((keys) =>
keys.filter((key) => key.startsWith(path) && isFileKey({key, path})),
)
.then(
// temporary any until async interface is migrated to receive keys for multi storage
(filteredKeys) => filteredKeys.map(withoutPath) as ReadonlyArray<any>,
)
},
clear: async () => {
const keys = await AsyncStorage.getAllKeys()
const filteredKeys = keys.filter((key) => key.startsWith(path))
await AsyncStorage.multiRemove(filteredKeys)
},
} as const
}
export const mountAsyncMultiStorage = <T = unknown>(
options: App.MultiStorageOptions<T>,
): Readonly<App.MultiStorage<T>> => {
const {
storage,
dataFolder,
keyExtractor,
serializer = JSON.stringify,
deserializer = parseSafe as (item: string | null) => T | null,
} = options
const dataStorage = storage.join(dataFolder)
const {getAllKeys: getAllKeysStorage, multiSet, multiGet} = dataStorage
const clear = () => storage.removeFolder(dataFolder)
const saveMany = (items: ReadonlyArray<NonNullable<T>>) => {
const entries: [string, T][] = items.map((item) => {
if (typeof keyExtractor === 'function') {
return [keyExtractor(item), item]
}
return [String(item[keyExtractor]), item]
})
const entriesWithKeys = entries.filter(([key]) => key != null && key !== '')
return multiSet(entriesWithKeys, serializer as (item: unknown) => string)
}
const readAll = () =>
getAllKeys().then((keysToRead) =>
multiGet<T | null>(keysToRead, deserializer),
)
const readMany = (keysToRead: ReadonlyArray<string>) =>
dataStorage.multiGet<T | null>(keysToRead, deserializer)
const removeMany = (keysToRead: ReadonlyArray<string>) =>
dataStorage.multiRemove(keysToRead)
const getAllKeys = () => getAllKeysStorage().then((keys) => keys)
return {
getAllKeys,
clear,
readAll,
saveMany,
readMany,
removeMany,
} as const
}