import { MMKV } from 'react-native-mmkv' import { AnyFunction, AnyRecord } from '@codeleap/types' type StorageKey> = keyof T | (string & {}) | ((allKeys: T) => string) | [keyof T, any] type ValueTypeMap = { string: string number: number boolean: boolean object: V } /** * Wraps `react-native-mmkv`, which is fully synchronous — reads and writes block the JS thread for microseconds but never return Promises, unlike AsyncStorage. Each `StorageManager` instance creates its own MMKV database file; if you need encryption, pass an `id` and `encryptionKey` to `new MMKV(options)` here before sharing the instance across the app. * * Objects are round-tripped through `JSON.stringify`/`JSON.parse`; non-serialisable values (class instances, functions) will silently lose their prototype on read-back. */ export class StorageManager> { public instance = new MMKV() public storageKeys: T constructor(keys: T) { this.storageKeys = keys } public getStorageKey(key: StorageKey): string { if (typeof key == 'function') { return key(this.storageKeys) } if (Array.isArray(key)) { const [storageKey, value] = key const extractor = this.storageKeys?.[storageKey] as unknown as AnyFunction return extractor?.(value) } const storageKey = this.storageKeys?.[key] if (!!storageKey) { return storageKey } else { return key as string } } private parseValue(value: any) { try { return JSON.parse(value) } catch (e) { return value } } public exists(key: StorageKey) { const storageKey = this.getStorageKey(key) return this.instance.contains(storageKey) } public get(key: StorageKey, type: 'string'): string | undefined public get(key: StorageKey, type: 'number'): number | undefined public get(key: StorageKey, type: 'boolean'): boolean | undefined public get(key: StorageKey): V | undefined public get(key: StorageKey, type?: keyof ValueTypeMap): ValueTypeMap[keyof ValueTypeMap] | undefined { const storageKey = this.getStorageKey(key) if (type === 'string') { return this.instance.getString(storageKey) as ValueTypeMap[keyof ValueTypeMap] } else if (type === 'number') { return this.instance.getNumber(storageKey) as ValueTypeMap[keyof ValueTypeMap] } else if (type === 'boolean') { return this.instance.getBoolean(storageKey) as ValueTypeMap[keyof ValueTypeMap] } else { const value = this.instance.getString(storageKey) return !!value ? this.parseValue(value) : undefined } } public set(key: StorageKey, value: V) { const storageKey = this.getStorageKey(key) const isValidValue = typeof value == 'string' || typeof value == 'number' || typeof value == 'boolean' const parsedValue = isValidValue ? value : JSON.stringify(value) this.instance.set(storageKey, parsedValue) } public remove(key: StorageKey) { const storageKey = this.getStorageKey(key) this.instance.delete(storageKey) } public multiRemove(keys: Array>) { for (const key of keys) { const storageKey = this.getStorageKey(key) this.instance.delete(storageKey) } } public multiGet(keys: Array>): Partial> { const values: Partial> = {} for (const key of keys) { const storageKey = this.getStorageKey(key) const value = this.instance.getString(storageKey) values[key as keyof T] = value } return values } }