Refinement
This commit is contained in:
@@ -14,26 +14,26 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
test(`gets and sets data synchronously`, async () => {
|
||||
storage.set('shell.colorMode', 'light')
|
||||
storage.set('shell', {colorMode: 'light'})
|
||||
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||
storage.STORAGE_ROOT_KEY,
|
||||
JSON.stringify({shell: {colorMode: 'light'}}),
|
||||
)
|
||||
expect(storage.get('shell.colorMode')).toBe('light')
|
||||
storage.set('shell', {colorMode: 'light'})
|
||||
expect(AsyncStorage.getItem).not.toHaveBeenCalled()
|
||||
|
||||
storage.set('shell.colorMode', 'dark')
|
||||
expect(storage.get('shell.colorMode')).toBe('dark')
|
||||
storage.set('shell', {colorMode: 'dark'})
|
||||
expect(storage.get('shell').colorMode).toBe('dark')
|
||||
})
|
||||
|
||||
test(`set rolls back on error`, async () => {
|
||||
storage.set('shell.colorMode', 'light')
|
||||
test(`set ignores error and continues in memory`, async () => {
|
||||
storage.set('shell', {colorMode: 'light'})
|
||||
|
||||
jest
|
||||
.mocked(AsyncStorage.setItem)
|
||||
.mockImplementationOnce(() => Promise.reject(new Error('test error')))
|
||||
|
||||
await storage.set('shell.colorMode', 'system')
|
||||
await storage.set('shell', {colorMode: 'system'})
|
||||
|
||||
expect(storage.get('shell.colorMode')).toBe('light')
|
||||
expect(storage.get('shell').colorMode).toBe('system')
|
||||
})
|
||||
|
||||
+21
-20
@@ -1,45 +1,46 @@
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {Schema, PropertyMap} from '#/storage/schema'
|
||||
import {Schema, defaultData} from '#/storage/schema'
|
||||
|
||||
export const STORAGE_ROOT_KEY = 'root'
|
||||
|
||||
/**
|
||||
* In memory cache of local storage data. Never export or reference this
|
||||
* directly.
|
||||
*
|
||||
* We set it to a default value so that if AsyncStorage entirely fails, the app
|
||||
* will continue to function entirely in memory.
|
||||
*/
|
||||
let data: Schema | undefined
|
||||
let data: Schema = defaultData
|
||||
|
||||
export async function init() {
|
||||
const raw = await AsyncStorage.getItem(STORAGE_ROOT_KEY)
|
||||
logger.debug(`storage initialized`)
|
||||
// TODO: Add try catch here etc, same in lib/storage.ts
|
||||
data = (raw ? JSON.parse(raw) : {}) as Schema
|
||||
logger.debug(`storage initializing`)
|
||||
|
||||
try {
|
||||
const raw = await AsyncStorage.getItem(STORAGE_ROOT_KEY)
|
||||
data = (raw ? JSON.parse(raw) : {}) as Schema
|
||||
} catch (e) {
|
||||
logger.error(`storage init() failed`)
|
||||
}
|
||||
}
|
||||
|
||||
export function get<T extends keyof PropertyMap>(key: T): PropertyMap[T] {
|
||||
export function get<T extends keyof Schema>(key: T): Schema[T] {
|
||||
logger.debug(`storage get(${key})`)
|
||||
if (!data) {
|
||||
throw new Error(`Data is not initialized. Did you forget to call init()`)
|
||||
}
|
||||
return data[key]
|
||||
}
|
||||
|
||||
export async function set<T extends keyof PropertyMap>(
|
||||
key: T,
|
||||
value: PropertyMap[T],
|
||||
) {
|
||||
export async function set<T extends keyof Schema>(key: T, value: Schema[T]) {
|
||||
logger.debug(`storage set(${key}, value)`)
|
||||
if (!data) {
|
||||
throw new Error(`Data is not initialized. Did you forget to call init()`)
|
||||
}
|
||||
data[ley] = value
|
||||
|
||||
// TODO: actual writing should probably be debounced
|
||||
data[key] = value
|
||||
|
||||
try {
|
||||
// TODO maybe debounce this in the future
|
||||
await AsyncStorage.setItem(STORAGE_ROOT_KEY, JSON.stringify(data))
|
||||
return true
|
||||
} catch (err) {
|
||||
logger.error(`storage set(${keypath}, value) failed`)
|
||||
logger.error(`storage set(${key}, value) failed`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
+4
-60
@@ -1,5 +1,3 @@
|
||||
// import {LabelPreference} from '@atproto/api'
|
||||
|
||||
/**
|
||||
* The shape of the object we store in local storage
|
||||
*/
|
||||
@@ -7,64 +5,10 @@ export type Schema = {
|
||||
shell: {
|
||||
colorMode: 'system' | 'light' | 'dark'
|
||||
}
|
||||
// session: {
|
||||
// data: {
|
||||
// service: string
|
||||
// did: `did:plc:${string}`
|
||||
// }
|
||||
// accounts: {
|
||||
// service: string
|
||||
// did: `did:plc:${string}`
|
||||
// refreshJwt: string
|
||||
// accessJwt: string
|
||||
// handle: string
|
||||
// email: string
|
||||
// displayName: string
|
||||
// aviUrl: string
|
||||
// emailConfirmed: boolean
|
||||
// }[]
|
||||
// }
|
||||
// me: {
|
||||
// did: `did:plc:${string}`
|
||||
// handle: string
|
||||
// displayName: string
|
||||
// description: string
|
||||
// avatar: string
|
||||
// }
|
||||
// onboarding: {
|
||||
// step: string
|
||||
// }
|
||||
// preferences: {
|
||||
// primaryLanguage: string
|
||||
// contentLanguages: string[]
|
||||
// postLanguage: string
|
||||
// postLanguageHistory: string[]
|
||||
// contentLabels: {
|
||||
// nsfw: LabelPreference
|
||||
// nudity: LabelPreference
|
||||
// suggestive: LabelPreference
|
||||
// gore: LabelPreference
|
||||
// hate: LabelPreference
|
||||
// spam: LabelPreference
|
||||
// impersonation: LabelPreference
|
||||
// }
|
||||
// savedFeeds: string[]
|
||||
// pinnedFeeds: string[]
|
||||
// requireAltTextEnabled: boolean
|
||||
// }
|
||||
// invitedUsers: {
|
||||
// seenDids: string[]
|
||||
// copiedInvites: string[]
|
||||
// }
|
||||
// mutedThreads: {uris: string[]}
|
||||
// reminders: {lastEmailConfirm: string}
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of all the properties that can be stored in the storage, and their
|
||||
* values. This mapping must be updated in order to get/set values by their
|
||||
* keypaths.
|
||||
*/
|
||||
export type PropertyMap = {
|
||||
'shell.colorMode': Schema['shell']['colorMode']
|
||||
export const defaultData: Schema = {
|
||||
shell: {
|
||||
colorMode: 'system',
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user