mirror of
https://github.com/unjs/unstorage.git
synced 2025-12-08 21:26:09 +00:00
initial commit
This commit is contained in:
commit
fa687aa697
15
.editorconfig
Normal file
15
.editorconfig
Normal file
@ -0,0 +1,15 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{package.json,*.yml,*.cjson}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
5
.eslintrc
Normal file
5
.eslintrc
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nuxtjs/eslint-config-typescript"
|
||||
]
|
||||
}
|
||||
54
.github/workflows/ci.yml
vendored
Normal file
54
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
name: ci
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [14]
|
||||
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile --non-interactive
|
||||
|
||||
- name: Lint
|
||||
run: yarn lint
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
|
||||
- name: Test
|
||||
run: yarn jest
|
||||
|
||||
- name: Coverage
|
||||
uses: codecov/codecov-action@v1
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
.vscode
|
||||
node_modules
|
||||
*.log
|
||||
.DS_Store
|
||||
coverage
|
||||
dist
|
||||
tmp
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Pooya Parsa <pyapar@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
168
README.md
Normal file
168
README.md
Normal file
@ -0,0 +1,168 @@
|
||||
# unstorage
|
||||
|
||||
[![npm version][npm-version-src]][npm-version-href]
|
||||
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
||||
[![Github Actions][github-actions-src]][github-actions-href]
|
||||
[![Codecov][codecov-src]][codecov-href]
|
||||
[![bundle][bundle-src]][bundle-href]
|
||||
|
||||
> Universal key-value Storage
|
||||
|
||||
- Works in all environments (Browser, NodeJS and Workers)
|
||||
- Asynchronous API
|
||||
- Unix-style mountable paths (multi provider)
|
||||
- Default in-memory storage
|
||||
- Tree-shakable and lightweight core
|
||||
|
||||
WIP:
|
||||
|
||||
- JSON serialization and native types (destr)
|
||||
- State compression
|
||||
- State hydration
|
||||
- Links (soft/junction)
|
||||
- Binary data
|
||||
- `getKeys` and `clear` with sub-path
|
||||
- IPC/HTTP interface
|
||||
- Virtual fs (fs-like interface)
|
||||
- Watcher
|
||||
- Reactivity
|
||||
- Basic array operations
|
||||
|
||||
## Providers
|
||||
|
||||
- [x] Memory (Universal)
|
||||
- [x] Filesystem (NodeJS)
|
||||
- [x] Nested
|
||||
- [ ] Flat
|
||||
- [x] LocalStorage (Browser)
|
||||
- [ ] Cookies (Browser)
|
||||
- [ ] Location P
|
||||
- [ ] HTTP (Universal)
|
||||
- [ ] S3
|
||||
- [ ] Cloudflare KV
|
||||
- [ ] Github
|
||||
|
||||
## Usage
|
||||
|
||||
Install `unistorage` npm package:
|
||||
|
||||
```sh
|
||||
yarn add unistorage
|
||||
# or
|
||||
npm i unistorage
|
||||
```
|
||||
|
||||
```js
|
||||
import { createStorage } from 'unistorage'
|
||||
|
||||
// Create a storage container with default memory storage
|
||||
const storage = createStorage()
|
||||
|
||||
await storage.getItem('foo:bar')
|
||||
// or
|
||||
await storage.getItem('/foo/bar')
|
||||
```
|
||||
|
||||
### `storage.hasItem(key)`
|
||||
|
||||
Checks if storage contains a key. Resolves to either `true` or `false`.
|
||||
|
||||
```js
|
||||
await storage.hasItem('foo:bar')
|
||||
```
|
||||
|
||||
### `storage.getItem(key)`
|
||||
|
||||
Gets value of a key in storage. Resolves to either `string` or `null`.
|
||||
|
||||
```js
|
||||
await storage.getItem('foo:bar')
|
||||
```
|
||||
|
||||
### `storage.setItem(key, value)`
|
||||
|
||||
Add Update a value to storage.
|
||||
|
||||
```js
|
||||
await storage.setItem('foo:bar', 'baz')
|
||||
```
|
||||
|
||||
### `storage.removeItem(key)`
|
||||
|
||||
Remove a value from storage.
|
||||
|
||||
```js
|
||||
await storage.removeItem('foo:bar')
|
||||
```
|
||||
|
||||
### `storage.getKeys()`
|
||||
|
||||
Get all keys. Returns an array of `string`.
|
||||
|
||||
```js
|
||||
await storage.getKeys()
|
||||
```
|
||||
|
||||
### `storage.clear()`
|
||||
|
||||
Removes all stored key/values.
|
||||
|
||||
```js
|
||||
await storage.clear()
|
||||
```
|
||||
|
||||
### `storage.mount(mountpoint, provider)`
|
||||
|
||||
By default, everything is stored in memory. We can mount additional storage space in a Unix-like fashion.
|
||||
|
||||
When operating with a `key` that starts with mountpoint, instead of default storage, mounted provider will be called.
|
||||
|
||||
```js
|
||||
import { createStorage, fsStorage } from 'unistorage'
|
||||
|
||||
// Create a storage container with default memory storage
|
||||
const storage = createStorage()
|
||||
|
||||
storage.mount('/output', fsStorage({ dir: './output' }))
|
||||
|
||||
// Writes to ./output/test file
|
||||
await storage.setItem('/output/test', 'works')
|
||||
|
||||
// Adds value to in-memory storage
|
||||
await storage.setItem('/foo', 'bar')
|
||||
```
|
||||
|
||||
### `storage.dispose()`
|
||||
|
||||
Disposes all mounted storages to ensure there are no open-handles left. Call it before exiting process.
|
||||
|
||||
```js
|
||||
await storage.dispose()
|
||||
```
|
||||
|
||||
## Contribution
|
||||
|
||||
- Clone repository
|
||||
- Install dependencies with `yarn install`
|
||||
- Use `yarn dev` to start jest watcher verifying changes
|
||||
- Use `yarn test` before push to ensure all tests and lint checks passing
|
||||
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE)
|
||||
|
||||
<!-- Badges -->
|
||||
[npm-version-src]: https://img.shields.io/npm/v/unstorage?style=flat-square
|
||||
[npm-version-href]: https://npmjs.com/package/unstorage
|
||||
|
||||
[npm-downloads-src]: https://img.shields.io/npm/dm/unstorage?style=flat-square
|
||||
[npm-downloads-href]: https://npmjs.com/package/unstorage
|
||||
|
||||
[github-actions-src]: https://img.shields.io/github/workflow/status/unjsio/unstorage/ci/main?style=flat-square
|
||||
[github-actions-href]: https://github.com/unjsio/unstorage/actions?query=workflow%3Aci
|
||||
|
||||
[codecov-src]: https://img.shields.io/codecov/c/gh/unjsio/unstorage/main?style=flat-square
|
||||
[codecov-href]: https://codecov.io/gh/unjsio/unstorage
|
||||
|
||||
[bundle-src]: https://img.shields.io/bundlephobia/minzip/unstorage?style=flat-square
|
||||
[bundle-href]: https://bundlephobia.com/result?p=unstorage
|
||||
5
jest.config.js
Normal file
5
jest.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
collectCoverage: true,
|
||||
testEnvironment: 'node'
|
||||
}
|
||||
37
package.json
Normal file
37
package.json
Normal file
@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "unstorage",
|
||||
"version": "0.0.0",
|
||||
"description": "",
|
||||
"repository": "unjsio/unstorage",
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "siroc build",
|
||||
"dev": "jest --watch",
|
||||
"lint": "eslint --ext .ts .",
|
||||
"prepublishOnly": "yarn build",
|
||||
"release": "yarn test && standard-version && git push --follow-tags && npm publish",
|
||||
"test": "yarn lint && jest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nuxtjs/eslint-config-typescript": "latest",
|
||||
"@types/jest": "latest",
|
||||
"@types/jsdom": "^16.2.6",
|
||||
"@types/node": "latest",
|
||||
"eslint": "latest",
|
||||
"jest": "latest",
|
||||
"jiti": "latest",
|
||||
"jsdom": "^16.4.0",
|
||||
"siroc": "latest",
|
||||
"standard-version": "latest",
|
||||
"ts-jest": "latest",
|
||||
"typescript": "latest",
|
||||
"vite": "^2.0.5"
|
||||
}
|
||||
}
|
||||
5
renovate.json
Normal file
5
renovate.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nuxtjs"
|
||||
]
|
||||
}
|
||||
3
src/index.ts
Normal file
3
src/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './storage'
|
||||
export * from './types'
|
||||
export * from './providers'
|
||||
38
src/providers/fs.ts
Normal file
38
src/providers/fs.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import { existsSync } from 'fs'
|
||||
import { resolve } from 'path'
|
||||
import type { StorageProviderFactory } from '../types'
|
||||
import { readFile, writeFile, readdirRecursive, rmRecursive, unlink } from '../utils/node-fs'
|
||||
|
||||
export interface FSStorageOptions {
|
||||
dir: string
|
||||
}
|
||||
|
||||
export const fsStorage: StorageProviderFactory = (opts: FSStorageOptions) => {
|
||||
if (!opts.dir) {
|
||||
throw new Error('dir is required')
|
||||
}
|
||||
|
||||
const r = (key: string) => resolve(opts.dir, key.replace(/:/g, '/'))
|
||||
|
||||
return {
|
||||
hasItem (key) {
|
||||
return existsSync(r(key))
|
||||
},
|
||||
getItem (key) {
|
||||
return readFile(r(key))
|
||||
},
|
||||
setItem (key, value) {
|
||||
return writeFile(r(key), value)
|
||||
},
|
||||
removeItem (key) {
|
||||
return unlink(r(key))
|
||||
},
|
||||
getKeys () {
|
||||
return readdirRecursive(r('.'))
|
||||
},
|
||||
async clear () {
|
||||
await rmRecursive(r('.'))
|
||||
},
|
||||
dispose () {}
|
||||
}
|
||||
}
|
||||
3
src/providers/index.ts
Normal file
3
src/providers/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './fs'
|
||||
export * from './memory'
|
||||
export * from './local'
|
||||
33
src/providers/local.ts
Normal file
33
src/providers/local.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import type { StorageProviderFactory } from '../types'
|
||||
|
||||
export interface LocalStorageOptions {
|
||||
localStorage?: typeof window.localStorage
|
||||
}
|
||||
|
||||
export const localStorage: StorageProviderFactory = (opts?: LocalStorageOptions) => {
|
||||
const _localStorage = opts?.localStorage || (globalThis.localStorage as typeof window.localStorage)
|
||||
if (!_localStorage) {
|
||||
throw new Error('localStorage not available')
|
||||
}
|
||||
|
||||
return {
|
||||
hasItem (key) {
|
||||
return Object.prototype.hasOwnProperty.call(_localStorage, key)
|
||||
},
|
||||
getItem (key) {
|
||||
return _localStorage.getItem(key)
|
||||
},
|
||||
setItem (key, value) {
|
||||
return _localStorage.setItem(key, value)
|
||||
},
|
||||
removeItem (key) {
|
||||
return _localStorage.removeItem(key)
|
||||
},
|
||||
getKeys () {
|
||||
return Object.keys(_localStorage)
|
||||
},
|
||||
clear () {
|
||||
_localStorage.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/providers/memory.ts
Normal file
29
src/providers/memory.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import type { StorageProviderFactory, StorageValue } from '../types'
|
||||
|
||||
export const memoryStorage: StorageProviderFactory = () => {
|
||||
const data = new Map<string, StorageValue>()
|
||||
|
||||
return {
|
||||
hasItem (key) {
|
||||
return data.has(key)
|
||||
},
|
||||
getItem (key) {
|
||||
return data.get(key) || null
|
||||
},
|
||||
setItem (key, value) {
|
||||
data.set(key, value)
|
||||
},
|
||||
removeItem (key) {
|
||||
data.delete(key)
|
||||
},
|
||||
getKeys () {
|
||||
return Array.from(data.keys())
|
||||
},
|
||||
clear () {
|
||||
data.clear()
|
||||
},
|
||||
dispose () {
|
||||
data.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/storage.ts
Normal file
73
src/storage.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import type { Storage, StorageProvider } from './types'
|
||||
import { memoryStorage } from './providers'
|
||||
import { normalizeKey, asyncCall } from './utils'
|
||||
|
||||
export function createStorage (): Storage {
|
||||
const defaultStorage = memoryStorage()
|
||||
|
||||
// TODO: refactor to SortedMap / SortedMap
|
||||
const mounts: Record<string, StorageProvider> = {}
|
||||
const mountKeys: string[] = [] // sorted keys of mounts
|
||||
|
||||
const getAllProviders = () => [defaultStorage, ...Object.values(mounts)]
|
||||
|
||||
const getProvider = (key: string) => {
|
||||
for (const base of mountKeys) {
|
||||
if (key.startsWith(base)) {
|
||||
return mounts[base]
|
||||
}
|
||||
}
|
||||
return defaultStorage
|
||||
}
|
||||
|
||||
const storage: Storage = {
|
||||
hasItem (key) {
|
||||
key = normalizeKey(key)
|
||||
return asyncCall(getProvider(key).hasItem, key)
|
||||
},
|
||||
getItem (key) {
|
||||
key = normalizeKey(key)
|
||||
return asyncCall(getProvider(key).getItem, key)
|
||||
},
|
||||
setItem (key, vlaue) {
|
||||
key = normalizeKey(key)
|
||||
return asyncCall(getProvider(key).setItem, key, vlaue)
|
||||
},
|
||||
removeItem (key) {
|
||||
key = normalizeKey(key)
|
||||
return asyncCall(getProvider(key).removeItem, key)
|
||||
},
|
||||
async getKeys () {
|
||||
const providerKeys = await Promise.all(getAllProviders().map(s => asyncCall(s.getKeys)))
|
||||
return providerKeys.flat().map(normalizeKey)
|
||||
},
|
||||
async clear () {
|
||||
await Promise.all(getAllProviders().map(s => asyncCall(s.clear)))
|
||||
},
|
||||
async dispose () {
|
||||
await Promise.all(getAllProviders().map(s => disposeStoage(s)))
|
||||
},
|
||||
mount (base, provider) {
|
||||
base = normalizeKey(base)
|
||||
if (!mountKeys.includes(base)) {
|
||||
mountKeys.push(base)
|
||||
mountKeys.sort((a, b) => b.length - a.length)
|
||||
}
|
||||
if (mounts[base]) {
|
||||
if (mounts[base].dispose) {
|
||||
// eslint-disable-next-line no-console
|
||||
disposeStoage(mounts[base]!).catch(console.error)
|
||||
}
|
||||
delete mounts[base]
|
||||
}
|
||||
mounts[base] = provider
|
||||
}
|
||||
}
|
||||
return storage
|
||||
}
|
||||
|
||||
async function disposeStoage (storage: StorageProvider) {
|
||||
if (typeof storage.dispose === 'function') {
|
||||
await asyncCall(storage.dispose)
|
||||
}
|
||||
}
|
||||
24
src/types.ts
Normal file
24
src/types.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export type StorageValue = string | null
|
||||
|
||||
export interface StorageProvider {
|
||||
hasItem: (key: string) => boolean | Promise<boolean>
|
||||
getItem: (key: string) => StorageValue | Promise<StorageValue>
|
||||
setItem: (key: string, value: string) => void | Promise<void>
|
||||
removeItem: (key: string) => void | Promise<void>
|
||||
getKeys: () => string[] | Promise<string[]>
|
||||
clear: () => void | Promise<void>
|
||||
dispose?: () => void | Promise<void>
|
||||
}
|
||||
|
||||
export type StorageProviderFactory<OptsT = any> = (opts?: OptsT) => StorageProvider
|
||||
|
||||
export interface Storage {
|
||||
hasItem: (key: string) => Promise<boolean>
|
||||
getItem: (key: string) => Promise<StorageValue>
|
||||
setItem: (key: string, value: string) => Promise<void>
|
||||
removeItem: (key: string) => Promise<void>
|
||||
getKeys: () => Promise<string[]>
|
||||
clear: () => Promise<void>
|
||||
mount: (mountpoint: string, provider: StorageProvider) => void
|
||||
dispose: () => Promise<void>
|
||||
}
|
||||
21
src/utils/index.ts
Normal file
21
src/utils/index.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export function normalizeKey (key: string) {
|
||||
return key.replace(/[/\\]/g, ':').replace(/^:|:$/g, '')
|
||||
}
|
||||
|
||||
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
|
||||
type Promisified<T> = Promise<Awaited<T>>
|
||||
|
||||
export function wrapToPromise<T> (val: T): Promisified<T> {
|
||||
if (!val || typeof (val as any).then !== 'function') {
|
||||
return Promise.resolve(val) as Promisified<T>
|
||||
}
|
||||
return val as unknown as Promisified<T>
|
||||
}
|
||||
|
||||
export function asyncCall<T extends (...args: any) => any>(fn: T, ...args: any[]): Promisified<ReturnType<T>> {
|
||||
try {
|
||||
return wrapToPromise(fn(...args))
|
||||
} catch (err) {
|
||||
return Promise.reject(err)
|
||||
}
|
||||
}
|
||||
64
src/utils/node-fs.ts
Normal file
64
src/utils/node-fs.ts
Normal file
@ -0,0 +1,64 @@
|
||||
import { promises as fsPromises } from 'fs'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { Dirent } from 'node:fs'
|
||||
|
||||
function isNotFound (err: any) {
|
||||
return err.code === 'ENOENT'
|
||||
}
|
||||
|
||||
export async function writeFile (path: string, data: string) {
|
||||
await ensuredir(dirname(path))
|
||||
return fsPromises.writeFile(path, data, 'utf8')
|
||||
}
|
||||
|
||||
export function readFile (path: string) {
|
||||
return fsPromises.readFile(path, 'utf8')
|
||||
.catch(err => isNotFound(err) ? null : Promise.reject(err))
|
||||
}
|
||||
|
||||
export function stat (path: string) {
|
||||
return fsPromises.stat(path)
|
||||
.catch(err => isNotFound(err) ? null : Promise.reject(err))
|
||||
}
|
||||
|
||||
export function unlink (path: string) {
|
||||
return fsPromises.unlink(path)
|
||||
.catch(err => isNotFound(err) ? undefined : Promise.reject(err))
|
||||
}
|
||||
|
||||
export async function ensuredir (dir: string) {
|
||||
const _stat = await stat(dir)
|
||||
if (_stat && _stat.isDirectory()) {
|
||||
return
|
||||
}
|
||||
await ensuredir(dirname(dir))
|
||||
await fsPromises.mkdir(dir)
|
||||
}
|
||||
|
||||
export async function readdirRecursive (dir: string): Promise<string[]> {
|
||||
const entries: Dirent[] = await fsPromises.readdir(dir, { withFileTypes: true })
|
||||
.catch(err => isNotFound(err) ? [] : Promise.reject(err))
|
||||
const files: string[] = []
|
||||
await Promise.all(entries.map(async (entry) => {
|
||||
const entryPath = resolve(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
const dirFiles = await readdirRecursive(entryPath)
|
||||
files.push(...dirFiles.map(f => entry.name + '/' + f))
|
||||
} else {
|
||||
files.push(entry.name)
|
||||
}
|
||||
}))
|
||||
return files
|
||||
}
|
||||
|
||||
export async function rmRecursive (dir: string): Promise<void> {
|
||||
const entries = await fsPromises.readdir(dir, { withFileTypes: true })
|
||||
await Promise.all(entries.map((entry) => {
|
||||
const entryPath = resolve(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
return rmRecursive(entryPath).then(() => fsPromises.rmdir(entryPath))
|
||||
} else {
|
||||
return fsPromises.unlink(entryPath)
|
||||
}
|
||||
}))
|
||||
}
|
||||
57
test/index.test.ts
Normal file
57
test/index.test.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { resolve } from 'path'
|
||||
import { JSDOM } from 'jsdom'
|
||||
import {
|
||||
Storage, StorageProvider, createStorage,
|
||||
memoryStorage, fsStorage, localStorage
|
||||
} from '../src'
|
||||
|
||||
describe('memoryStorage', () => {
|
||||
testProvider(() => memoryStorage())
|
||||
})
|
||||
|
||||
describe('fsStorage', () => {
|
||||
testProvider(() => fsStorage({
|
||||
dir: resolve(__dirname, 'tmp')
|
||||
}))
|
||||
})
|
||||
|
||||
describe('localStorage', () => {
|
||||
testProvider(() => {
|
||||
const jsdom = new JSDOM('', {
|
||||
url: 'http://localhost'
|
||||
})
|
||||
return localStorage({
|
||||
localStorage: jsdom.window.localStorage
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function testProvider (getProvider: () => StorageProvider | Promise<StorageProvider>) {
|
||||
let storage: Storage
|
||||
it('init', async () => {
|
||||
storage = createStorage()
|
||||
const provider = await getProvider()
|
||||
storage.mount('/', provider)
|
||||
await storage.clear()
|
||||
})
|
||||
|
||||
it('initial state', async () => {
|
||||
expect(await storage.hasItem('foo:bar')).toBe(false)
|
||||
expect(await storage.getItem('foo:bar')).toBe(null)
|
||||
expect(await storage.getKeys()).toMatchObject([])
|
||||
})
|
||||
|
||||
it('setItem', async () => {
|
||||
await storage.setItem('/foo:bar/', 'test_data')
|
||||
expect(await storage.hasItem('foo:bar')).toBe(true)
|
||||
expect(await storage.getItem('foo:bar')).toBe('test_data')
|
||||
expect(await storage.getKeys()).toMatchObject(['foo:bar'])
|
||||
})
|
||||
|
||||
it('removeItem', async () => {
|
||||
await storage.removeItem('foo:bar')
|
||||
expect(await storage.hasItem('foo:bar')).toBe(false)
|
||||
expect(await storage.getItem('foo:bar')).toBe(null)
|
||||
expect(await storage.getKeys()).toMatchObject([])
|
||||
})
|
||||
}
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": [
|
||||
"ES2019",
|
||||
"DOM"
|
||||
],
|
||||
"moduleResolution": "Node",
|
||||
"esModuleInterop": true,
|
||||
"outDir": "dist",
|
||||
"strict": true,
|
||||
"types": [
|
||||
"node",
|
||||
"jest"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user