From 1b893a2e5039b50b1aa8d62e2c45e00c3e107108 Mon Sep 17 00:00:00 2001 From: streamich Date: Tue, 20 Jun 2023 22:57:44 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20.drop()=20me?= =?UTF-8?q?thod?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/crud/types.ts | 4 +- src/fsa-crud/FsaCrud.ts | 32 ++++++++++++---- src/fsa-crud/__tests__/FsaCrud.test.ts | 51 ++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/src/crud/types.ts b/src/crud/types.ts index aa1eb61dd..86d2b5561 100644 --- a/src/crud/types.ts +++ b/src/crud/types.ts @@ -41,8 +41,10 @@ export interface CrudApi { * Deletes all resources of a collection, and deletes recursively all sub-collections. * * @param collection Type of the resource, collection name. + * @param silent When true, does not throw an error if the collection or + * resource does not exist. Default is false. */ - drop: (collection: CrudCollection) => Promise; + drop: (collection: CrudCollection, silent?: boolean) => Promise; /** * Fetches a list of resources of a collection, and sub-collections. diff --git a/src/fsa-crud/FsaCrud.ts b/src/fsa-crud/FsaCrud.ts index d218af450..d08d0da64 100644 --- a/src/fsa-crud/FsaCrud.ts +++ b/src/fsa-crud/FsaCrud.ts @@ -6,12 +6,16 @@ import {assertType} from './util'; export class FsaCrud implements crud.CrudApi { public constructor (protected readonly root: fsa.IFileSystemDirectoryHandle | Promise) {} - protected async getDir(collection: crud.CrudCollection, create: boolean): Promise { + protected async getDir(collection: crud.CrudCollection, create: boolean): Promise<[dir: fsa.IFileSystemDirectoryHandle, parent: fsa.IFileSystemDirectoryHandle | undefined]> { + let parent: undefined | fsa.IFileSystemDirectoryHandle = undefined; let dir = await this.root; try { - for (const name of collection) - dir = await dir.getDirectoryHandle(name, {create}); - return dir; + for (const name of collection) { + const child = await dir.getDirectoryHandle(name, {create}); + parent = dir; + dir = child; + } + return [dir, parent]; } catch (error) { if (error.name === 'NotFoundError') throw new DOMException(`Collection /${collection.join('/')} does not exist`, 'CollectionNotFound'); @@ -20,7 +24,7 @@ export class FsaCrud implements crud.CrudApi { } protected async getFile(collection: crud.CrudCollection, id: string): Promise<[dir: fsa.IFileSystemDirectoryHandle, file: fsa.IFileSystemFileHandle]> { - const dir = await this.getDir(collection, false); + const [dir] = await this.getDir(collection, false); try { const file = await dir.getFileHandle(id, {create: false}); return [dir, file]; @@ -34,7 +38,7 @@ export class FsaCrud implements crud.CrudApi { public readonly put = async (collection: crud.CrudCollection, id: string, data: Uint8Array, options?: crud.CrudPutOptions): Promise => { assertType(collection, 'put', 'crudfs'); assertName(id, 'put', 'crudfs'); - const dir = await this.getDir(collection, true); + const [dir] = await this.getDir(collection, true); let file: fsa.IFileSystemFileHandle | undefined; switch (options?.throwIf) { case 'exists': { @@ -106,8 +110,20 @@ export class FsaCrud implements crud.CrudApi { } }; - public readonly drop = async (collection: crud.CrudCollection): Promise => { - throw new Error('Not implemented'); + public readonly drop = async (collection: crud.CrudCollection, silent?: boolean): Promise => { + assertType(collection, 'drop', 'crudfs'); + try { + const [dir, parent] = await this.getDir(collection, false); + if (parent) { + await parent.removeEntry(dir.name, {recursive: true}); + } else { + const root = await this.root; + for await (const name of root.keys()) + await root.removeEntry(name, {recursive: true}); + } + } catch (error) { + if (!silent) throw error; + } }; public readonly list = async (collection: crud.CrudCollection): Promise => { diff --git a/src/fsa-crud/__tests__/FsaCrud.test.ts b/src/fsa-crud/__tests__/FsaCrud.test.ts index 25ae12fc9..4cac208ae 100644 --- a/src/fsa-crud/__tests__/FsaCrud.test.ts +++ b/src/fsa-crud/__tests__/FsaCrud.test.ts @@ -224,4 +224,55 @@ onlyOnNode20('FsaCrud', () => { expect((err).name).toBe('CollectionNotFound'); }); }); + + describe('.drop()', () => { + test('throws if the collection is not valid', async () => { + const {crud} = setup(); + const [, err] = await of(crud.drop(['', 'foo'])); + expect(err).toBeInstanceOf(TypeError); + expect((err).message).toBe("Failed to execute 'drop' on 'crudfs': Name is not allowed."); + }); + + test('can recursively delete a collection', async () => { + const {crud} = setup(); + await crud.put(['foo', 'a'], 'bar', b('1')); + await crud.put(['foo', 'a'], 'baz', b('2')); + await crud.put(['foo', 'b'], 'xyz', b('3')); + const info = await crud.info(['foo', 'a']); + expect(info.type).toBe('collection'); + await crud.drop(['foo', 'a']); + const [, err] = await of(crud.info(['foo', 'a'])); + expect(err).toBeInstanceOf(DOMException); + expect((err).name).toBe('CollectionNotFound'); + }); + + test('throws if collection does not exist', async () => { + const {crud} = setup(); + await crud.put(['foo', 'a'], 'bar', b('1')); + await crud.put(['foo', 'a'], 'baz', b('2')); + await crud.put(['foo', 'b'], 'xyz', b('3')); + const [, err] = await of(crud.drop(['gg'])); + expect(err).toBeInstanceOf(DOMException); + expect((err).name).toBe('CollectionNotFound'); + }); + + test('when "silent" flag set, does not throw if collection does not exist', async () => { + const {crud} = setup(); + await crud.put(['foo', 'a'], 'bar', b('1')); + await crud.put(['foo', 'a'], 'baz', b('2')); + await crud.put(['foo', 'b'], 'xyz', b('3')); + await crud.drop(['gg'], true); + }); + + test('can recursively delete everything from root', async () => { + const {crud, snapshot} = setup(); + await crud.put(['foo', 'a'], 'bar', b('1')); + await crud.put(['baz', 'a'], 'baz', b('2')); + await crud.put(['bar', 'b'], 'xyz', b('3')); + const info = await crud.info(['foo', 'a']); + expect(info.type).toBe('collection'); + await crud.drop([]); + expect(snapshot()).toEqual({}); + }); + }); });