diff --git a/src/crud/__tests__/testCrudfs.ts b/src/crud/__tests__/testCrudfs.ts index 433fe251..6054f71f 100644 --- a/src/crud/__tests__/testCrudfs.ts +++ b/src/crud/__tests__/testCrudfs.ts @@ -1,5 +1,5 @@ import { of } from '../../thingies'; -import type { CrudApi } from '../types'; +import type { CrudApi, CrudCollectionEntry } from '../types'; export type Setup = () => { crud: CrudApi; @@ -271,12 +271,59 @@ export const testCrudfs = (setup: Setup) => { }); }); + describe('.scan()', () => { + test('throws if the collection is not valid', async () => { + const { crud } = setup(); + try { + const iterable = crud.scan(['./..', 'foo']); + await iterable.next(); + throw 'should not reach here'; + } catch (err) { + expect(err).toBeInstanceOf(TypeError); + expect((err).message).toBe("Failed to execute 'scan' on 'crudfs': Name is not allowed."); + } + }); + + test('can retrieve a list of resources and collections at root', async () => { + const { crud } = setup(); + await crud.put(['foo'], 'bar', b('1')); + await crud.put([], 'baz', b('1')); + await crud.put([], 'qux', b('2')); + const list: CrudCollectionEntry[] = []; + for await (const entry of crud.scan([])) list.push(entry); + expect(list.length).toBe(3); + expect(list.find(x => x.id === 'baz')).toMatchObject({ + type: 'resource', + id: 'baz', + }); + expect(list.find(x => x.id === 'qux')).toMatchObject({ + type: 'resource', + id: 'qux', + }); + expect(list.find(x => x.id === 'foo')).toMatchObject({ + type: 'collection', + id: 'foo', + }); + }); + + test('throws when trying to list a non-existing collection', async () => { + const { crud } = setup(); + await crud.put(['foo'], 'bar', b('1')); + await crud.put([], 'baz', b('1')); + await crud.put([], 'qux', b('2')); + const iterator = crud.scan(['gg']); + const [, err] = await of(iterator.next()); + expect(err).toBeInstanceOf(DOMException); + expect((err).name).toBe('CollectionNotFound'); + }); + }); + describe('.list()', () => { test('throws if the collection is not valid', async () => { const { crud } = setup(); const [, err] = await of(crud.list(['./..', 'foo'])); expect(err).toBeInstanceOf(TypeError); - expect((err).message).toBe("Failed to execute 'drop' on 'crudfs': Name is not allowed."); + expect((err).message).toBe("Failed to execute 'scan' on 'crudfs': Name is not allowed."); }); test('can retrieve a list of resources and collections at root', async () => { @@ -300,7 +347,7 @@ export const testCrudfs = (setup: Setup) => { }); }); - test('throws when try to list a non-existing collection', async () => { + test('throws when trying to list a non-existing collection', async () => { const { crud } = setup(); await crud.put(['foo'], 'bar', b('1')); await crud.put([], 'baz', b('1')); diff --git a/src/crud/types.ts b/src/crud/types.ts index 13a100c8..1f39554c 100644 --- a/src/crud/types.ts +++ b/src/crud/types.ts @@ -46,6 +46,14 @@ export interface CrudApi { */ drop: (collection: CrudCollection, silent?: boolean) => Promise; + /** + * Iterates over all resources of a collection. + * + * @param collection Type of the resource, collection name. + * @returns Iterator of resources of the given type. + */ + scan: (collection: CrudCollection) => AsyncIterableIterator; + /** * Fetches a list of resources of a collection, and sub-collections. * @@ -84,8 +92,3 @@ export interface CrudResourceInfo extends CrudCollectionEntry { /** Timestamp when the resource was created. */ created?: number; } - -export interface CrudScanResult { - cursor: string | ''; - list: CrudCollectionEntry[]; -} diff --git a/src/fsa-to-crud/FsaCrud.ts b/src/fsa-to-crud/FsaCrud.ts index 9108ac64..7bce1c45 100644 --- a/src/fsa-to-crud/FsaCrud.ts +++ b/src/fsa-to-crud/FsaCrud.ts @@ -137,23 +137,29 @@ export class FsaCrud implements crud.CrudApi { } }; - public readonly list = async (collection: crud.CrudCollection): Promise => { - assertType(collection, 'drop', 'crudfs'); + public readonly scan = async function* ( + collection: crud.CrudCollection, + ): AsyncIterableIterator { + assertType(collection, 'scan', 'crudfs'); const [dir] = await this.getDir(collection, false); - const entries: crud.CrudCollectionEntry[] = []; for await (const [id, handle] of dir.entries()) { if (handle.kind === 'file') { - entries.push({ + yield { type: 'resource', id, - }); + }; } else if (handle.kind === 'directory') { - entries.push({ + yield { type: 'collection', id, - }); + }; } } + }; + + public readonly list = async (collection: crud.CrudCollection): Promise => { + const entries: crud.CrudCollectionEntry[] = []; + for await (const entry of this.scan(collection)) entries.push(entry); return entries; }; diff --git a/src/node-to-crud/NodeCrud.ts b/src/node-to-crud/NodeCrud.ts index c5953129..b449609e 100644 --- a/src/node-to-crud/NodeCrud.ts +++ b/src/node-to-crud/NodeCrud.ts @@ -182,24 +182,30 @@ export class NodeCrud implements crud.CrudApi { } }; - public readonly list = async (collection: crud.CrudCollection): Promise => { - assertType(collection, 'drop', 'crudfs'); + public readonly scan = async function* ( + collection: crud.CrudCollection, + ): AsyncIterableIterator { + assertType(collection, 'scan', 'crudfs'); const dir = await this.checkDir(collection); const dirents = (await this.fs.readdir(dir, { withFileTypes: true })) as IDirent[]; - const entries: crud.CrudCollectionEntry[] = []; for await (const entry of dirents) { if (entry.isFile()) { - entries.push({ + yield { type: 'resource', id: '' + entry.name, - }); + }; } else if (entry.isDirectory()) { - entries.push({ + yield { type: 'collection', id: '' + entry.name, - }); + }; } } + }; + + public readonly list = async (collection: crud.CrudCollection): Promise => { + const entries: crud.CrudCollectionEntry[] = []; + for await (const entry of this.scan(collection)) entries.push(entry); return entries; };