Comparing version 0.0.1 to 0.0.2
@@ -5,2 +5,16 @@ # Changelog | ||
### [0.0.2](https://github.com/unjsio/unstorage/compare/v0.0.1...v0.0.2) (2021-03-11) | ||
### Features | ||
* http driver ([438db64](https://github.com/unjsio/unstorage/commit/438db6427602a08343c8836a3386b9d712ca6ee9)) | ||
* support base for drivers ([6844cd1](https://github.com/unjsio/unstorage/commit/6844cd11373c7aeee49780322d4c23c48342eb8a)) | ||
* watcher ([ebcf1f1](https://github.com/unjsio/unstorage/commit/ebcf1f1a742756b78adaa955bdc90615554404cf)) | ||
### Bug Fixes | ||
* add mount prefix to watch key ([0bb634d](https://github.com/unjsio/unstorage/commit/0bb634dcc51de2f32f2b2b892efa9090ef2c6885)) | ||
### 0.0.1 (2021-03-11) | ||
@@ -7,0 +21,0 @@ |
declare type StorageValue = null | string | String | number | Number | boolean | Boolean | object; | ||
declare type WatchEvent = 'update' | 'remove'; | ||
declare type WatchCallback = (event: WatchEvent, key: string) => any; | ||
interface Driver { | ||
@@ -10,2 +12,3 @@ hasItem: (key: string) => boolean | Promise<boolean>; | ||
dispose?: () => void | Promise<void>; | ||
watch?: (callback: WatchCallback) => void | Promise<void>; | ||
} | ||
@@ -21,5 +24,6 @@ declare type DriverFactory<OptsT = any> = (opts?: OptsT) => Driver; | ||
clear: (base?: string) => Promise<void>; | ||
mount: (base: string, driver: Driver, initialState?: Record<string, string>) => Promise<void>; | ||
mount: (base: string, driver: Driver, initialState?: Record<string, StorageValue>) => Promise<void>; | ||
unmount: (base: string, dispose?: boolean) => Promise<void>; | ||
dispose: () => Promise<void>; | ||
watch: (callback: WatchCallback) => Promise<void>; | ||
} | ||
@@ -30,2 +34,2 @@ | ||
export { Driver, DriverFactory, Storage, StorageValue, createStorage, snapshot }; | ||
export { Driver, DriverFactory, Storage, StorageValue, WatchCallback, WatchEvent, createStorage, snapshot }; |
@@ -111,6 +111,7 @@ 'use strict'; | ||
mounts: {"": memory()}, | ||
mountpoints: [""] | ||
mountpoints: [""], | ||
watching: false, | ||
watchListeners: [] | ||
}; | ||
const getMount = (key) => { | ||
key = normalizeKey(key); | ||
for (const base of ctx.mountpoints) { | ||
@@ -130,3 +131,2 @@ if (key.startsWith(base)) { | ||
const getMounts = (base) => { | ||
base = normalizeBase(base); | ||
return ctx.mountpoints.filter((mountpoint) => base.startsWith(mountpoint)).map((mountpoint) => ({ | ||
@@ -137,4 +137,23 @@ mountpoint, | ||
}; | ||
const onChange = (event, key) => { | ||
if (!ctx.watching) { | ||
return; | ||
} | ||
key = normalizeKey(key); | ||
for (const listener of ctx.watchListeners) { | ||
listener(event, key); | ||
} | ||
}; | ||
const startWatch = async () => { | ||
if (ctx.watching) { | ||
return; | ||
} | ||
ctx.watching = true; | ||
for (const mountpoint in ctx.mounts) { | ||
await watch(ctx.mounts[mountpoint], onChange, mountpoint); | ||
} | ||
}; | ||
const storage = { | ||
hasItem(key) { | ||
key = normalizeKey(key); | ||
const {relativeKey, driver} = getMount(key); | ||
@@ -144,11 +163,16 @@ return asyncCall(driver.hasItem, relativeKey); | ||
getItem(key) { | ||
key = normalizeKey(key); | ||
const {relativeKey, driver} = getMount(key); | ||
return asyncCall(driver.getItem, relativeKey).then((val) => destr(val)); | ||
}, | ||
setItem(key, value) { | ||
async setItem(key, value) { | ||
if (value === void 0) { | ||
return storage.removeItem(key); | ||
} | ||
key = normalizeKey(key); | ||
const {relativeKey, driver} = getMount(key); | ||
return asyncCall(driver.setItem, relativeKey, stringify(value)); | ||
await asyncCall(driver.setItem, relativeKey, stringify(value)); | ||
if (!driver.watch) { | ||
onChange("update", key); | ||
} | ||
}, | ||
@@ -159,5 +183,9 @@ async setItems(base, items) { | ||
}, | ||
removeItem(key) { | ||
async removeItem(key) { | ||
key = normalizeKey(key); | ||
const {relativeKey, driver} = getMount(key); | ||
return asyncCall(driver.removeItem, relativeKey); | ||
await asyncCall(driver.removeItem, relativeKey); | ||
if (!driver.watch) { | ||
onChange("remove", key); | ||
} | ||
}, | ||
@@ -174,2 +202,3 @@ async getKeys(base) { | ||
async clear(base) { | ||
base = normalizeBase(base); | ||
await Promise.all(getMounts(base).map((m) => asyncCall(m.driver.clear))); | ||
@@ -193,2 +222,5 @@ }, | ||
ctx.mounts[base] = driver; | ||
if (ctx.watching) { | ||
await watch(driver, onChange, base); | ||
} | ||
if (initialState) { | ||
@@ -208,2 +240,6 @@ await storage.setItems(base, initialState); | ||
delete ctx.mounts[base]; | ||
}, | ||
async watch(callback) { | ||
await startWatch(); | ||
ctx.watchListeners.push(callback); | ||
} | ||
@@ -222,2 +258,7 @@ }; | ||
} | ||
function watch(storage, onChange, base) { | ||
if (storage.watch) { | ||
return storage.watch((event, key) => onChange(event, base + key)); | ||
} | ||
} | ||
async function dispose(storage) { | ||
@@ -224,0 +265,0 @@ if (typeof storage.dispose === "function") { |
import {existsSync} from "fs"; | ||
import {resolve} from "path"; | ||
import {resolve, relative, join} from "path"; | ||
import {watch} from "chokidar"; | ||
import {readFile, writeFile, readdirRecursive, rmRecursive, unlink} from "./utils/node-fs"; | ||
export default (function(opts) { | ||
if (!opts.dir) { | ||
if (!opts.base) { | ||
throw new Error("dir is required"); | ||
} | ||
const r = (key) => resolve(opts.dir, key.replace(/:/g, "/")); | ||
if (!opts.ingore) { | ||
opts.ingore = [ | ||
"node_modules" | ||
]; | ||
} | ||
opts.base = resolve(opts.base); | ||
const r = (key) => join(opts.base, key.replace(/:/g, "/")); | ||
let _watcher; | ||
return { | ||
@@ -28,5 +36,27 @@ hasItem(key) { | ||
}, | ||
dispose() { | ||
async dispose() { | ||
if (_watcher) { | ||
await _watcher.close(); | ||
} | ||
}, | ||
watch(callback) { | ||
if (_watcher) { | ||
return; | ||
} | ||
return new Promise((resolve2, reject) => { | ||
_watcher = watch(opts.base, { | ||
ignoreInitial: true, | ||
ignored: opts.ingore, | ||
...opts.watchOptions | ||
}).on("ready", resolve2).on("error", reject).on("all", (eventName, path) => { | ||
path = relative(opts.base, path); | ||
if (eventName === "change" || eventName === "add") { | ||
callback("update", path); | ||
} else if (eventName === "unlink") { | ||
callback("remove", path); | ||
} | ||
}); | ||
}); | ||
} | ||
}; | ||
}); |
@@ -1,26 +0,52 @@ | ||
export default (function(opts) { | ||
const _localStorage = opts?.localStorage || globalThis.localStorage; | ||
if (!_localStorage) { | ||
export default (function(opts = {}) { | ||
if (!opts.window) { | ||
opts.window = typeof window !== "undefined" ? window : void 0; | ||
} | ||
if (!opts.localStorage) { | ||
opts.localStorage = opts.window?.localStorage; | ||
} | ||
if (!opts.localStorage) { | ||
throw new Error("localStorage not available"); | ||
} | ||
const r = (key) => (opts.base ? opts.base + ":" : "") + key; | ||
let _storageListener; | ||
return { | ||
hasItem(key) { | ||
return Object.prototype.hasOwnProperty.call(_localStorage, key); | ||
return Object.prototype.hasOwnProperty.call(opts.localStorage, r(key)); | ||
}, | ||
getItem(key) { | ||
return _localStorage.getItem(key); | ||
return opts.localStorage.getItem(r(key)); | ||
}, | ||
setItem(key, value) { | ||
return _localStorage.setItem(key, value); | ||
return opts.localStorage.setItem(r(key), value); | ||
}, | ||
removeItem(key) { | ||
return _localStorage.removeItem(key); | ||
return opts.localStorage.removeItem(r(key)); | ||
}, | ||
getKeys() { | ||
return Object.keys(_localStorage); | ||
return Object.keys(opts.localStorage); | ||
}, | ||
clear() { | ||
_localStorage.clear(); | ||
if (!opts.base) { | ||
opts.localStorage.clear(); | ||
} else { | ||
for (const key of Object.keys(opts.localStorage)) { | ||
opts.localStorage?.removeItem(key); | ||
} | ||
} | ||
if (opts.window && _storageListener) { | ||
opts.window.removeEventListener("storage", _storageListener); | ||
} | ||
}, | ||
watch(callback) { | ||
if (opts.window) { | ||
_storageListener = (ev) => { | ||
if (ev.key) { | ||
callback(ev.newValue ? "update" : "remove", ev.key); | ||
} | ||
}; | ||
opts.window.addEventListener("storage", _storageListener); | ||
} | ||
} | ||
}; | ||
}); |
{ | ||
"name": "unstorage", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "", | ||
@@ -21,4 +21,10 @@ "repository": "unjsio/unstorage", | ||
"release": "yarn test && standard-version && git push --follow-tags && npm publish", | ||
"test": "yarn lint && jest" | ||
"test": "yarn lint && jest", | ||
"doctoc": "doctoc README.md --notitle" | ||
}, | ||
"dependencies": { | ||
"chokidar": "^3.5.1", | ||
"ohmyfetch": "^0.1.8", | ||
"ufo": "^0.6.10" | ||
}, | ||
"devDependencies": { | ||
@@ -30,2 +36,3 @@ "@nuxtjs/eslint-config-typescript": "latest", | ||
"destr": "^1.1.0", | ||
"doctoc": "^2.0.0", | ||
"eslint": "latest", | ||
@@ -35,2 +42,3 @@ "jest": "latest", | ||
"jsdom": "^16.4.0", | ||
"listhen": "^0.1.4", | ||
"mkdist": "^0.1.2", | ||
@@ -37,0 +45,0 @@ "siroc": "latest", |
101
README.md
@@ -21,9 +21,38 @@ # unstorage | ||
- State snapshot | ||
- Abstract watcher | ||
WIP: | ||
- State watching | ||
- Key expiration | ||
- Storage server | ||
**Table of Contents** | ||
<!-- START doctoc generated TOC please keep comment here to allow auto update --> | ||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> | ||
- [Drivers](#drivers) | ||
- [Usage](#usage) | ||
- [Storage Interface](#storage-interface) | ||
- [`storage.hasItem(key)`](#storagehasitemkey) | ||
- [`storage.getItem(key)`](#storagegetitemkey) | ||
- [`storage.setItem(key, value)`](#storagesetitemkey-value) | ||
- [`storage.setItems(base, items)`](#storagesetitemsbase-items) | ||
- [`storage.removeItem(key)`](#storageremoveitemkey) | ||
- [`storage.getKeys(base?)`](#storagegetkeysbase) | ||
- [`storage.clear(base?)`](#storageclearbase) | ||
- [`storage.dispose()`](#storagedispose) | ||
- [`storage.mount(mountpoint, driver, initialState?)`](#storagemountmountpoint-driver-initialstate) | ||
- [`storage.unmount(mountpoint, dispose = true)`](#storageunmountmountpoint-dispose--true) | ||
- [`storage.watch(callback)`](#storagewatchcallback) | ||
- [Utils](#utils) | ||
- [`snapshot(storage, base?)`](#snapshotstorage-base) | ||
- [Drivers](#drivers-1) | ||
- [`fs` (node)](#fs-node) | ||
- [`localStorage` (browser)](#localstorage-browser) | ||
- [`memory` (universal)](#memory-universal) | ||
- [Contribution](#contribution) | ||
- [License](#license) | ||
<!-- END doctoc generated TOC please keep comment here to allow auto update --> | ||
## Drivers | ||
@@ -34,5 +63,5 @@ | ||
- [x] LocalStorage (Browser) | ||
- [x] HTTP (Universal) | ||
- [ ] Cookies (Browser) | ||
- [ ] Location params (Browser) | ||
- [ ] HTTP (Universal) | ||
- [ ] S3 (Universal) | ||
@@ -63,2 +92,4 @@ - [ ] Cloudflare KV (Workers and Universal) | ||
## Storage Interface | ||
### `storage.hasItem(key)` | ||
@@ -136,3 +167,3 @@ | ||
### `storage.mount(mountpoint, driver, items?)` | ||
### `storage.mount(mountpoint, driver, initialState?)` | ||
@@ -143,3 +174,3 @@ By default, everything is stored in memory. We can mount additional storage space in a Unix-like fashion. | ||
If `items` argument is provided, restores/hydrates state of mountpoint using `setItems`. | ||
If `initialState` argument is provided, restores/hydrates state of mountpoint using `setItems`. | ||
@@ -168,2 +199,16 @@ <!-- TODO: Explain mountpoint hiding --> | ||
```js | ||
await storage.unmount('/output') | ||
``` | ||
### `storage.watch(callback)` | ||
Starts watching on all mountpoints. If driver does not supports watching, only emits even when `storage.*` methods are called. | ||
```js | ||
await storage.watch((event, key) => { }) | ||
``` | ||
## Utils | ||
### `snapshot(storage, base?)` | ||
@@ -178,2 +223,50 @@ | ||
## Drivers | ||
### `fs` (node) | ||
Maps data to real filesystem using directory structure for nested keys. Supports watching using [chokidar](https://github.com/paulmillr/chokidar). | ||
```js | ||
import fsDriver from 'unstorage/drivers/memory' | ||
await storage.mount('/tmp', fsDriver({ base: './tmp' })) | ||
``` | ||
**Options:** | ||
- `base`: Base directory to isolate operations on this directory | ||
- `ignore`: Ignore patterns for watch <!-- and key listing --> | ||
- `watchOptions`: Additional [chokidar](https://github.com/paulmillr/chokidar) options. | ||
### `localStorage` (browser) | ||
Store data in [localStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage). | ||
```js | ||
import lsDriver from 'unstorage/drivers/memory' | ||
await storage.mount('local', lsDriver({ base: 'myapp' })) | ||
``` | ||
**Options:** | ||
- `base`: Add `${base}:` to all keys to avoid collision | ||
- `localStorage`: Optionally provide `localStorage` object | ||
- `window`: Optionally provide `window` object | ||
### `memory` (universal) | ||
Keeps data in memory using [Set](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set). | ||
By default it is mounted to top level so it is unlikely you need to mount it again. | ||
```js | ||
import memoryDriver from 'unstorage/drivers/memory' | ||
storage.mount('/tmp', memory()) | ||
``` | ||
## Contribution | ||
@@ -180,0 +273,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32926
12
754
291
3
17
+ Addedchokidar@^3.5.1
+ Addedohmyfetch@^0.1.8
+ Addedufo@^0.6.10
+ Addedanymatch@3.1.3(transitive)
+ Addedbinary-extensions@2.3.0(transitive)
+ Addedbraces@3.0.3(transitive)
+ Addedchokidar@3.6.0(transitive)
+ Addedfill-range@7.1.1(transitive)
+ Addedfsevents@2.3.3(transitive)
+ Addedglob-parent@5.1.2(transitive)
+ Addedis-binary-path@2.1.0(transitive)
+ Addedis-extglob@2.1.1(transitive)
+ Addedis-glob@4.0.3(transitive)
+ Addedis-number@7.0.0(transitive)
+ Addednode-fetch@2.7.0(transitive)
+ Addednormalize-path@3.0.0(transitive)
+ Addedohmyfetch@0.1.8(transitive)
+ Addedpicomatch@2.3.1(transitive)
+ Addedreaddirp@3.6.0(transitive)
+ Addedto-regex-range@5.0.1(transitive)
+ Addedtr46@0.0.3(transitive)
+ Addedufo@0.6.12(transitive)
+ Addedwebidl-conversions@3.0.1(transitive)
+ Addedwhatwg-url@5.0.0(transitive)