Skip to content

Commit

Permalink
Merge pull request #1026 from streamich/crud-scan
Browse files Browse the repository at this point in the history
The `.scan()` method for CRUD
  • Loading branch information
streamich committed Apr 27, 2024
2 parents 577ee35 + 456e004 commit b01bec5
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 22 deletions.
53 changes: 50 additions & 3 deletions 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;
Expand Down Expand Up @@ -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((<any>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((<any>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((<any>err).message).toBe("Failed to execute 'drop' on 'crudfs': Name is not allowed.");
expect((<any>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 () => {
Expand All @@ -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'));
Expand Down
13 changes: 8 additions & 5 deletions src/crud/types.ts
Expand Up @@ -46,6 +46,14 @@ export interface CrudApi {
*/
drop: (collection: CrudCollection, silent?: boolean) => Promise<void>;

/**
* 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<CrudCollectionEntry>;

/**
* Fetches a list of resources of a collection, and sub-collections.
*
Expand Down Expand Up @@ -84,8 +92,3 @@ export interface CrudResourceInfo extends CrudCollectionEntry {
/** Timestamp when the resource was created. */
created?: number;
}

export interface CrudScanResult {
cursor: string | '';
list: CrudCollectionEntry[];
}
20 changes: 13 additions & 7 deletions src/fsa-to-crud/FsaCrud.ts
Expand Up @@ -137,23 +137,29 @@ export class FsaCrud implements crud.CrudApi {
}
};

public readonly list = async (collection: crud.CrudCollection): Promise<crud.CrudCollectionEntry[]> => {
assertType(collection, 'drop', 'crudfs');
public readonly scan = async function* (
collection: crud.CrudCollection,
): AsyncIterableIterator<crud.CrudCollectionEntry> {
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<crud.CrudCollectionEntry[]> => {
const entries: crud.CrudCollectionEntry[] = [];
for await (const entry of this.scan(collection)) entries.push(entry);
return entries;
};

Expand Down
20 changes: 13 additions & 7 deletions src/node-to-crud/NodeCrud.ts
Expand Up @@ -182,24 +182,30 @@ export class NodeCrud implements crud.CrudApi {
}
};

public readonly list = async (collection: crud.CrudCollection): Promise<crud.CrudCollectionEntry[]> => {
assertType(collection, 'drop', 'crudfs');
public readonly scan = async function* (
collection: crud.CrudCollection,
): AsyncIterableIterator<crud.CrudCollectionEntry> {
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<crud.CrudCollectionEntry[]> => {
const entries: crud.CrudCollectionEntry[] = [];
for await (const entry of this.scan(collection)) entries.push(entry);
return entries;
};

Expand Down

0 comments on commit b01bec5

Please sign in to comment.