react-native-mmkv
Version:
⚡️ The fastest key/value storage for React Native.
154 lines (147 loc) • 4.5 kB
text/typescript
import type { MMKV } from '../specs/MMKV.nitro'
import type { Configuration } from '../specs/MMKVFactory.nitro'
import { createTextDecoder } from '../web/createTextDecoder'
import { createTextEncoder } from '../web/createTextEncoder'
import {
getLocalStorage,
LOCAL_STORAGE_KEY_WILDCARD,
} from '../web/getLocalStorage'
export function createMMKV(
config: Configuration = { id: 'mmkv.default' }
): MMKV {
if (config.encryptionKey != null) {
throw new Error("MMKV: 'encryptionKey' is not supported on Web!")
}
if (config.path != null) {
throw new Error("MMKV: 'path' is not supported on Web!")
}
const textDecoder = createTextDecoder()
const textEncoder = createTextEncoder()
const listeners = new Set<(key: string) => void>()
if (config.id.includes(LOCAL_STORAGE_KEY_WILDCARD)) {
throw new Error('MMKV: `id` cannot contain the backslash character (`\\`)!')
}
const keyPrefix = `${config.id}${LOCAL_STORAGE_KEY_WILDCARD}` // mmkv.default\\
const prefixedKey = (key: string) => {
if (key.includes('\\')) {
throw new Error(
'MMKV: `key` cannot contain the backslash character (`\\`)!'
)
}
return `${keyPrefix}${key}`
}
const callListeners = (key: string) => {
listeners.forEach((l) => l(key))
}
return {
id: config.id,
get length(): number {
return getLocalStorage().length
},
get size(): number {
return this.byteSize
},
get byteSize(): number {
// estimate - assumes UTF8
return JSON.stringify(getLocalStorage()).length
},
isReadOnly: false,
isEncrypted: false,
clearAll: () => {
const storage = getLocalStorage()
const keys = Object.keys(storage)
for (const key of keys) {
if (key.startsWith(keyPrefix)) {
storage.removeItem(key)
callListeners(key)
}
}
},
remove: (key) => {
const storage = getLocalStorage()
storage.removeItem(prefixedKey(key))
const wasRemoved = storage.getItem(prefixedKey(key)) === null
if (wasRemoved) callListeners(key)
return wasRemoved
},
set: (key, value) => {
const storage = getLocalStorage()
if (key === '') throw new Error('Cannot set a value for an empty key!')
if (value instanceof ArrayBuffer) {
storage.setItem(prefixedKey(key), textDecoder.decode(value))
} else {
storage.setItem(prefixedKey(key), String(value))
}
callListeners(key)
},
getString: (key) => {
const storage = getLocalStorage()
return storage.getItem(prefixedKey(key)) ?? undefined
},
getNumber: (key) => {
const storage = getLocalStorage()
const value = storage.getItem(prefixedKey(key))
if (value == null) return undefined
return Number(value)
},
getBoolean: (key) => {
const storage = getLocalStorage()
const value = storage.getItem(prefixedKey(key))
if (value == null) return undefined
return value === 'true'
},
getBuffer: (key) => {
const storage = getLocalStorage()
const value = storage.getItem(prefixedKey(key))
if (value == null) return undefined
return textEncoder.encode(value).buffer
},
getAllKeys: () => {
const storage = getLocalStorage()
const keys = Object.keys(storage)
return keys
.filter((key) => key.startsWith(keyPrefix))
.map((key) => key.slice(keyPrefix.length))
},
contains: (key) => {
const storage = getLocalStorage()
return storage.getItem(prefixedKey(key)) != null
},
recrypt: () => {
throw new Error('`recrypt(..)` is not supported on Web!')
},
encrypt: () => {
throw new Error('`encrypt(..)` is not supported on Web!')
},
decrypt: () => {
throw new Error('`decrypt(..)` is not supported on Web!')
},
trim: () => {
// no-op
},
dispose: () => {},
equals: () => false,
name: 'MMKV',
addOnValueChangedListener: (listener) => {
listeners.add(listener)
return {
remove: () => {
listeners.delete(listener)
},
}
},
importAllFrom: (other) => {
const storage = getLocalStorage()
const keys = other.getAllKeys()
let imported = 0
for (const key of keys) {
const string = other.getString(key)
if (string != null) {
storage.set(key, string)
imported++
}
}
return imported
},
}
}