Skip to content

Commit

Permalink
feat(fs): support read-only and read-write file systems
Browse files Browse the repository at this point in the history
  • Loading branch information
blacha committed Jun 15, 2022
1 parent f907f11 commit a7f52a9
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 41 deletions.
43 changes: 35 additions & 8 deletions packages/fs/src/__test__/fs.abstraction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,35 @@ o.spec('FileSystemAbstraction', () => {
const fakeUnknown = new FakeSystem('unknown');
fsa.register('', fakeUnknown);

o(fsa.get('fake://foo').protocol).equals('fake');
o(fsa.get('fake:/foo').protocol).equals('unknown');
o(fsa.get('fake://foo', 'r').protocol).equals('fake');
o(fsa.get('fake:/foo', 'r').protocol).equals('unknown');
});

o('should register filesystems as rw', () => {
const fsa = new FileSystemAbstraction();

const fakeLocal = new FakeSystem('fake');
fsa.register('fake://', fakeLocal);
o(fsa.get('fake://foo', 'rw').protocol).equals('fake');
});

o('should not return a read only filesystem when wanting to write', () => {
const fsa = new FileSystemAbstraction();

const fakeLocal = new FakeSystem('fake');
fsa.register('fake://', fakeLocal, 'r');
o(() => fsa.get('fake://foo', 'rw')).throws(Error);
});

o('should allow read and read-write file systems to be registered', () => {
const fsa = new FileSystemAbstraction();

const fakeR = new FakeSystem('r');
const fakeRw = new FakeSystem('rw');

fsa.register('fake://', fakeR, 'r');
fsa.register('fake://', fakeRw, 'rw');
o(fsa.get('fake://foo', 'rw').protocol).equals('rw');
});

