diff --git a/README.md b/README.md index b0f11e16..a0c65b20 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,9 @@ WIP: -- Unmount +- State watching - Key expiration -- State compression -- Watcher +- Storage server ## Drivers @@ -113,6 +112,23 @@ If a base is provided, only keys starting with base will be returned also only m await storage.getKeys() ``` +### `storage.clear(base?)` + +Removes all stored key/values. If a base is provided, only mounts matching base will be cleared. + +```js +await storage.clear() +``` + +### `storage.dispose()` + +Disposes all mounted storages to ensure there are no open-handles left. Call it before exiting process. + +**Note:** Dispose also clears in-memory data. + +```js +await storage.dispose() +``` ### `storage.mount(mountpoint, driver, items?)` @@ -140,13 +156,9 @@ await storage.setItem('/output/test', 'works') await storage.setItem('/foo', 'bar') ``` -### `storage.clear(base?)` - -Removes all stored key/values. If a base is provided, only mounts matching base will be cleared. +### `storage.unmount(mountpoint, dispose = true)` -```js -await storage.clear() -``` +Unregisters a mountpoint. Has no effect if mountpoint is not found or is root. ### `snapshot(storage, base?)` @@ -157,16 +169,6 @@ import { snapshot } from 'unstorage' const data = await snapshot(storage, '/etc') ``` -### `storage.dispose()` - -Disposes all mounted storages to ensure there are no open-handles left. Call it before exiting process. - -**Note:** Dispose also clears in-memory data. - -```js -await storage.dispose() -``` - ## Contribution - Clone repository diff --git a/src/drivers/memory.ts b/src/drivers/memory.ts index 658accb9..21dd0383 100644 --- a/src/drivers/memory.ts +++ b/src/drivers/memory.ts @@ -10,19 +10,19 @@ export default function () { getItem (key) { return data.get(key) || null }, - setItem (key, value) { + setItem(key, value) { data.set(key, value) }, removeItem (key) { data.delete(key) }, - getKeys () { + getKeys() { return Array.from(data.keys()) }, - clear () { + clear() { data.clear() }, - dispose () { + dispose() { data.clear() } } diff --git a/src/storage.ts b/src/storage.ts index 6a028bee..5b272393 100644 --- a/src/storage.ts +++ b/src/storage.ts @@ -5,18 +5,18 @@ import { normalizeKey, normalizeBase, asyncCall, stringify } from './utils' interface StorageCTX { mounts: Record - mountKeys: string[] + mountpoints: string[] } export function createStorage (): Storage { const ctx: StorageCTX = { mounts: { '': memory() }, - mountKeys: [''] + mountpoints: [''] } const getMount = (key?: string) => { key = normalizeKey(key) - for (const base of ctx.mountKeys) { + for (const base of ctx.mountpoints) { if (key.startsWith(base)) { return { relativeKey: key.substr(base.length), @@ -32,9 +32,12 @@ export function createStorage (): Storage { const getMounts = (base?: string) => { base = normalizeBase(base) - return ctx.mountKeys - .filter(key => base!.startsWith(key)) - .map(key => getMount(key)) + return ctx.mountpoints + .filter(mountpoint => base!.startsWith(mountpoint)) + .map(mountpoint => ({ + mountpoint, + driver: ctx.mounts[mountpoint] + })) } const storage: Storage = { @@ -63,8 +66,11 @@ export function createStorage (): Storage { }, async getKeys (base) { base = normalizeBase(base) - const rawKeys = await Promise.all(getMounts(base).map(m => asyncCall(m.driver.getKeys))) - const keys = rawKeys.flat().map(key => normalizeKey(key)) + const keyGroups = await Promise.all(getMounts(base).map(async (mount) => { + const rawKeys = await asyncCall(mount.driver.getKeys) + return rawKeys.map(key => mount.mountpoint + normalizeKey(key)) + })) + const keys = keyGroups.flat() return base ? keys.filter(key => key.startsWith(base!)) : keys }, async clear (base) { @@ -74,15 +80,15 @@ export function createStorage (): Storage { await Promise.all(Object.values(ctx.mounts).map(driver => dispose(driver))) }, async mount (base, driver, initialState) { - base = normalizeKey(base) - if (!ctx.mountKeys.includes(base)) { - ctx.mountKeys.push(base) - ctx.mountKeys.sort((a, b) => b.length - a.length) + base = normalizeBase(base) + if (!ctx.mountpoints.includes(base)) { + ctx.mountpoints.push(base) + ctx.mountpoints.sort((a, b) => b.length - a.length) } if (ctx.mounts[base]) { if (ctx.mounts[base].dispose) { // eslint-disable-next-line no-console - dispose(ctx.mounts[base]!).catch(console.error) + await dispose(ctx.mounts[base]!) } delete ctx.mounts[base] } @@ -90,6 +96,17 @@ export function createStorage (): Storage { if (initialState) { await storage.setItems(base, initialState) } + }, + async unmount (base: string, _dispose = true) { + base = normalizeBase(base) + if (!base /* root */ || !ctx.mounts[base]) { + return + } + if (_dispose) { + await dispose(ctx.mounts[base]) + } + ctx.mountpoints = ctx.mountpoints.filter(key => key !== base) + delete ctx.mounts[base] } } diff --git a/src/types.ts b/src/types.ts index de834f03..9d123e84 100644 --- a/src/types.ts +++ b/src/types.ts @@ -21,5 +21,6 @@ export interface Storage { getKeys: (base?: string) => Promise clear: (base?: string) => Promise mount: (base: string, driver: Driver, initialState?: Record) => Promise + unmount: (base: string, dispose?: boolean) => Promise dispose: () => Promise } diff --git a/test/generic.test.ts b/test/drivers.test.ts similarity index 100% rename from test/generic.test.ts rename to test/drivers.test.ts diff --git a/test/snapshot.test.ts b/test/storage.test.ts similarity index 59% rename from test/snapshot.test.ts rename to test/storage.test.ts index 4b7a8170..348c3f32 100644 --- a/test/snapshot.test.ts +++ b/test/storage.test.ts @@ -1,20 +1,21 @@ import { createStorage, snapshot } from '../src' +import memory from '../src/drivers/memory' const data = { 'etc:conf': 'test', 'data:foo': 'bar' } -describe('snapshot', () => { - it('snapshot', async () => { +describe('storage', () => { + it('mount/unmount', async () => { const storage = createStorage() - await storage.setItems('', data) - expect(await snapshot(storage, '')).toMatchObject(data) + await storage.mount('/mnt', memory(), data) + expect(await snapshot(storage, '/mnt')).toMatchObject(data) }) - it('snapshot (subpath)', async () => { + it('snapshot', async () => { const storage = createStorage() await storage.setItems('', data) - expect(await snapshot(storage, 'etc')).toMatchObject({ conf: 'test' }) + expect(await snapshot(storage, '')).toMatchObject(data) }) })