From 921e05d26092f6dbf5a4a302c4ac251944d5d008 Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 27 Apr 2024 12:21:59 +0200 Subject: [PATCH 1/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20define=20.scan()=20C?= =?UTF-8?q?RUD=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/crud/types.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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[]; -} From a148fb89d0373442f5773c6656e433abdafdb2a0 Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 27 Apr 2024 12:22:18 +0200 Subject: [PATCH 2/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20.scan()?= =?UTF-8?q?=20method=20for=20FSA=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/crud/__tests__/testCrudfs.ts | 53 ++++++++++++++++++++++++++++++-- src/fsa-to-crud/FsaCrud.ts | 18 ++++++----- 2 files changed, 61 insertions(+), 10 deletions(-) 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/fsa-to-crud/FsaCrud.ts b/src/fsa-to-crud/FsaCrud.ts index 9108ac64..59b3472b 100644 --- a/src/fsa-to-crud/FsaCrud.ts +++ b/src/fsa-to-crud/FsaCrud.ts @@ -137,23 +137,27 @@ 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; }; From 3d973b7dd4475c6a59443171795ef5449d11805b Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 27 Apr 2024 12:23:59 +0200 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20implement=20.scan()?= =?UTF-8?q?=20in=20Node.js=20CRUD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/node-to-crud/NodeCrud.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/node-to-crud/NodeCrud.ts b/src/node-to-crud/NodeCrud.ts index c5953129..6c4a527c 100644 --- a/src/node-to-crud/NodeCrud.ts +++ b/src/node-to-crud/NodeCrud.ts @@ -182,24 +182,28 @@ 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; }; From 2351ff27e034004fbfe73339178bc60b00488a0d Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 27 Apr 2024 12:27:13 +0200 Subject: [PATCH 4/5] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20Prettier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/crud-to-cas/CrudCas.ts | 5 +---- src/fsa-to-crud/FsaCrud.ts | 4 +++- src/fsa-to-node/FsaNodeDirent.ts | 5 +---- src/fsa-to-node/FsaNodeStats.ts | 6 +----- src/node-to-crud/NodeCrud.ts | 4 +++- src/node-to-fsa/NodeFileSystemDirectoryHandle.ts | 6 +----- src/node-to-fsa/NodeFileSystemFileHandle.ts | 6 +----- src/node-to-fsa/NodeFileSystemHandle.ts | 5 +---- src/node-to-fsa/NodeFileSystemWritableFileStream.ts | 6 +----- src/node-to-fsa/NodePermissionStatus.ts | 5 +---- 10 files changed, 14 insertions(+), 38 deletions(-) diff --git a/src/crud-to-cas/CrudCas.ts b/src/crud-to-cas/CrudCas.ts index d08e8266..d9881697 100644 --- a/src/crud-to-cas/CrudCas.ts +++ b/src/crud-to-cas/CrudCas.ts @@ -9,10 +9,7 @@ export interface CrudCasOptions { const hashEqual = (h1: string, h2: string) => h1 === h2; export class CrudCas extends CrudCasBase { - constructor( - protected readonly crud: CrudApi, - protected readonly options: CrudCasOptions, - ) { + constructor(protected readonly crud: CrudApi, protected readonly options: CrudCasOptions) { super(crud, options.hash, hashToLocation, hashEqual); } } diff --git a/src/fsa-to-crud/FsaCrud.ts b/src/fsa-to-crud/FsaCrud.ts index 59b3472b..7bce1c45 100644 --- a/src/fsa-to-crud/FsaCrud.ts +++ b/src/fsa-to-crud/FsaCrud.ts @@ -137,7 +137,9 @@ export class FsaCrud implements crud.CrudApi { } }; - public readonly scan = async function* (collection: crud.CrudCollection): AsyncIterableIterator { + public readonly scan = async function* ( + collection: crud.CrudCollection, + ): AsyncIterableIterator { assertType(collection, 'scan', 'crudfs'); const [dir] = await this.getDir(collection, false); for await (const [id, handle] of dir.entries()) { diff --git a/src/fsa-to-node/FsaNodeDirent.ts b/src/fsa-to-node/FsaNodeDirent.ts index 634167c4..94251926 100644 --- a/src/fsa-to-node/FsaNodeDirent.ts +++ b/src/fsa-to-node/FsaNodeDirent.ts @@ -1,10 +1,7 @@ import type { IDirent, TDataOut } from '../node/types/misc'; export class FsaNodeDirent implements IDirent { - public constructor( - public readonly name: TDataOut, - protected readonly kind: 'file' | 'directory', - ) {} + public constructor(public readonly name: TDataOut, protected readonly kind: 'file' | 'directory') {} public isDirectory(): boolean { return this.kind === 'directory'; diff --git a/src/fsa-to-node/FsaNodeStats.ts b/src/fsa-to-node/FsaNodeStats.ts index d91756a4..3407dba0 100644 --- a/src/fsa-to-node/FsaNodeStats.ts +++ b/src/fsa-to-node/FsaNodeStats.ts @@ -24,11 +24,7 @@ export class FsaNodeStats implements misc.IStats { public readonly mode: T; public readonly nlink: T; - public constructor( - isBigInt: boolean, - size: T, - protected readonly kind: 'file' | 'directory', - ) { + public constructor(isBigInt: boolean, size: T, protected readonly kind: 'file' | 'directory') { const dummy = (isBigInt ? timex : time) as any as T; this.uid = dummy; this.gid = dummy; diff --git a/src/node-to-crud/NodeCrud.ts b/src/node-to-crud/NodeCrud.ts index 6c4a527c..b449609e 100644 --- a/src/node-to-crud/NodeCrud.ts +++ b/src/node-to-crud/NodeCrud.ts @@ -182,7 +182,9 @@ export class NodeCrud implements crud.CrudApi { } }; - public readonly scan = async function* (collection: crud.CrudCollection): AsyncIterableIterator { + 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[]; diff --git a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts index 8fa9b443..1bf7fa21 100644 --- a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts +++ b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts @@ -27,11 +27,7 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle implemen /** Directory path with trailing slash. */ public readonly __path: string; - public constructor( - protected readonly fs: NodeFsaFs, - path: string, - ctx: Partial = {}, - ) { + public constructor(protected readonly fs: NodeFsaFs, path: string, ctx: Partial = {}) { super('directory', basename(path, ctx.separator || '/')); this.ctx = createCtx(ctx); this.__path = path[path.length - 1] === this.ctx.separator ? path : path + this.ctx.separator; diff --git a/src/node-to-fsa/NodeFileSystemFileHandle.ts b/src/node-to-fsa/NodeFileSystemFileHandle.ts index 31faf332..ab5206bd 100644 --- a/src/node-to-fsa/NodeFileSystemFileHandle.ts +++ b/src/node-to-fsa/NodeFileSystemFileHandle.ts @@ -8,11 +8,7 @@ import type { IFileSystemFileHandle, IFileSystemSyncAccessHandle } from '../fsa/ export class NodeFileSystemFileHandle extends NodeFileSystemHandle implements IFileSystemFileHandle { protected readonly ctx: NodeFsaContext; - constructor( - protected readonly fs: NodeFsaFs, - public readonly __path: string, - ctx: Partial = {}, - ) { + constructor(protected readonly fs: NodeFsaFs, public readonly __path: string, ctx: Partial = {}) { ctx = createCtx(ctx); super('file', basename(__path, ctx.separator!)); this.ctx = ctx as NodeFsaContext; diff --git a/src/node-to-fsa/NodeFileSystemHandle.ts b/src/node-to-fsa/NodeFileSystemHandle.ts index 8dc19a29..95233d63 100644 --- a/src/node-to-fsa/NodeFileSystemHandle.ts +++ b/src/node-to-fsa/NodeFileSystemHandle.ts @@ -8,10 +8,7 @@ import type { IFileSystemHandle, FileSystemHandlePermissionDescriptor } from '.. * @see [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle) */ export abstract class NodeFileSystemHandle implements IFileSystemHandle { - constructor( - public readonly kind: 'file' | 'directory', - public readonly name: string, - ) {} + constructor(public readonly kind: 'file' | 'directory', public readonly name: string) {} /** * Compares two handles to see if the associated entries (either a file or directory) match. diff --git a/src/node-to-fsa/NodeFileSystemWritableFileStream.ts b/src/node-to-fsa/NodeFileSystemWritableFileStream.ts index 61e7f91f..d9381ce1 100644 --- a/src/node-to-fsa/NodeFileSystemWritableFileStream.ts +++ b/src/node-to-fsa/NodeFileSystemWritableFileStream.ts @@ -61,11 +61,7 @@ interface SwapFile { export class NodeFileSystemWritableFileStream extends WritableStream implements IFileSystemWritableFileStream { protected readonly swap: SwapFile; - constructor( - protected readonly fs: NodeFsaFs, - protected readonly path: string, - keepExistingData: boolean, - ) { + constructor(protected readonly fs: NodeFsaFs, protected readonly path: string, keepExistingData: boolean) { const swap: SwapFile = { handle: undefined, path: '', offset: 0 }; super({ async start() { diff --git a/src/node-to-fsa/NodePermissionStatus.ts b/src/node-to-fsa/NodePermissionStatus.ts index 2de2e33f..6039bb0a 100644 --- a/src/node-to-fsa/NodePermissionStatus.ts +++ b/src/node-to-fsa/NodePermissionStatus.ts @@ -2,8 +2,5 @@ * @see [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/PermissionStatus) */ export class NodePermissionStatus { - constructor( - public readonly name: string, - public readonly state: 'granted' | 'denied' | 'prompt', - ) {} + constructor(public readonly name: string, public readonly state: 'granted' | 'denied' | 'prompt') {} } From 456e004be6a3240a10b51138b5bd79e76585f827 Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 27 Apr 2024 12:36:25 +0200 Subject: [PATCH 5/5] =?UTF-8?q?style:=20=F0=9F=92=84=20run=20Prettier=20wi?= =?UTF-8?q?th=20updated=20deps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/crud-to-cas/CrudCas.ts | 5 ++++- src/fsa-to-node/FsaNodeDirent.ts | 5 ++++- src/fsa-to-node/FsaNodeStats.ts | 6 +++++- src/node-to-fsa/NodeFileSystemDirectoryHandle.ts | 6 +++++- src/node-to-fsa/NodeFileSystemFileHandle.ts | 6 +++++- src/node-to-fsa/NodeFileSystemHandle.ts | 5 ++++- src/node-to-fsa/NodeFileSystemWritableFileStream.ts | 6 +++++- src/node-to-fsa/NodePermissionStatus.ts | 5 ++++- 8 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/crud-to-cas/CrudCas.ts b/src/crud-to-cas/CrudCas.ts index d9881697..d08e8266 100644 --- a/src/crud-to-cas/CrudCas.ts +++ b/src/crud-to-cas/CrudCas.ts @@ -9,7 +9,10 @@ export interface CrudCasOptions { const hashEqual = (h1: string, h2: string) => h1 === h2; export class CrudCas extends CrudCasBase { - constructor(protected readonly crud: CrudApi, protected readonly options: CrudCasOptions) { + constructor( + protected readonly crud: CrudApi, + protected readonly options: CrudCasOptions, + ) { super(crud, options.hash, hashToLocation, hashEqual); } } diff --git a/src/fsa-to-node/FsaNodeDirent.ts b/src/fsa-to-node/FsaNodeDirent.ts index 94251926..634167c4 100644 --- a/src/fsa-to-node/FsaNodeDirent.ts +++ b/src/fsa-to-node/FsaNodeDirent.ts @@ -1,7 +1,10 @@ import type { IDirent, TDataOut } from '../node/types/misc'; export class FsaNodeDirent implements IDirent { - public constructor(public readonly name: TDataOut, protected readonly kind: 'file' | 'directory') {} + public constructor( + public readonly name: TDataOut, + protected readonly kind: 'file' | 'directory', + ) {} public isDirectory(): boolean { return this.kind === 'directory'; diff --git a/src/fsa-to-node/FsaNodeStats.ts b/src/fsa-to-node/FsaNodeStats.ts index 3407dba0..d91756a4 100644 --- a/src/fsa-to-node/FsaNodeStats.ts +++ b/src/fsa-to-node/FsaNodeStats.ts @@ -24,7 +24,11 @@ export class FsaNodeStats implements misc.IStats { public readonly mode: T; public readonly nlink: T; - public constructor(isBigInt: boolean, size: T, protected readonly kind: 'file' | 'directory') { + public constructor( + isBigInt: boolean, + size: T, + protected readonly kind: 'file' | 'directory', + ) { const dummy = (isBigInt ? timex : time) as any as T; this.uid = dummy; this.gid = dummy; diff --git a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts index 1bf7fa21..8fa9b443 100644 --- a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts +++ b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts @@ -27,7 +27,11 @@ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle implemen /** Directory path with trailing slash. */ public readonly __path: string; - public constructor(protected readonly fs: NodeFsaFs, path: string, ctx: Partial = {}) { + public constructor( + protected readonly fs: NodeFsaFs, + path: string, + ctx: Partial = {}, + ) { super('directory', basename(path, ctx.separator || '/')); this.ctx = createCtx(ctx); this.__path = path[path.length - 1] === this.ctx.separator ? path : path + this.ctx.separator; diff --git a/src/node-to-fsa/NodeFileSystemFileHandle.ts b/src/node-to-fsa/NodeFileSystemFileHandle.ts index ab5206bd..31faf332 100644 --- a/src/node-to-fsa/NodeFileSystemFileHandle.ts +++ b/src/node-to-fsa/NodeFileSystemFileHandle.ts @@ -8,7 +8,11 @@ import type { IFileSystemFileHandle, IFileSystemSyncAccessHandle } from '../fsa/ export class NodeFileSystemFileHandle extends NodeFileSystemHandle implements IFileSystemFileHandle { protected readonly ctx: NodeFsaContext; - constructor(protected readonly fs: NodeFsaFs, public readonly __path: string, ctx: Partial = {}) { + constructor( + protected readonly fs: NodeFsaFs, + public readonly __path: string, + ctx: Partial = {}, + ) { ctx = createCtx(ctx); super('file', basename(__path, ctx.separator!)); this.ctx = ctx as NodeFsaContext; diff --git a/src/node-to-fsa/NodeFileSystemHandle.ts b/src/node-to-fsa/NodeFileSystemHandle.ts index 95233d63..8dc19a29 100644 --- a/src/node-to-fsa/NodeFileSystemHandle.ts +++ b/src/node-to-fsa/NodeFileSystemHandle.ts @@ -8,7 +8,10 @@ import type { IFileSystemHandle, FileSystemHandlePermissionDescriptor } from '.. * @see [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle) */ export abstract class NodeFileSystemHandle implements IFileSystemHandle { - constructor(public readonly kind: 'file' | 'directory', public readonly name: string) {} + constructor( + public readonly kind: 'file' | 'directory', + public readonly name: string, + ) {} /** * Compares two handles to see if the associated entries (either a file or directory) match. diff --git a/src/node-to-fsa/NodeFileSystemWritableFileStream.ts b/src/node-to-fsa/NodeFileSystemWritableFileStream.ts index d9381ce1..61e7f91f 100644 --- a/src/node-to-fsa/NodeFileSystemWritableFileStream.ts +++ b/src/node-to-fsa/NodeFileSystemWritableFileStream.ts @@ -61,7 +61,11 @@ interface SwapFile { export class NodeFileSystemWritableFileStream extends WritableStream implements IFileSystemWritableFileStream { protected readonly swap: SwapFile; - constructor(protected readonly fs: NodeFsaFs, protected readonly path: string, keepExistingData: boolean) { + constructor( + protected readonly fs: NodeFsaFs, + protected readonly path: string, + keepExistingData: boolean, + ) { const swap: SwapFile = { handle: undefined, path: '', offset: 0 }; super({ async start() { diff --git a/src/node-to-fsa/NodePermissionStatus.ts b/src/node-to-fsa/NodePermissionStatus.ts index 6039bb0a..2de2e33f 100644 --- a/src/node-to-fsa/NodePermissionStatus.ts +++ b/src/node-to-fsa/NodePermissionStatus.ts @@ -2,5 +2,8 @@ * @see [MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/PermissionStatus) */ export class NodePermissionStatus { - constructor(public readonly name: string, public readonly state: 'granted' | 'denied' | 'prompt') {} + constructor( + public readonly name: string, + public readonly state: 'granted' | 'denied' | 'prompt', + ) {} }