o('should find file systems in order they were registered', () => {
Expand All @@ -31,9 +58,9 @@ o.spec('FileSystemAbstraction', () => {
fsa.register('fake://', fakeA);
fsa.register('fake://some-prefix-string/', fakeB);

o(fsa.get('fake://foo').protocol).equals('fake');
o(fsa.get('fake://some-prefix-string/').protocol).equals('fakeSpecific');
o(fsa.get('fake://some-prefix-string/some-key').protocol).equals('fakeSpecific');
o(fsa.get('fake://foo', 'r').protocol).equals('fake');
o(fsa.get('fake://some-prefix-string/', 'r').protocol).equals('fakeSpecific');
o(fsa.get('fake://some-prefix-string/some-key', 'r').protocol).equals('fakeSpecific');
});

o('should order file systems by length', () => {
Expand All @@ -44,9 +71,9 @@ o.spec('FileSystemAbstraction', () => {
fsa.register('fake://some-prefix-string/', fakeB);
fsa.register('fake://', fakeA);

o(fsa.get('fake://foo').protocol).equals('fake');
o(fsa.get('fake://some-prefix-string/').protocol).equals('fakeSpecific');
o(fsa.get('fake://some-prefix-string/some-key').protocol).equals('fakeSpecific');
o(fsa.get('fake://foo', 'r').protocol).equals('fake');
o(fsa.get('fake://some-prefix-string/', 'r').protocol).equals('fakeSpecific');
o(fsa.get('fake://some-prefix-string/some-key', 'r').protocol).equals('fakeSpecific');
});

o('should replace file systems when registering duplicates', () => {
Expand Down
61 changes: 28 additions & 33 deletions packages/fs/src/fs.abstraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,37 @@ function isRecord(obj: unknown): obj is Record<string, unknown> {
return obj.constructor === Object;
}

export type Flag = FlagRead | FlagReadWrite;
export type FlagRead = 'r';
export type FlagReadWrite = 'rw';

export class FileSystemAbstraction implements FileSystem {
protocol = 'abstract';
/**
* Is the systems array currently ordered
* @see FileSystemAbstraction.sortSystems
*/
private isOrdered = true;
systems: { path: string; system: FileSystem }[] = [];
systems: { path: string; system: FileSystem; flag: Flag }[] = [];

/**
* Register a file system to a specific path which can then be used with any `fsa` command
*
* @example
* fsa.register('s3://', fsS3)
* fsa.register('s3://bucket-a/key-a', specificS3)
* fsa.register('s3://', fsS3, 'rw')
* fsa.register('s3://bucket-a/key-a', specificS3, 'r')
* fsa.register('http://', fsHttp)
*
*/
register(path: string, system: FileSystem): void {
register(path: string, system: FileSystem, flag: Flag = 'rw'): void {
for (let i = 0; i < this.systems.length; i++) {
const sys = this.systems[i];
if (sys.path === path) {
this.systems.splice(i, 1, { path, system });
if (sys.path === path && sys.flag === flag) {
this.systems.splice(i, 1, { path, system, flag });
return;
}
}
this.systems.push({ path, system });
this.systems.push({ path, system, flag });
this.isOrdered = false;
}

Expand All @@ -56,10 +60,11 @@ export class FileSystemAbstraction implements FileSystem {
* @returns Content of the file
*/
read(filePath: string): Promise<Buffer> {
return this.get(filePath).read(filePath);
return this.get(filePath, 'r').read(filePath);
}

/** Read a file as JSON
/**
* Read a file as JSON
* @param filePath file to read
* @returns JSON Content of the file
*/
Expand All @@ -75,7 +80,7 @@ export class FileSystemAbstraction implements FileSystem {
* @returns Stream of file contents
*/
stream(filePath: string): Readable {
return this.get(filePath).stream(filePath);
return this.get(filePath, 'r').stream(filePath);
}

/**
Expand All @@ -89,9 +94,9 @@ export class FileSystemAbstraction implements FileSystem {
write(filePath: string, buffer: FileWriteTypes, opts?: WriteOptions): Promise<void> {
if (Array.isArray(buffer) || isRecord(buffer)) {
const content = JSON.stringify(buffer, null, 2);
return this.get(filePath).write(filePath, content, { contentType: 'application/json', ...opts });
return this.get(filePath, 'rw').write(filePath, content, { contentType: 'application/json', ...opts });
}
return this.get(filePath).write(filePath, buffer, opts);
return this.get(filePath, 'rw').write(filePath, buffer, opts);
}

/**
Expand All @@ -100,7 +105,7 @@ export class FileSystemAbstraction implements FileSystem {
* @returns list of files inside that path
*/
list(filePath: string, opts?: ListOptions): AsyncGenerator<string> {
return this.get(filePath).list(filePath, opts);
return this.get(filePath, 'r').list(filePath, opts);
}

/**
Expand All @@ -111,7 +116,7 @@ export class FileSystemAbstraction implements FileSystem {
* @returns list of files inside that path
*/
details(filePath: string, opts?: ListOptions): AsyncGenerator<FileInfo> {
return this.get(filePath).details(filePath, opts);
return this.get(filePath, 'r').details(filePath, opts);
}

/**
Expand All @@ -121,7 +126,7 @@ export class FileSystemAbstraction implements FileSystem {
* @returns true if file exists, false otherwise
*/
exists(filePath: string): Promise<boolean> {
return this.get(filePath)
return this.get(filePath, 'r')
.head(filePath)
.then((f) => f != null);
}
Expand All @@ -133,7 +138,7 @@ export class FileSystemAbstraction implements FileSystem {
* @returns basic information such as file size
*/
head(filePath: string): Promise<FileInfo | null> {
return this.get(filePath).head(filePath);
return this.get(filePath, 'r').head(filePath);
}

join = joinUri;
Expand All @@ -145,7 +150,7 @@ export class FileSystemAbstraction implements FileSystem {
* @returns
*/
source(filePath: string): ChunkSource {
return this.get(filePath).source(filePath);
return this.get(filePath, 'r').source(filePath);
}

/**
Expand All @@ -160,28 +165,18 @@ export class FileSystemAbstraction implements FileSystem {
}

/** Find the filesystem that would be used for a given path */
get(filePath: string): FileSystem {
get(filePath: string, flag: Flag): FileSystem {
this.sortSystems();
for (const cfg of this.systems) {
if (filePath.startsWith(cfg.path)) return cfg.system;
if (filePath.startsWith(cfg.path)) {
// If we want to write to the system but only have read-only access
if (flag === 'rw' && cfg.flag === 'r') continue;
return cfg.system;
}
}

throw new Error(`Unable to find file system for path:${filePath}`);
}
}

export const fsa = new FileSystemAbstraction();

// async function main(): Promise<void> {
// for await (const f of fsa.list('')) {
// console.log(f);
// }

// for await (const f of fsa.list('', { details: true })) {
// console.log(f.path);
// }

// for await (const f of fsa.list('', { details: false, recursive: false })) {
// console.log(f.path);
// }
// }

0 comments on commit a7f52a9

Please sign in to comment.