prorage v1.1.1
Prorage
中文 | English
Storage used like object, Based on ES6 API Proxy and @vue/reactivity.
Prorage = Proxy + Storage
Contents
Features
- Storage used like object
- Customizable, most customization can be achieved by Plugins.
- No side effects, supports Tree Shaking.
- Based on
@vue/reactivity, it can be used well with Vue. - Better for Vue projects that without Vuex/Pinia, but can also be used without Vue.
Quick Start
Installation
npm install @vue/reactivity
npm install prorageIf you have already installed Vue, you do not need to install @vue/reactivity.
Usage
import { createStorage } from 'prorage'
const storage = createStorage()
storage.foo = 'foo'
delete foo
storage.bar = []
storage.bar.push('hello')createStorage
Options
import { createStorage, expiresPlugin } from 'prorage'
const storage = createStorage({
storage: localStorage,
stringify: JSON.stringify,
parse: JSON.parse,
saveFlush: 'async',
plugins: [expiresPlugin()],
prefix: 'prefix#',
})| Parameter | Type | Default | Description |
|---|---|---|---|
| storage | StorageLike | localStorage | Storage Object |
| stringify | StringifyLike | JSON.stringify | Method to convert to JSON |
| parse | ParseLike | JSON.parse | Method to parse JSON |
| saveFlush | "sync" | "async" | "async" | Timing of save execution |
| plugins | ProragePlugin[] | [] | Plugins |
| prefix | string | Prefix of storage key name |
StorageLike, such aslocalStorageandsessionStorage, should have methods:getItem,setItemandremoveItem.getItemmust be a synchronous method.- When
saveFlushis set toasync, multiple operations will be merged into one save,syncwill save immediately after each operation. - For information on
plugins, please refer to the Built-in Plugin and Plugin Development.
API
storage.clear
clear data.
storage.clear() // Clear all data (data that matches the prefix)
storage.clear('foo') // Clear data under the 'foo' namespacestorage.reload
reload data.
storage.reload('foo')It should be noted that old data will be out of control (overwritten). For example:
storage.test = { a: 1 }
const temp = storage.test
temp.a = 2 // it works
storage.reload('test')
temp.a = 3 // it not works
temp === storage.test // false在调用 reload 后 storage.test 上的引用被替换为重新读取的数据, 原先储存的 temp 不再是 storage.test 的数据. 故再对 temp 对象进行修改, 不会再影响 storage.test.
After run reload, the reference to storage.test is replaced with the reloaded data, and temp isn't the data of storage.test. So, modifying the temp, can't affect storage.test.
with storage event
addEventListener('storage', ({ key }) => storage.reload(key))storage.save
Save data.
storage.save('foo')watch
Similar to the watch function in Vue, but with some limitations. This API is mainly aimed at projects that without Vue.
watch(
() => storage,
() => {
console.log('change')
}, {
deep: true,
flush: 'async'
}
)- The first parameter
source, only supports function. - The second parameter
callback, no provideoldValueandnewValue. - The third parameter
options,deepandimmediateare same as Vue.flushonly supportssyncandasync,syncis same as Vue,asyncis executed asynchronously.
Built-in Plugin
extraPlugin
Add extra properties to the data. As a basic Plugin, it does not need to be declared for use.
import { createStorage, useExtra } from 'prorage'
const storage = createStorage()
storage.foo = useExtra('bar', {
test: 'hello world'
})
getExtra(storage, 'foo') // { test: 'hello world' }API
useExtra
create a data with extra properties.
function useExtra<T>(value: T, extra: Record<string, unknown>): TgetExtra
get the extra properties of key on target.
function getExtra(target: object, key: string | symbol): Record<string, unknown>expiresPlugin
add expires to the data.
import { createStorage, expiresPlugin, useExpires } from 'prorage'
const storage = createStorage({
plugins: [
expiresPlugin({
checkInterval: 1000,
})
]
})
storage.foo = useExpires('bar', { days: 7 })Options
| Parameter | Type | Default | Description | ||
|---|---|---|---|---|---|
| checkInterval | 'none' | 'raf' | number | "none" | Interval of checking expires |
- Expired data is not immediately deleted, but will be deleted when be getted.
- If
checkIntervalis"raf"or a number, the data will be periodically checked for expires usingrequestAnimationFrame/setTimeout. Only the data that has been getted will be added to the check queue. - When using
setTimeout, there is a compensation mechanism. Whenexpires - Date.now()is greater thancheckInterval, the next execution delay will beexpires - Date.now().
API
useExpires
function useExpires<T>(value: T, expires: ExpiresDate): T
type ExpiresDate = number | Date | ExpiresDateOptions
type ExpiresDateOptions = {
milliseconds?: number
seconds?: number
minutes?: number
hours?: number
days?: number
months?: number
years?: number
}- If
expiresisnumber, timestamp, absolute time for expires. - If
expiresisDate, absolute time for expires. - If
expiresisExpiresDateOptions, it represents the relative time for expiration (relative to now).monthsis calculated based on the natural month, and is not equal to 30 days.
translatePlugin
Translate data to a format that is better for storage.
import { createStorage, translatePlugin } from 'prorage'
const storage = createStorage({
plugins: [translatePlugin()]
})
storage.foo = new Date()
storage.bar = 123n
storage.baz = /test/gi
storage.qux = InfinityDefault Supports Types: BigInt, NaN/Infinity/-Infinity, Date, RegExp.
Options
| Parameters | Type | Default | Description |
|---|---|---|---|
| dictionary | TranslateDictionary[] | [] | dictionary for translate |
dictionary
import { createStorage, translatePlugin } from 'prorage'
const storage = createStorage({
plugins: [
translatePlugin({
dictionary: [
{
name: 'Symbol',
test: (value) => typeof value === 'symbol',
stringify: (value) => value.toString(),
parse: (value) => {
const _Symbol = (value) => {
try {
return new Function(`return ${value}`)()
} catch (e) {
return typeof value === 'symbol'
? value
: Symbol.for(String(value))
}
}
const _value = value.replace(/^Symbol\((.*)\)$/, '_Symbol("$1")')
return new Function('_Symbol', `return ${_value}`)(_Symbol)
},
}
]
})
]
})
storage.foo = Symbol.for('123')| Parameters | Type | Description |
|---|---|---|
| name | string | Unique identifier |
| test | (value: unknown) => boolean | Test the value, if return true, the value will be translated by the dictionary |
| stringify | (value: any) => any | Translate to storage format |
| parse | (value: any) => any | Restores data |
name需要唯一, 内置的标识有:BigInt,Number,Date,RegExp.按数组顺序进行
test(内置的追加在数组末尾), 匹配后该数据将不再进行其他转换操作.nameis unique. The built-in name are:BigInt,Number,Date,RegExp.- The
testis runed in the order of the array (built-ins are appended to array end), if returntrue, the data don't try other translate.
Other
Data Types Support
Keys support is same as object, but symbol will be ignored when JSON.stringify.
Values of support:
Primitives
| Type | Basic Support | With translatePlugin |
|---|---|---|
| undefined | ✔️ | ✔️ |
| null | ✔️ | ✔️ |
| String | ✔️ | ✔️ |
| Boolean | ✔️ | ✔️ |
| Number | ✔️ | ✔️ |
| BigInt | ❌ | ✔️ |
| Symbol | ❌ | can support Symbol.for (user configurable) |
Objects
| Type | Basic Support | With translatePlugin | Description |
|---|---|---|---|
| Basic Object | ✔️ | ✔️ | |
| Array | ✔️ | ✔️ | |
| Date | ❌ | ✔️ | |
| RegExp | ❌ | ✔️ | |
| Function | ❌ | Barely supported (user configurable) | scopes will lose |
| Set | ❌ | ❌ | Code cost more than the benefits |
| Map | ❌ | ❌ | Code cost more than the benefits |
| WeakSet | ❌ | ❌ | No value |
| WeakMap | ❌ | ❌ | No value |
Plugin Development
Circular Object
You can use JSON library like flatted to solve the problem of circular object.
import { stringify, parse, } from 'flatted'
import { createStorage } from 'prorage'
const storage = createStorage({
stringify,
parse,
})
storage.test = {}
storage.test.circular = storage.testWith TypeScript
import { createStorage } from 'prorage'
type MyStorage = {
foo: string
bar: number
}
const storage = createStorage<MyStorage>()With React
Like @vue/reactivity with React. Simple example: Prorage With React - StackBlitz.
Why can't trigger Vue update
If you use the vue.xxx.global.js or vue.xxx-browser.js version of Vue, it will cause dependency @vue/reactivity to not be the same code, resulting in independent trigger events between the two. Avoid using these two versions of Vue.