refactor: update repository

This commit is contained in:
Pooya Parsa 2022-11-15 02:16:38 +01:00
parent 5ac62d7b45
commit ae352daa68
17 changed files with 419 additions and 480 deletions

View File

@ -1,5 +1,9 @@
{
"extends": [
"@nuxtjs/eslint-config-typescript"
]
"eslint-config-unjs"
],
"rules": {
"unicorn/no-null": 0,
"unicorn/prevent-abbreviations": 0
}
}

View File

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2022 - UnJS
Copyright (c) Pooya Parsa <pooya@pi0.io>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@ -113,7 +113,7 @@ await storage.hasItem('foo:bar')
### `storage.getItem(key)`
Gets the value of a key in storage. Resolves to either `string` or `null`.
Gets the value of a key in storage. Resolves to either `string` or `undefined`.
```js
await storage.getItem('foo:bar')

View File

@ -1,4 +1,4 @@
import { defineBuildConfig } from 'unbuild'
import { defineBuildConfig } from "unbuild";
export default defineBuildConfig({
declaration: true,
@ -6,9 +6,9 @@ export default defineBuildConfig({
emitCJS: true
},
entries: [
'src/index',
'src/server',
{ input: 'src/drivers/', outDir: 'dist/drivers', format: 'esm' },
{ input: 'src/drivers/', outDir: 'dist/drivers', format: 'cjs', ext: 'cjs', declaration: false }
"src/index",
"src/server",
{ input: "src/drivers/", outDir: "dist/drivers", format: "esm" },
{ input: "src/drivers/", outDir: "dist/drivers", format: "cjs", ext: "cjs", declaration: false }
]
})
});

View File

@ -1,27 +1,28 @@
import { resolve } from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { createStorage } from '../src'
import { createStorageServer } from '../src/server'
import fsdriver from '../src/drivers/fs'
import { resolve } from "node:path";
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { createStorage } from "../src";
import { createStorageServer } from "../src/server";
import fsdriver from "../src/drivers/fs";
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'node-fetch': 'node-fetch/browser'
"node-fetch": "node-fetch/browser"
}
},
plugins: [
vue(),
{
name: 'app',
name: "app",
configureServer (server) {
const storage = createStorage()
const storageServer = createStorageServer(storage)
storage.mount('/src', fsdriver({ base: resolve(__dirname, '..') }))
server.middlewares.use('/storage', storageServer.handle)
const storage = createStorage();
const storageServer = createStorageServer(storage);
// eslint-disable-next-line unicorn/prefer-module
storage.mount("/src", fsdriver({ base: resolve(__dirname, "..") }));
server.middlewares.use("/storage", storageServer.handle);
}
}
]
})
});

View File

@ -13,7 +13,8 @@
},
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
},
"./server": {
"import": "./dist/server.mjs",
@ -44,7 +45,7 @@
"destr": "^1.2.1",
"h3": "^0.8.6",
"ioredis": "^5.2.4",
"listhen": "^0.3.5",
"listhen": "^1.0.0",
"mkdir": "^0.0.2",
"mri": "^1.2.0",
"ohmyfetch": "^0.4.21",
@ -53,8 +54,6 @@
},
"devDependencies": {
"@cloudflare/workers-types": "^3.18.0",
"@nuxtjs/eslint-config-typescript": "^11.0.0",
"@types/ioredis": "^4.28.10",
"@types/jsdom": "^20.0.1",
"@types/mri": "^1.1.1",
"@types/node": "^18.11.9",
@ -66,6 +65,7 @@
"changelogen": "^0.4.0",
"doctoc": "^2.2.1",
"eslint": "^8.27.0",
"eslint-config-unjs": "^0.0.2",
"jiti": "^1.16.0",
"jsdom": "^20.0.2",
"mkdist": "latest",

158
pnpm-lock.yaml generated
View File

@ -2,8 +2,6 @@ lockfileVersion: 5.4
specifiers:
'@cloudflare/workers-types': ^3.18.0
'@nuxtjs/eslint-config-typescript': ^11.0.0
'@types/ioredis': ^4.28.10
'@types/jsdom': ^20.0.1
'@types/mri': ^1.1.1
'@types/node': ^18.11.9
@ -18,11 +16,12 @@ specifiers:
destr: ^1.2.1
doctoc: ^2.2.1
eslint: ^8.27.0
eslint-config-unjs: ^0.0.2
h3: ^0.8.6
ioredis: ^5.2.4
jiti: ^1.16.0
jsdom: ^20.0.2
listhen: ^0.3.5
listhen: ^1.0.0
mkdir: ^0.0.2
mkdist: latest
monaco-editor: ^0.34.1
@ -44,7 +43,7 @@ dependencies:
destr: 1.2.1
h3: 0.8.6
ioredis: 5.2.4
listhen: 0.3.5
listhen: 1.0.0
mkdir: 0.0.2
mri: 1.2.0
ohmyfetch: 0.4.21
@ -53,8 +52,6 @@ dependencies:
devDependencies:
'@cloudflare/workers-types': 3.18.0
'@nuxtjs/eslint-config-typescript': 11.0.0_rmayb2veg2btbq6mbmnyivgasy
'@types/ioredis': 4.28.10
'@types/jsdom': 20.0.1
'@types/mri': 1.1.1
'@types/node': 18.11.9
@ -66,6 +63,7 @@ devDependencies:
changelogen: 0.4.0
doctoc: 2.2.1
eslint: 8.27.0
eslint-config-unjs: 0.0.2_rmayb2veg2btbq6mbmnyivgasy
jiti: 1.16.0
jsdom: 20.0.2
mkdist: 0.4.0_typescript@4.8.4
@ -459,43 +457,6 @@ packages:
fastq: 1.13.0
dev: true
/@nuxtjs/eslint-config-typescript/11.0.0_rmayb2veg2btbq6mbmnyivgasy:
resolution: {integrity: sha512-hmFjGtXT524ql8eTbK8BaRkamcXB6Z8YOW8nSQhosTP6oBw9WtOFUeWr7holyE278UhOmx+wDFG90BnyM9D+UA==}
peerDependencies:
eslint: ^8.23.0
dependencies:
'@nuxtjs/eslint-config': 11.0.0_atoydicabq4zoeebkyg3dz56cy
'@typescript-eslint/eslint-plugin': 5.39.0_7f3aqhlvvz4evw3j7e7r2zx4rq
'@typescript-eslint/parser': 5.39.0_rmayb2veg2btbq6mbmnyivgasy
eslint: 8.27.0
eslint-import-resolver-typescript: 3.5.1_dcpv4nbdr5ks2h5677xdltrk6e
eslint-plugin-import: 2.26.0_atoydicabq4zoeebkyg3dz56cy
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
- typescript
dev: true
/@nuxtjs/eslint-config/11.0.0_atoydicabq4zoeebkyg3dz56cy:
resolution: {integrity: sha512-o4zFOpU8gJgwrC/gLE7c2E0XEjkv2fEixCGG1y+dZYzBPyzTorkQmfxskSF3WRXcZkpkS9uUYlRkeOSdYB7z0w==}
peerDependencies:
eslint: ^8.23.0
dependencies:
eslint: 8.27.0
eslint-config-standard: 17.0.0_5p2jx74osvs7etmizpb2fndoma
eslint-plugin-import: 2.26.0_atoydicabq4zoeebkyg3dz56cy
eslint-plugin-n: 15.3.0_eslint@8.27.0
eslint-plugin-node: 11.1.0_eslint@8.27.0
eslint-plugin-promise: 6.0.1_eslint@8.27.0
eslint-plugin-unicorn: 43.0.2_eslint@8.27.0
eslint-plugin-vue: 9.6.0_eslint@8.27.0
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-typescript
- eslint-import-resolver-webpack
- supports-color
dev: true
/@open-draft/until/1.0.3:
resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==}
dev: true
@ -659,12 +620,6 @@ packages:
resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
dev: true
/@types/ioredis/4.28.10:
resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==}
dependencies:
'@types/node': 18.11.9
dev: true
/@types/istanbul-lib-coverage/2.0.4:
resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
dev: true
@ -1143,10 +1098,6 @@ packages:
readable-stream: 3.6.0
dev: true
/boolbase/1.0.0:
resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
@ -1476,12 +1427,6 @@ packages:
shebang-command: 2.0.0
which: 2.0.2
/cssesc/3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'}
hasBin: true
dev: true
/cssom/0.3.8:
resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==}
dev: true
@ -1584,6 +1529,11 @@ packages:
/defu/6.1.0:
resolution: {integrity: sha512-pOFYRTIhoKujrmbTRhcW5lYQLBXw/dlTwfI8IguF1QCDJOcJzNH1w+YFjxqy6BAuJrClTy6MUE8q+oKJ2FLsIw==}
dev: true
/defu/6.1.1:
resolution: {integrity: sha512-aA964RUCsBt0FGoNIlA3uFgo2hO+WWC0fiC6DBps/0SFzkKcYoM/3CzVLIa5xSsrFjdioMdYgAIbwo80qp2MoA==}
dev: false
/delayed-stream/1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
@ -2412,6 +2362,15 @@ packages:
source-map: 0.6.1
dev: true
/eslint-config-prettier/8.5.0_eslint@8.27.0:
resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.27.0
dev: true
/eslint-config-standard/17.0.0_5p2jx74osvs7etmizpb2fndoma:
resolution: {integrity: sha512-/2ks1GKyqSOkH7JFvXJicu0iMpoojkwB+f5Du/1SC0PtBL+s8v30k9njRZ21pm2drKYm2342jFnGWzttxPmZVg==}
peerDependencies:
@ -2426,6 +2385,29 @@ packages:
eslint-plugin-promise: 6.0.1_eslint@8.27.0
dev: true
/eslint-config-unjs/0.0.2_rmayb2veg2btbq6mbmnyivgasy:
resolution: {integrity: sha512-6zhni5Fdlpu/DJ+hfcbNEqmUixhGujBQXXB7N9SdZcvh135RlFTNoqor7sowP1ttd0lDY4lTewUzUjXSOVB7Ww==}
peerDependencies:
eslint: '*'
typescript: '*'
dependencies:
'@typescript-eslint/eslint-plugin': 5.39.0_7f3aqhlvvz4evw3j7e7r2zx4rq
'@typescript-eslint/parser': 5.39.0_rmayb2veg2btbq6mbmnyivgasy
eslint: 8.27.0
eslint-config-prettier: 8.5.0_eslint@8.27.0
eslint-config-standard: 17.0.0_5p2jx74osvs7etmizpb2fndoma
eslint-import-resolver-typescript: 3.5.1_dcpv4nbdr5ks2h5677xdltrk6e
eslint-plugin-import: 2.26.0_atoydicabq4zoeebkyg3dz56cy
eslint-plugin-n: 15.3.0_eslint@8.27.0
eslint-plugin-node: 11.1.0_eslint@8.27.0
eslint-plugin-promise: 6.0.1_eslint@8.27.0
eslint-plugin-unicorn: 43.0.2_eslint@8.27.0
typescript: 4.8.4
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
dev: true
/eslint-import-resolver-node/0.3.6:
resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==}
dependencies:
@ -2599,24 +2581,6 @@ packages:
strip-indent: 3.0.0
dev: true
/eslint-plugin-vue/9.6.0_eslint@8.27.0:
resolution: {integrity: sha512-zzySkJgVbFCylnG2+9MDF7N+2Rjze2y0bF8GyUNpFOnT8mCMfqqtLDJkHBuYu9N/psW1A6DVbQhPkP92E+qakA==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
dependencies:
eslint: 8.27.0
eslint-utils: 3.0.0_eslint@8.27.0
natural-compare: 1.4.0
nth-check: 2.1.1
postcss-selector-parser: 6.0.10
semver: 7.3.8
vue-eslint-parser: 9.1.0_eslint@8.27.0
xml-name-validator: 4.0.0
transitivePeerDependencies:
- supports-color
dev: true
/eslint-scope/5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
@ -3720,17 +3684,17 @@ packages:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
dev: true
/listhen/0.3.5:
resolution: {integrity: sha512-suyt79hNmCFeBIyftcLqLPfYiXeB795gSUWOJT7nspl2IvREY0Q9xvchLhekxvQ0KiOPvWoyALnc9Mxoelm0Pw==}
/listhen/1.0.0:
resolution: {integrity: sha512-frdf7TVqT/JSHzRjEuo/vWIgbBYzEuY3oeTq8Yv1XkQVTKDPs2M4yotXICqYZYj2QxbkqKssSo8Wa6QCtBnFhg==}
dependencies:
clipboardy: 3.0.0
colorette: 2.0.19
defu: 6.1.0
defu: 6.1.1
get-port-please: 2.6.1
http-shutdown: 1.2.2
ip-regex: 5.0.0
node-forge: 1.3.1
ufo: 0.8.6
ufo: 1.0.0
dev: false
/local-pkg/0.4.2:
@ -4251,12 +4215,6 @@ packages:
path-key: 4.0.0
dev: true
/nth-check/2.1.1:
resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
dependencies:
boolbase: 1.0.0
dev: true
/nwsapi/2.2.2:
resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==}
dev: true
@ -4538,14 +4496,6 @@ packages:
engines: {node: '>=4'}
dev: true
/postcss-selector-parser/6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'}
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
dev: true
/postcss/8.4.17:
resolution: {integrity: sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==}
engines: {node: ^10 || ^12 || >=14}
@ -5492,24 +5442,6 @@ packages:
- terser
dev: true
/vue-eslint-parser/9.1.0_eslint@8.27.0:
resolution: {integrity: sha512-NGn/iQy8/Wb7RrRa4aRkokyCZfOUWk19OP5HP6JEozQFX5AoS/t+Z0ZN7FY4LlmWc4FNI922V7cvX28zctN8dQ==}
engines: {node: ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '>=6.0.0'
dependencies:
debug: 4.3.4
eslint: 8.27.0
eslint-scope: 7.1.1
eslint-visitor-keys: 3.3.0
espree: 9.4.0
esquery: 1.4.0
lodash: 4.17.21
semver: 7.3.8
transitivePeerDependencies:
- supports-color
dev: true
/vue/3.2.45:
resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==}
dependencies:

View File

@ -1,5 +1,5 @@
{
"extends": [
"@nuxtjs"
"github>unjs/renovate-config"
]
}

View File

@ -1,26 +1,26 @@
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type Promisified<T> = Promise<Awaited<T>>
export function wrapToPromise<T> (val: T) {
if (!val || typeof (val as any).then !== 'function') {
return Promise.resolve(val) as Promisified<T>
export function wrapToPromise<T> (value: T) {
if (!value || typeof (value as any).then !== "function") {
return Promise.resolve(value) as Promisified<T>;
}
return val as unknown as Promisified<T>
return value as unknown as Promisified<T>;
}
export function asyncCall<T extends (...args: any) => any>(fn: T, ...args: any[]): Promisified<ReturnType<T>> {
export function asyncCall<T extends (...arguments_: any) => any>(function_: T, ...arguments_: any[]): Promisified<ReturnType<T>> {
try {
return wrapToPromise(fn(...args))
} catch (err) {
return Promise.reject(err)
return wrapToPromise(function_(...arguments_));
} catch (error) {
return Promise.reject(error);
}
}
export function isPrimitive (arg: any) {
const type = typeof arg
return arg === null || (type !== 'object' && type !== 'function')
export function isPrimitive (argument: any) {
const type = typeof argument;
return argument === null || (type !== "object" && type !== "function");
}
export function stringify (arg: any) {
return isPrimitive(arg) ? (arg + '') : JSON.stringify(arg)
export function stringify (argument: any) {
return isPrimitive(argument) ? (argument + "") : JSON.stringify(argument);
}

View File

@ -1,35 +1,38 @@
import { resolve } from 'path'
import mri from 'mri'
import { listen } from 'listhen'
import { createStorage } from './storage'
import { createStorageServer } from './server'
import fsDriver from './drivers/fs'
import { resolve } from "node:path";
import mri from "mri";
import { listen } from "listhen";
import { createStorage } from "./storage";
import { createStorageServer } from "./server";
import fsDriver from "./drivers/fs";
async function main () {
const args = mri(process.argv.splice(2))
const arguments_ = mri(process.argv.splice(2));
if (args.help) {
if (arguments_.help) {
// eslint-disable-next-line no-console
console.log('Usage: npx unstorage [rootDir]')
process.exit(0)
console.log("Usage: npx unstorage [rootDir]");
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0);
}
const rootDir = resolve(args._[0] || '.')
const rootDir = resolve(arguments_._[0] || ".");
const storage = createStorage({
driver: fsDriver({ base: rootDir })
})
});
const storageServer = createStorageServer(storage)
const storageServer = createStorageServer(storage);
await listen(storageServer.handle, {
name: 'Storage server',
name: "Storage server",
port: 8080
})
});
}
main().catch((err) => {
// eslint-disable-next-line unicorn/prefer-top-level-await
main().catch((error) => {
// eslint-disable-next-line no-console
console.error(err)
process.exit(1)
})
console.error(error);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
});

View File

@ -1,21 +1,21 @@
export * from './storage'
export * from './types'
export * from './utils'
export { defineDriver } from './drivers/utils'
export * from "./storage";
export * from "./types";
export * from "./utils";
export { defineDriver } from "./drivers/utils";
export const builtinDrivers = {
cloudflareKVHTTP: 'unstorage/drivers/cloudflare-kv-http',
cloudflareKVBinding: 'unstorage/drivers/cloudflare-kv-binding',
'cloudflare-kv-http': 'unstorage/drivers/cloudflare-kv-http',
'cloudflare-kv-binding': 'unstorage/drivers/cloudflare-kv-binding',
fs: 'unstorage/drivers/fs',
github: 'unstorage/drivers/github',
http: 'unstorage/drivers/http',
localStorage: 'unstorage/drivers/localstorage',
localstorage: 'unstorage/drivers/localstorage',
memory: 'unstorage/drivers/memory',
overlay: 'unstorage/drivers/overlay',
redis: 'unstorage/drivers/redis'
}
cloudflareKVHTTP: "unstorage/drivers/cloudflare-kv-http",
cloudflareKVBinding: "unstorage/drivers/cloudflare-kv-binding",
"cloudflare-kv-http": "unstorage/drivers/cloudflare-kv-http",
"cloudflare-kv-binding": "unstorage/drivers/cloudflare-kv-binding",
fs: "unstorage/drivers/fs",
github: "unstorage/drivers/github",
http: "unstorage/drivers/http",
localStorage: "unstorage/drivers/localstorage",
localstorage: "unstorage/drivers/localstorage",
memory: "unstorage/drivers/memory",
overlay: "unstorage/drivers/overlay",
redis: "unstorage/drivers/redis"
};
export type BuiltinDriverName = keyof typeof builtinDrivers

View File

@ -1,7 +1,7 @@
import { RequestListener } from 'http'
import { createApp, createError, readBody, eventHandler, toNodeListener } from 'h3'
import { Storage } from './types'
import { stringify } from './_utils'
import { RequestListener } from "node:http";
import { createApp, createError, readBody, eventHandler, toNodeListener } from "h3";
import { Storage } from "./types";
import { stringify } from "./_utils";
export interface StorageServerOptions {}
@ -9,48 +9,48 @@ export interface StorageServer {
handle: RequestListener
}
export function createStorageServer (storage: Storage, _opts: StorageServerOptions = {}): StorageServer {
const app = createApp()
export function createStorageServer (storage: Storage, _options: StorageServerOptions = {}): StorageServer {
const app = createApp();
app.use(eventHandler(async (event) => {
// GET => getItem
if (event.req.method === 'GET') {
const val = await storage.getItem(event.req.url!)
if (!val) {
const keys = await storage.getKeys(event.req.url)
return keys.map(key => key.replace(/:/g, '/'))
if (event.req.method === "GET") {
const value = await storage.getItem(event.req.url!);
if (!value) {
const keys = await storage.getKeys(event.req.url);
return keys.map(key => key.replace(/:/g, "/"));
}
return stringify(val)
return stringify(value);
}
// HEAD => hasItem + meta (mtime)
if (event.req.method === 'HEAD') {
const _hasItem = await storage.hasItem(event.req.url!)
event.res.statusCode = _hasItem ? 200 : 404
if (event.req.method === "HEAD") {
const _hasItem = await storage.hasItem(event.req.url!);
event.res.statusCode = _hasItem ? 200 : 404;
if (_hasItem) {
const meta = await storage.getMeta(event.req.url!)
const meta = await storage.getMeta(event.req.url!);
if (meta.mtime) {
event.res.setHeader('Last-Modified', new Date(meta.mtime).toUTCString())
event.res.setHeader("Last-Modified", new Date(meta.mtime).toUTCString());
}
}
return ''
return "";
}
// PUT => setItem
if (event.req.method === 'PUT') {
const val = await readBody(event)
await storage.setItem(event.req.url!, val)
return 'OK'
if (event.req.method === "PUT") {
const value = await readBody(event);
await storage.setItem(event.req.url!, value);
return "OK";
}
// DELETE => removeItem
if (event.req.method === 'DELETE') {
await storage.removeItem(event.req.url!)
return 'OK'
if (event.req.method === "DELETE") {
await storage.removeItem(event.req.url!);
return "OK";
}
throw createError({
statusCode: 405,
statusMessage: 'Method Not Allowed'
})
}))
statusMessage: "Method Not Allowed"
});
}));
return {
handle: toNodeListener(app)
}
};
}

View File

@ -1,8 +1,8 @@
import destr from 'destr'
import type { Storage, Driver, WatchCallback, Unwatch, StorageValue } from './types'
import memory from './drivers/memory'
import { asyncCall, stringify } from './_utils'
import { normalizeKey, normalizeBaseKey } from './utils'
import destr from "destr";
import type { Storage, Driver, WatchCallback, Unwatch, StorageValue } from "./types";
import memory from "./drivers/memory";
import { asyncCall, stringify } from "./_utils";
import { normalizeKey, normalizeBaseKey } from "./utils";
interface StorageCTX {
mounts: Record<string, Driver>
@ -16,250 +16,249 @@ export interface CreateStorageOptions {
driver?: Driver
}
export function createStorage (opts: CreateStorageOptions = {}): Storage {
const ctx: StorageCTX = {
mounts: { '': opts.driver || memory() },
mountpoints: [''],
export function createStorage (options: CreateStorageOptions = {}): Storage {
const context: StorageCTX = {
mounts: { "": options.driver || memory() },
mountpoints: [""],
watching: false,
watchListeners: [],
unwatch: {}
}
};
const getMount = (key: string) => {
for (const base of ctx.mountpoints) {
for (const base of context.mountpoints) {
if (key.startsWith(base)) {
return {
relativeKey: key.substring(base.length),
driver: ctx.mounts[base]
}
relativeKey: key.slice(base.length),
driver: context.mounts[base]
};
}
}
return {
relativeKey: key,
driver: ctx.mounts['']
}
}
driver: context.mounts[""]
};
};
const getMounts = (base: string, includeParent: boolean) => {
return ctx.mountpoints
return context.mountpoints
.filter(mountpoint => (mountpoint.startsWith(base)) || (includeParent && base!.startsWith(mountpoint)))
.map(mountpoint => ({
relativeBase: base.length > mountpoint.length ? base!.substring(mountpoint.length) : undefined,
relativeBase: base.length > mountpoint.length ? base!.slice(mountpoint.length) : undefined,
mountpoint,
driver: ctx.mounts[mountpoint]
}))
}
driver: context.mounts[mountpoint]
}));
};
const onChange: WatchCallback = (event, key) => {
if (!ctx.watching) { return }
key = normalizeKey(key)
for (const listener of ctx.watchListeners) {
listener(event, key)
if (!context.watching) { return; }
key = normalizeKey(key);
for (const listener of context.watchListeners) {
listener(event, key);
}
}
};
const startWatch = async () => {
if (ctx.watching) { return }
ctx.watching = true
for (const mountpoint in ctx.mounts) {
ctx.unwatch[mountpoint] = await watch(ctx.mounts[mountpoint], onChange, mountpoint)
if (context.watching) { return; }
context.watching = true;
for (const mountpoint in context.mounts) {
context.unwatch[mountpoint] = await watch(context.mounts[mountpoint], onChange, mountpoint);
}
}
};
const stopWatch = async () => {
if (!ctx.watching) { return }
for (const mountpoint in ctx.unwatch) {
await ctx.unwatch[mountpoint]()
if (!context.watching) { return; }
for (const mountpoint in context.unwatch) {
await context.unwatch[mountpoint]();
}
ctx.unwatch = {}
ctx.watching = false
}
context.unwatch = {};
context.watching = false;
};
const storage: Storage = {
// Item
hasItem (key) {
key = normalizeKey(key)
const { relativeKey, driver } = getMount(key)
return asyncCall(driver.hasItem, relativeKey)
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
return asyncCall(driver.hasItem, relativeKey);
},
getItem (key) {
key = normalizeKey(key)
const { relativeKey, driver } = getMount(key)
return asyncCall(driver.getItem, relativeKey).then(val => destr(val))
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
return asyncCall(driver.getItem, relativeKey).then(value => destr(value));
},
async setItem (key, value) {
if (value === undefined) {
return storage.removeItem(key)
return storage.removeItem(key);
}
key = normalizeKey(key)
const { relativeKey, driver } = getMount(key)
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
if (!driver.setItem) {
return // Readonly
return; // Readonly
}
await asyncCall(driver.setItem, relativeKey, stringify(value))
await asyncCall(driver.setItem, relativeKey, stringify(value));
if (!driver.watch) {
onChange('update', key)
onChange("update", key);
}
},
async removeItem (key, removeMeta = true) {
key = normalizeKey(key)
const { relativeKey, driver } = getMount(key)
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
if (!driver.removeItem) {
return // Readonly
return; // Readonly
}
await asyncCall(driver.removeItem, relativeKey)
await asyncCall(driver.removeItem, relativeKey);
if (removeMeta) {
await asyncCall(driver.removeItem, relativeKey + '$')
await asyncCall(driver.removeItem, relativeKey + "$");
}
if (!driver.watch) {
onChange('remove', key)
onChange("remove", key);
}
},
// Meta
async getMeta (key, nativeMetaOnly) {
key = normalizeKey(key)
const { relativeKey, driver } = getMount(key)
const meta = Object.create(null)
key = normalizeKey(key);
const { relativeKey, driver } = getMount(key);
const meta = Object.create(null);
if (driver.getMeta) {
Object.assign(meta, await asyncCall(driver.getMeta, relativeKey))
Object.assign(meta, await asyncCall(driver.getMeta, relativeKey));
}
if (!nativeMetaOnly) {
const val = await asyncCall(driver.getItem, relativeKey + '$').then(val => destr(val))
if (val && typeof val === 'object') {
const value = await asyncCall(driver.getItem, relativeKey + "$").then(value_ => destr(value_));
if (value && typeof value === "object") {
// TODO: Support date by destr?
if (typeof val.atime === 'string') { val.atime = new Date(val.atime) }
if (typeof val.mtime === 'string') { val.mtime = new Date(val.mtime) }
Object.assign(meta, val)
if (typeof value.atime === "string") { value.atime = new Date(value.atime); }
if (typeof value.mtime === "string") { value.mtime = new Date(value.mtime); }
Object.assign(meta, value);
}
}
return meta
return meta;
},
setMeta (key: string, value: any) {
return this.setItem(key + '$', value)
return this.setItem(key + "$", value);
},
removeMeta (key: string) {
return this.removeItem(key + '$')
return this.removeItem(key + "$");
},
// Keys
async getKeys (base) {
base = normalizeBaseKey(base)
const mounts = getMounts(base, true)
let maskedMounts = []
const allKeys = []
base = normalizeBaseKey(base);
const mounts = getMounts(base, true);
let maskedMounts = [];
const allKeys = [];
for (const mount of mounts) {
const rawKeys = await asyncCall(mount.driver.getKeys, mount.relativeBase)
const rawKeys = await asyncCall(mount.driver.getKeys, mount.relativeBase);
const keys = rawKeys
.map(key => mount.mountpoint + normalizeKey(key))
.filter(key => !maskedMounts.find(p => key.startsWith(p)))
allKeys.push(...keys)
.filter(key => !maskedMounts.some(p => key.startsWith(p)));
allKeys.push(...keys);
// When /mnt/foo is processed, any key in /mnt with /mnt/foo prefix should be masked
// Using filter to improve performance. /mnt mask already covers /mnt/foo
maskedMounts = [mount.mountpoint].concat(maskedMounts.filter(p => !p.startsWith(mount.mountpoint)))
maskedMounts = [
mount.mountpoint,
...maskedMounts.filter(p => !p.startsWith(mount.mountpoint))
];
}
return base
? allKeys.filter(key => key.startsWith(base!) && !key.endsWith('$'))
: allKeys.filter(key => !key.endsWith('$'))
? allKeys.filter(key => key.startsWith(base!) && !key.endsWith("$"))
: allKeys.filter(key => !key.endsWith("$"));
},
// Utils
async clear (base) {
base = normalizeBaseKey(base)
base = normalizeBaseKey(base);
await Promise.all(getMounts(base, false).map(async (m) => {
if (m.driver.clear) {
return asyncCall(m.driver.clear)
return asyncCall(m.driver.clear);
}
// Fallback to remove all keys if clear not implemented
if (m.driver.removeItem) {
const keys = await m.driver.getKeys()
return Promise.all(keys.map(key => m.driver.removeItem!(key)))
const keys = await m.driver.getKeys();
return Promise.all(keys.map(key => m.driver.removeItem!(key)));
}
// Readonly
}))
}));
},
async dispose () {
await Promise.all(Object.values(ctx.mounts).map(driver => dispose(driver)))
await Promise.all(Object.values(context.mounts).map(driver => dispose(driver)));
},
async watch (callback) {
await startWatch()
ctx.watchListeners.push(callback)
await startWatch();
context.watchListeners.push(callback);
return async () => {
ctx.watchListeners = ctx.watchListeners.filter(listener => listener !== callback)
if (ctx.watchListeners.length === 0) {
await stopWatch()
context.watchListeners = context.watchListeners.filter(listener => listener !== callback);
if (context.watchListeners.length === 0) {
await stopWatch();
}
}
};
},
async unwatch () {
ctx.watchListeners = []
await stopWatch()
context.watchListeners = [];
await stopWatch();
},
// Mount
mount (base, driver) {
base = normalizeBaseKey(base)
if (base && ctx.mounts[base]) {
throw new Error(`already mounted at ${base}`)
base = normalizeBaseKey(base);
if (base && context.mounts[base]) {
throw new Error(`already mounted at ${base}`);
}
if (base) {
ctx.mountpoints.push(base)
ctx.mountpoints.sort((a, b) => b.length - a.length)
context.mountpoints.push(base);
context.mountpoints.sort((a, b) => b.length - a.length);
}
ctx.mounts[base] = driver
if (ctx.watching) {
context.mounts[base] = driver;
if (context.watching) {
Promise.resolve(watch(driver, onChange, base))
.then((unwatcher) => {
ctx.unwatch[base] = unwatcher
context.unwatch[base] = unwatcher;
})
.catch(console.error) // eslint-disable-line no-console
.catch(console.error); // eslint-disable-line no-console
}
return storage
return storage;
},
async unmount (base: string, _dispose = true) {
base = normalizeBaseKey(base)
if (!base /* root */ || !ctx.mounts[base]) {
return
base = normalizeBaseKey(base);
if (!base /* root */ || !context.mounts[base]) {
return;
}
if (ctx.watching && base in ctx.unwatch) {
ctx.unwatch[base]()
delete ctx.unwatch[base]
if (context.watching && base in context.unwatch) {
context.unwatch[base]();
delete context.unwatch[base];
}
if (_dispose) {
await dispose(ctx.mounts[base])
await dispose(context.mounts[base]);
}
ctx.mountpoints = ctx.mountpoints.filter(key => key !== base)
delete ctx.mounts[base]
context.mountpoints = context.mountpoints.filter(key => key !== base);
delete context.mounts[base];
}
}
};
return storage
return storage;
}
export type Snapshot<T=string> = Record<string, T>
export async function snapshot (storage: Storage, base: string): Promise<Snapshot<string>> {
base = normalizeBaseKey(base)
const keys = await storage.getKeys(base)
const snapshot: any = {}
base = normalizeBaseKey(base);
const keys = await storage.getKeys(base);
const snapshot: any = {};
await Promise.all(keys.map(async (key) => {
snapshot[key.substr(base.length)] = await storage.getItem(key)
}))
return snapshot
snapshot[key.slice(base.length)] = await storage.getItem(key);
}));
return snapshot;
}
export async function restoreSnapshot (driver: Storage, snapshot: Snapshot<StorageValue>, base: string = '') {
base = normalizeBaseKey(base)
await Promise.all(Object.entries(snapshot).map(e => driver.setItem(base + e[0], e[1])))
export async function restoreSnapshot (driver: Storage, snapshot: Snapshot<StorageValue>, base: string = "") {
base = normalizeBaseKey(base);
await Promise.all(Object.entries(snapshot).map(e => driver.setItem(base + e[0], e[1])));
}
function watch (driver: Driver, onChange: WatchCallback, base: string) {
if (driver.watch) {
return driver.watch((event, key) => onChange(event, base + key))
} else {
return () => undefined
}
return driver.watch ? driver.watch((event, key) => onChange(event, base + key)) : () => {};
}
async function dispose (driver: Driver) {
if (typeof driver.dispose === 'function') {
await asyncCall(driver.dispose)
if (typeof driver.dispose === "function") {
await asyncCall(driver.dispose);
}
}

View File

@ -1,5 +1,5 @@
export type StorageValue = null | string | String | number | Number | boolean | Boolean | object
export type WatchEvent = 'update' | 'remove'
export type WatchEvent = "update" | "remove"
export type WatchCallback = (event: WatchEvent, key: string) => any
type MaybePromise<T> = T | Promise<T>

View File

@ -1,50 +1,50 @@
import type { Storage } from './types'
import type { Storage } from "./types";
type StorageKeys = Array<keyof Storage>
const storageKeyProps: StorageKeys = [
'hasItem',
'getItem',
'setItem',
'removeItem',
'getMeta',
'setMeta',
'removeMeta',
'getKeys',
'clear',
'mount',
'unmount'
]
const storageKeyProperties: StorageKeys = [
"hasItem",
"getItem",
"setItem",
"removeItem",
"getMeta",
"setMeta",
"removeMeta",
"getKeys",
"clear",
"mount",
"unmount"
];
export function prefixStorage (storage: Storage, base: string) {
base = normalizeBaseKey(base)
base = normalizeBaseKey(base);
if (!base) {
return storage
return storage;
}
const nsStorage: Storage = { ...storage }
for (const prop of storageKeyProps) {
const nsStorage: Storage = { ...storage };
for (const property of storageKeyProperties) {
// @ts-ignore Better types?
nsStorage[prop] = (key: string = '', ...args) => storage[prop](base + key, ...args)
nsStorage[property] = (key: string = "", ...arguments_) => storage[property](base + key, ...arguments_);
}
nsStorage.getKeys = (key: string = '', ...args) =>
nsStorage.getKeys = (key: string = "", ...arguments_) =>
storage
.getKeys(base + key, ...args)
.getKeys(base + key, ...arguments_)
// Remove Prefix
.then(keys => keys.map(key => key.substr(base.length)))
.then(keys => keys.map(key => key.slice(base.length)));
return nsStorage
return nsStorage;
}
export function normalizeKey (key?: string) {
if (!key) { return '' }
return key.replace(/[/\\]/g, ':').replace(/:+/g, ':').replace(/^:|:$/g, '')
if (!key) { return ""; }
return key.replace(/[/\\]/g, ":").replace(/:+/g, ":").replace(/^:|:$/g, "");
}
export function joinKeys (...keys: string[]) {
return normalizeKey(keys.join(':'))
return normalizeKey(keys.join(":"));
}
export function normalizeBaseKey (base?: string) {
base = normalizeKey(base)
return base ? (base + ':') : ''
base = normalizeKey(base);
return base ? (base + ":") : "";
}

View File

@ -1,32 +1,32 @@
import { describe, it, expect } from 'vitest'
import { listen } from 'listhen'
import { $fetch } from 'ohmyfetch'
import { createStorage } from '../src'
import { createStorageServer } from '../src/server'
import { describe, it, expect } from "vitest";
import { listen } from "listhen";
import { $fetch } from "ohmyfetch";
import { createStorage } from "../src";
import { createStorageServer } from "../src/server";
describe('server', () => {
it('basic', async () => {
const storage = createStorage()
const storageServer = createStorageServer(storage)
describe("server", () => {
it("basic", async () => {
const storage = createStorage();
const storageServer = createStorageServer(storage);
const { close, url: serverURL } = await listen(storageServer.handle, {
port: { random: true }
})
});
const fetchStorage = (url: string, opts?: any) => $fetch(url, { baseURL: serverURL, ...opts })
const fetchStorage = (url: string, options?: any) => $fetch(url, { baseURL: serverURL, ...options });
expect(await fetchStorage('foo', {})).toMatchObject([])
expect(await fetchStorage("foo", {})).toMatchObject([]);
await storage.setItem('foo/bar', 'bar')
await storage.setMeta('foo/bar', { mtime: new Date() })
expect(await fetchStorage('foo/bar')).toBe('bar')
await storage.setItem("foo/bar", "bar");
await storage.setMeta("foo/bar", { mtime: new Date() });
expect(await fetchStorage("foo/bar")).toBe("bar");
expect(await fetchStorage('foo/bar', { method: 'PUT', body: 'updated' })).toBe('OK')
expect(await fetchStorage('foo/bar')).toBe('updated')
expect(await fetchStorage('/')).toMatchObject(['foo/bar'])
expect(await fetchStorage("foo/bar", { method: "PUT", body: "updated" })).toBe("OK");
expect(await fetchStorage("foo/bar")).toBe("updated");
expect(await fetchStorage("/")).toMatchObject(["foo/bar"]);
expect(await fetchStorage('foo/bar', { method: 'DELETE' })).toBe('OK')
expect(await fetchStorage('foo/bar', {})).toMatchObject([])
expect(await fetchStorage("foo/bar", { method: "DELETE" })).toBe("OK");
expect(await fetchStorage("foo/bar", {})).toMatchObject([]);
await close()
})
})
await close();
});
});

View File

@ -1,107 +1,107 @@
import { describe, it, expect, vi } from 'vitest'
import { createStorage, snapshot, restoreSnapshot, prefixStorage } from '../src'
import memory from '../src/drivers/memory'
import { describe, it, expect, vi } from "vitest";
import { createStorage, snapshot, restoreSnapshot, prefixStorage } from "../src";
import memory from "../src/drivers/memory";
const data = {
'etc:conf': 'test',
'data:foo': 123
}
"etc:conf": "test",
"data:foo": 123
};
describe('storage', () => {
it('mount/unmount', async () => {
const storage = createStorage().mount('/mnt', memory())
await restoreSnapshot(storage, data, 'mnt')
expect(await snapshot(storage, '/mnt')).toMatchObject(data)
})
describe("storage", () => {
it("mount/unmount", async () => {
const storage = createStorage().mount("/mnt", memory());
await restoreSnapshot(storage, data, "mnt");
expect(await snapshot(storage, "/mnt")).toMatchObject(data);
});
it('snapshot', async () => {
const storage = createStorage()
await restoreSnapshot(storage, data)
expect(await snapshot(storage, '')).toMatchObject(data)
})
it("snapshot", async () => {
const storage = createStorage();
await restoreSnapshot(storage, data);
expect(await snapshot(storage, "")).toMatchObject(data);
});
it('watch', async () => {
const onChange = vi.fn()
const storage = createStorage().mount('/mnt', memory())
await storage.watch(onChange)
await restoreSnapshot(storage, data, 'mnt')
expect(onChange).toHaveBeenCalledWith('update', 'mnt:etc:conf')
expect(onChange).toHaveBeenCalledWith('update', 'mnt:data:foo')
expect(onChange).toHaveBeenCalledTimes(2)
})
it("watch", async () => {
const onChange = vi.fn();
const storage = createStorage().mount("/mnt", memory());
await storage.watch(onChange);
await restoreSnapshot(storage, data, "mnt");
expect(onChange).toHaveBeenCalledWith("update", "mnt:etc:conf");
expect(onChange).toHaveBeenCalledWith("update", "mnt:data:foo");
expect(onChange).toHaveBeenCalledTimes(2);
});
it('unwatch return', async () => {
const onChange = vi.fn()
const storage = createStorage().mount('/mnt', memory())
const unwatch = await storage.watch(onChange)
await storage.setItem('mnt:data:foo', 42)
await unwatch()
await storage.setItem('mnt:data:foo', 41)
expect(onChange).toHaveBeenCalledTimes(1)
})
it("unwatch return", async () => {
const onChange = vi.fn();
const storage = createStorage().mount("/mnt", memory());
const unwatch = await storage.watch(onChange);
await storage.setItem("mnt:data:foo", 42);
await unwatch();
await storage.setItem("mnt:data:foo", 41);
expect(onChange).toHaveBeenCalledTimes(1);
});
it('unwatch all', async () => {
const onChange = vi.fn()
const storage = createStorage().mount('/mnt', memory())
await storage.watch(onChange)
await storage.setItem('mnt:data:foo', 42)
await storage.unwatch()
await storage.setItem('mnt:data:foo', 41)
expect(onChange).toHaveBeenCalledTimes(1)
})
it("unwatch all", async () => {
const onChange = vi.fn();
const storage = createStorage().mount("/mnt", memory());
await storage.watch(onChange);
await storage.setItem("mnt:data:foo", 42);
await storage.unwatch();
await storage.setItem("mnt:data:foo", 41);
expect(onChange).toHaveBeenCalledTimes(1);
});
it('mount overides', async () => {
const mainStorage = memory()
const storage = createStorage({ driver: mainStorage })
await storage.setItem('/mnt/test.txt', 'v1')
await storage.setItem('/mnt/test.base.txt', 'v1')
it("mount overides", async () => {
const mainStorage = memory();
const storage = createStorage({ driver: mainStorage });
await storage.setItem("/mnt/test.txt", "v1");
await storage.setItem("/mnt/test.base.txt", "v1");
const initialKeys = await storage.getKeys()
const initialKeys = await storage.getKeys();
expect(initialKeys).toMatchInlineSnapshot(`
[
"mnt:test.txt",
"mnt:test.base.txt",
]
`)
`);
storage.mount('/mnt', memory())
await storage.setItem('/mnt/test.txt', 'v2')
storage.mount("/mnt", memory());
await storage.setItem("/mnt/test.txt", "v2");
await storage.setItem('/mnt/foo/test.txt', 'v3')
storage.mount('/mnt/foo', memory())
expect(await storage.getItem('/mnt/foo/test.txt')).toBe(null)
await storage.setItem("/mnt/foo/test.txt", "v3");
storage.mount("/mnt/foo", memory());
expect(await storage.getItem("/mnt/foo/test.txt")).toBe(null);
expect(await storage.getItem('/mnt/test.txt')).toBe('v2')
expect(await storage.getItem("/mnt/test.txt")).toBe("v2");
expect(await storage.getKeys()).toMatchInlineSnapshot(`
[
"mnt:test.txt",
]
`)
`);
await storage.clear('/mnt')
await storage.unmount('/mnt')
expect(await storage.getKeys()).toMatchObject(initialKeys)
expect(await storage.getItem('/mnt/test.txt')).toBe('v1')
})
})
await storage.clear("/mnt");
await storage.unmount("/mnt");
expect(await storage.getKeys()).toMatchObject(initialKeys);
expect(await storage.getItem("/mnt/test.txt")).toBe("v1");
});
});
describe('utils', () => {
it('prefixStorage', async () => {
const storage = createStorage()
const pStorage = prefixStorage(storage, 'foo')
await pStorage.setItem('x', 'bar')
await pStorage.setItem('y', 'baz')
expect(await storage.getItem('foo:x')).toBe('bar')
expect(await pStorage.getItem('x')).toBe('bar')
expect(await pStorage.getKeys()).toStrictEqual(['x', 'y'])
describe("utils", () => {
it("prefixStorage", async () => {
const storage = createStorage();
const pStorage = prefixStorage(storage, "foo");
await pStorage.setItem("x", "bar");
await pStorage.setItem("y", "baz");
expect(await storage.getItem("foo:x")).toBe("bar");
expect(await pStorage.getItem("x")).toBe("bar");
expect(await pStorage.getKeys()).toStrictEqual(["x", "y"]);
// Higher order storage
const secondStorage = createStorage()
secondStorage.mount('/mnt', storage)
const mntStorage = prefixStorage(secondStorage, 'mnt')
const secondStorage = createStorage();
secondStorage.mount("/mnt", storage);
const mntStorage = prefixStorage(secondStorage, "mnt");
expect(await mntStorage.getKeys()).toStrictEqual(['foo:x', 'foo:y'])
expect(await mntStorage.getKeys()).toStrictEqual(["foo:x", "foo:y"]);
// Get keys from sub-storage
expect(await mntStorage.getKeys('foo')).toStrictEqual(['foo:x', 'foo:y'])
})
})
expect(await mntStorage.getKeys("foo")).toStrictEqual(["foo:x", "foo:y"]);
});
});