Refinement
This commit is contained in:
@@ -14,26 +14,26 @@ afterEach(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test(`gets and sets data synchronously`, async () => {
|
test(`gets and sets data synchronously`, async () => {
|
||||||
storage.set('shell.colorMode', 'light')
|
storage.set('shell', {colorMode: 'light'})
|
||||||
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
|
||||||
storage.STORAGE_ROOT_KEY,
|
storage.STORAGE_ROOT_KEY,
|
||||||
JSON.stringify({shell: {colorMode: 'light'}}),
|
JSON.stringify({shell: {colorMode: 'light'}}),
|
||||||
)
|
)
|
||||||
expect(storage.get('shell.colorMode')).toBe('light')
|
storage.set('shell', {colorMode: 'light'})
|
||||||
expect(AsyncStorage.getItem).not.toHaveBeenCalled()
|
expect(AsyncStorage.getItem).not.toHaveBeenCalled()
|
||||||
|
|
||||||
storage.set('shell.colorMode', 'dark')
|
storage.set('shell', {colorMode: 'dark'})
|
||||||
expect(storage.get('shell.colorMode')).toBe('dark')
|
expect(storage.get('shell').colorMode).toBe('dark')
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`set rolls back on error`, async () => {
|
test(`set ignores error and continues in memory`, async () => {
|
||||||
storage.set('shell.colorMode', 'light')
|
storage.set('shell', {colorMode: 'light'})
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.mocked(AsyncStorage.setItem)
|
.mocked(AsyncStorage.setItem)
|
||||||
.mockImplementationOnce(() => Promise.reject(new Error('test error')))
|
.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 AsyncStorage from '@react-native-async-storage/async-storage'
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
import {logger} from '#/logger'
|
||||||
import {Schema, PropertyMap} from '#/storage/schema'
|
import {Schema, defaultData} from '#/storage/schema'
|
||||||
|
|
||||||
export const STORAGE_ROOT_KEY = 'root'
|
export const STORAGE_ROOT_KEY = 'root'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In memory cache of local storage data. Never export or reference this
|
* In memory cache of local storage data. Never export or reference this
|
||||||
* directly.
|
* 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() {
|
export async function init() {
|
||||||
const raw = await AsyncStorage.getItem(STORAGE_ROOT_KEY)
|
logger.debug(`storage initializing`)
|
||||||
logger.debug(`storage initialized`)
|
|
||||||
// TODO: Add try catch here etc, same in lib/storage.ts
|
try {
|
||||||
data = (raw ? JSON.parse(raw) : {}) as Schema
|
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})`)
|
logger.debug(`storage get(${key})`)
|
||||||
if (!data) {
|
|
||||||
throw new Error(`Data is not initialized. Did you forget to call init()`)
|
|
||||||
}
|
|
||||||
return data[key]
|
return data[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function set<T extends keyof PropertyMap>(
|
export async function set<T extends keyof Schema>(key: T, value: Schema[T]) {
|
||||||
key: T,
|
|
||||||
value: PropertyMap[T],
|
|
||||||
) {
|
|
||||||
logger.debug(`storage set(${key}, value)`)
|
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 {
|
try {
|
||||||
|
// TODO maybe debounce this in the future
|
||||||
await AsyncStorage.setItem(STORAGE_ROOT_KEY, JSON.stringify(data))
|
await AsyncStorage.setItem(STORAGE_ROOT_KEY, JSON.stringify(data))
|
||||||
|
return true
|
||||||
} catch (err) {
|
} 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
|
* The shape of the object we store in local storage
|
||||||
*/
|
*/
|
||||||
@@ -7,64 +5,10 @@ export type Schema = {
|
|||||||
shell: {
|
shell: {
|
||||||
colorMode: 'system' | 'light' | 'dark'
|
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}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const defaultData: Schema = {
|
||||||
* A map of all the properties that can be stored in the storage, and their
|
shell: {
|
||||||
* values. This mapping must be updated in order to get/set values by their
|
colorMode: 'system',
|
||||||
* keypaths.
|
},
|
||||||
*/
|
|
||||||
export type PropertyMap = {
|
|
||||||
'shell.colorMode': Schema['shell']['colorMode']
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user