Skip to content

Commit

Permalink
feat(useStorageAsync): new function
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Dec 12, 2021
1 parent be9828e commit 169b02b
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 21 deletions.
19 changes: 19 additions & 0 deletions packages/core/useStorage/guess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function guessSerializerType<T extends(string | number | boolean | object | null)>(rawInit: T) {
return rawInit == null
? 'any'
: rawInit instanceof Set
? 'set'
: rawInit instanceof Map
? 'map'
: typeof rawInit === 'boolean'
? 'boolean'
: typeof rawInit === 'string'
? 'string'
: typeof rawInit === 'object'
? 'object'
: Array.isArray(rawInit)
? 'object'
: !Number.isNaN(rawInit)
? 'number'
: 'any'
}
41 changes: 20 additions & 21 deletions packages/core/useStorage/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { ConfigurableFlush, watchWithFilter, ConfigurableEventFilter, MaybeRef, RemovableRef } from '@vueuse/shared'
import { ConfigurableFlush, watchWithFilter, ConfigurableEventFilter, MaybeRef, RemovableRef, Awaitable } from '@vueuse/shared'
import { ref, Ref, unref, shallowRef } from 'vue-demi'
import { useEventListener } from '../useEventListener'
import { ConfigurableWindow, defaultWindow } from '../_configurable'
import { guessSerializerType } from './guess'

export type Serializer<T> = {
read(raw: string): T
write(value: T): string
}

export type SerializerAsync<T> = {
read(raw: string): Awaitable<T>
write(value: T): Awaitable<string>
}

export interface StorageLikeAsync {
getItem(key: string): Awaitable<string | null>
setItem(key: string, value: string): Awaitable<void>
removeItem(key: string): Awaitable<void>
}

export interface StorageLike {
getItem(key: string): string | null
setItem(key: string, value: string): void
removeItem(key: string): void
}

export const StorageSerializers: Record<'boolean' | 'object' | 'number' | 'any' | 'string' | 'map' | 'set', Serializer<any>> = {
boolean: {
read: (v: any) => v === 'true',
Expand Down Expand Up @@ -39,8 +57,6 @@ export const StorageSerializers: Record<'boolean' | 'object' | 'number' | 'any'
},
}

export type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>

export interface StorageOptions<T> extends ConfigurableEventFilter, ConfigurableWindow, ConfigurableFlush {
/**
* Watch for deep changes
Expand Down Expand Up @@ -118,24 +134,7 @@ export function useStorage<T extends(string|number|boolean|object|null)> (
} = options

const rawInit: T = unref(initialValue)

const type = rawInit == null
? 'any'
: rawInit instanceof Set
? 'set'
: rawInit instanceof Map
? 'map'
: typeof rawInit === 'boolean'
? 'boolean'
: typeof rawInit === 'string'
? 'string'
: typeof rawInit === 'object'
? 'object'
: Array.isArray(rawInit)
? 'object'
: !Number.isNaN(rawInit)
? 'number'
: 'any'
const type = guessSerializerType<T>(rawInit)

const data = (shallow ? shallowRef : ref)(initialValue) as Ref<T>
const serializer = options.serializer ?? StorageSerializers[type]
Expand Down
9 changes: 9 additions & 0 deletions packages/core/useStorageAsync/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
category: State
---

# useStorageAsync

Reactive Storage in with async support.

Prefer to `useStorage`.
103 changes: 103 additions & 0 deletions packages/core/useStorageAsync/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { watchWithFilter, MaybeRef, RemovableRef } from '@vueuse/shared'
import { ref, Ref, unref, shallowRef } from 'vue-demi'
import { SerializerAsync, StorageLikeAsync, StorageOptions, StorageSerializers } from '../useStorage'
import { useEventListener } from '../useEventListener'
import { guessSerializerType } from '../useStorage/guess'
import { defaultWindow } from '../_configurable'

export interface StorageAsyncOptions<T> extends Omit<StorageOptions<T>, 'serializer'> {
/**
* Custom data serialization
*/
serializer?: SerializerAsync<T>
}

export function useStorageAsync(key: string, initialValue: MaybeRef<string>, storage?: StorageLikeAsync, options?: StorageAsyncOptions<string>): RemovableRef<string>
export function useStorageAsync(key: string, initialValue: MaybeRef<boolean>, storage?: StorageLikeAsync, options?: StorageAsyncOptions<boolean>): RemovableRef<boolean>
export function useStorageAsync(key: string, initialValue: MaybeRef<number>, storage?: StorageLikeAsync, options?: StorageAsyncOptions<number>): RemovableRef<number>
export function useStorageAsync<T> (key: string, initialValue: MaybeRef<T>, storage?: StorageLikeAsync, options?: StorageAsyncOptions<T>): RemovableRef<T>
export function useStorageAsync<T = unknown> (key: string, initialValue: MaybeRef<null>, storage?: StorageLikeAsync, options?: StorageAsyncOptions<T>): RemovableRef<T>

/**
* Reactive Storage in with async support.
*
* @see https://vueuse.org/useStorageAsync
* @param key
* @param initialValue
* @param storage
* @param options
*/
export function useStorageAsync<T extends(string|number|boolean|object|null)> (
key: string,
initialValue: MaybeRef<T>,
storage: StorageLikeAsync | undefined = defaultWindow?.localStorage,
options: StorageAsyncOptions<T> = {},
): RemovableRef<T> {
const {
flush = 'pre',
deep = true,
listenToStorageChanges = true,
writeDefaults = true,
shallow,
window = defaultWindow,
eventFilter,
onError = (e) => {
console.error(e)
},
} = options

const rawInit: T = unref(initialValue)
const type = guessSerializerType<T>(rawInit)

const data = (shallow ? shallowRef : ref)(initialValue) as Ref<T>
const serializer = options.serializer ?? StorageSerializers[type]

async function read(event?: StorageEvent) {
if (!storage || (event && event.key !== key))
return

try {
const rawValue = event ? event.newValue : await storage.getItem(key)
if (rawValue == null) {
data.value = rawInit
if (writeDefaults && rawInit !== null)
await storage.setItem(key, await serializer.write(rawInit))
}
else {
data.value = await serializer.read(rawValue)
}
}
catch (e) {
onError(e)
}
}

read()

if (window && listenToStorageChanges)
useEventListener(window, 'storage', e => setTimeout(() => read(e), 0))

if (storage) {
watchWithFilter(
data,
async() => {
try {
if (data.value == null)
await storage.removeItem(key)
else
await storage.setItem(key, await serializer.write(data.value))
}
catch (e) {
onError(e)
}
},
{
flush,
deep,
eventFilter,
},
)
}

return data as RemovableRef<T>
}
2 changes: 2 additions & 0 deletions packages/shared/utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export type ElementOf<T> = T extends (infer E)[] ? E : never

export type ShallowUnwrapRef<T> = T extends Ref<infer P> ? P : T

export type Awaitable<T> = Promise<T> | T

export interface Pausable {
/**
* A ref indicate whether a pusable instance is active
Expand Down

0 comments on commit 169b02b

Please sign in to comment.