1.1.1 • Published 11 months ago

prorage v1.1.1

Weekly downloads
-
License
MIT
Repository
github
Last release
11 months ago

Prorage

中文 | English

Storage used like object, Based on ES6 API Proxy and @vue/reactivity.

Prorage = Proxy + Storage

Playground: Stackblitz

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 prorage

If 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#',
})
ParameterTypeDefaultDescription
storageStorageLikelocalStorageStorage Object
stringifyStringifyLikeJSON.stringifyMethod to convert to JSON
parseParseLikeJSON.parseMethod to parse JSON
saveFlush"sync" | "async""async"Timing of save execution
pluginsProragePlugin[][]Plugins
prefixstringPrefix of storage key name
  • StorageLike, such as localStorage and sessionStorage, should have methods: getItem, setItem and removeItem. getItem must be a synchronous method.
  • When saveFlush is set to async, multiple operations will be merged into one save, sync will 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' namespace

storage.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

在调用 reloadstorage.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 provide oldValue and newValue.
  • The third parameter options, deep and immediate are same as Vue. flush only supports sync and async, sync is same as Vue, async is 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>): T
getExtra

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

ParameterTypeDefaultDescription
checkInterval'none''raf'number"none"Interval of checking expires
  • Expired data is not immediately deleted, but will be deleted when be getted.
  • If checkInterval is "raf" or a number, the data will be periodically checked for expires using requestAnimationFrame/setTimeout. Only the data that has been getted will be added to the check queue.
  • When using setTimeout, there is a compensation mechanism. When expires - Date.now() is greater than checkInterval, the next execution delay will be expires - 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 expires is number, timestamp, absolute time for expires.
  • If expires is Date, absolute time for expires.
  • If expires is ExpiresDateOptions, it represents the relative time for expiration (relative to now). months is 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 = Infinity

Default Supports Types: BigInt, NaN/Infinity/-Infinity, Date, RegExp.

Options

ParametersTypeDefaultDescription
dictionaryTranslateDictionary[][]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')
ParametersTypeDescription
namestringUnique identifier
test(value: unknown) => booleanTest the value, if return true, the value will be translated by the dictionary
stringify(value: any) => anyTranslate to storage format
parse(value: any) => anyRestores data
  • name 需要唯一, 内置的标识有: BigInt, Number, Date, RegExp.
  • 按数组顺序进行 test (内置的追加在数组末尾), 匹配后该数据将不再进行其他转换操作.

  • name is unique. The built-in name are: BigInt, Number, Date, RegExp.

  • The test is runed in the order of the array (built-ins are appended to array end), if return true, 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

TypeBasic SupportWith translatePlugin
undefined✔️✔️
null✔️✔️
String✔️✔️
Boolean✔️✔️
Number✔️✔️
BigInt✔️
Symbolcan support Symbol.for (user configurable)

Objects

TypeBasic SupportWith translatePluginDescription
Basic Object✔️✔️
Array✔️✔️
Date✔️
RegExp✔️
FunctionBarely supported (user configurable)scopes will lose
SetCode cost more than the benefits
MapCode cost more than the benefits
WeakSetNo value
WeakMapNo value

Plugin Development

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.test

With 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.

1.1.1

11 months ago

1.1.0

11 months ago

1.0.0

11 months ago

0.1.0

12 months ago

0.3.0

12 months ago

0.2.0

12 months ago

0.4.1

12 months ago

0.4.0

12 months ago

0.3.1

12 months ago

0.0.2

12 months ago

0.0.1

12 months ago