Compare commits

...

8 Commits

Author SHA1 Message Date
Eric Bailey bd87df1957 Remove unused export 2023-11-06 13:40:08 -06:00
Eric Bailey 20065949c9 Remove unused lodash deps 2023-11-06 13:39:50 -06:00
Eric Bailey 4dd425fdec Comments 2023-11-06 13:38:16 -06:00
Eric Bailey bdb9082e7e Refinement 2023-11-06 13:31:03 -06:00
dan ff946c59ec Update index.ts 2023-11-06 17:26:23 +00:00
Eric Bailey 2570fb82fa Comment out unused schema for now 2023-11-06 11:11:09 -06:00
Eric Bailey af9aafca16 Revise according to discussion 2023-11-06 11:10:03 -06:00
Eric Bailey b56415356c Add async storage interface 2023-11-05 13:03:08 -06:00
3 changed files with 124 additions and 0 deletions
+41
View File
@@ -0,0 +1,41 @@
import {jest, beforeAll, afterEach, expect, test} from '@jest/globals'
// has a built-in mock
import AsyncStorage from '@react-native-async-storage/async-storage'
import * as storage from '#/storage'
beforeAll(async () => {
await storage.init()
jest.mocked(AsyncStorage.getItem).mockClear()
})
afterEach(() => {
jest.mocked(AsyncStorage.getItem).mockClear()
})
test(`gets and sets data synchronously`, async () => {
storage.set('shell', {colorMode: 'light'})
expect(AsyncStorage.setItem).toHaveBeenCalledWith(
storage.ROOT_STATE_STORAGE_KEY,
JSON.stringify({shell: {colorMode: 'light'}}),
)
storage.set('shell', {colorMode: 'light'})
expect(AsyncStorage.getItem).not.toHaveBeenCalled()
storage.set('shell', {colorMode: 'dark'})
// @ts-expect-error
expect(storage.get('shell').colorMode).toBe('dark')
})
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'})
// @ts-expect-error
expect(storage.get('shell').colorMode).toBe('system')
})
+64
View File
@@ -0,0 +1,64 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import {logger} from '#/logger'
import {Schema, defaultData} from '#/storage/schema'
/**
* The key we use to store our data in local storage.
*
* This value exists in `src/state/index.ts` also, but that file loads every mobx
* store too, so it's duplicated here so that tests run faster.
*/
export const ROOT_STATE_STORAGE_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 = defaultData
/**
* Loads data from local storage into memory for synchronous access. This
* should be called once at the root of the application.
*/
export async function init() {
logger.debug(`storage initializing`)
try {
const raw = await AsyncStorage.getItem(ROOT_STATE_STORAGE_KEY)
data = (raw ? JSON.parse(raw) : {}) as Schema
} catch (e) {
logger.error(`storage init() failed`)
}
}
/**
* Get a value from local storage. Returns `unknown` type to force us to
* validate and cast to the types we expect.
*/
export function get<T extends keyof Schema>(key: T): unknown {
logger.debug(`storage get(${key})`)
return data[key]
}
/**
* Set a value on local storage. When setting objects, you need to manually
* merge in new values.
*/
export async function set<T extends keyof Schema>(key: T, value: Schema[T]) {
logger.debug(`storage set(${key}, value)`)
data[key] = value
try {
// TODO maybe debounce this in the future
await AsyncStorage.setItem(ROOT_STATE_STORAGE_KEY, JSON.stringify(data))
return true
} catch (err) {
logger.error(`storage set(${key}, value) failed`)
return false
}
}
+19
View File
@@ -0,0 +1,19 @@
/**
* The shape of the object we store in local storage.
*/
export type Schema = {
shell: {
colorMode: 'system' | 'light' | 'dark'
}
}
/**
* The default values for the schema. This is used to initialize the store, and
* is used in case AsyncStorage is unavailable. This should be kept in sync
* with the Schema type above.
*/
export const defaultData: Schema = {
shell: {
colorMode: 'system',
},
}