-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add file system abstraction layer (#34)
BREAKING CHANGE: this renames `@chunkd/source-url` to `@chunkd/source-http`
- Loading branch information
Showing
41 changed files
with
1,325 additions
and
284 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
export const enum ErrorCodes { | ||
PermissionDenied = 403, | ||
NotFound = 404, | ||
InternalError = 500, | ||
} | ||
/** | ||
* Utility error to wrap other errors to make them more understandable | ||
*/ | ||
export class CompositeError extends Error { | ||
name = 'CompositeError'; | ||
code: ErrorCodes; | ||
reason: unknown; | ||
|
||
constructor(msg: string, code: ErrorCodes, reason: unknown) { | ||
super(msg); | ||
this.code = code; | ||
this.reason = reason; | ||
} | ||
|
||
static isCompositeError(e: unknown): e is CompositeError { | ||
if (typeof e !== 'object' || e == null) return false; | ||
return (e as CompositeError).name === 'CompositeError'; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import type { Readable } from 'node:stream'; | ||
import { ChunkSource } from '.'; | ||
|
||
export interface FileInfo { | ||
/** file path */ | ||
path: string; | ||
/** | ||
* Size of file in bytes | ||
* undefined if no size found | ||
*/ | ||
size?: number; | ||
} | ||
|
||
export interface FileSystem<T extends ChunkSource = ChunkSource> { | ||
/** | ||
* Protocol used for communication | ||
* @example | ||
* file | ||
* s3 | ||
* http | ||
*/ | ||
protocol: string; | ||
/** Read a file into a buffer */ | ||
read(filePath: string): Promise<Buffer>; | ||
/** Create a read stream */ | ||
stream(filePath: string): Readable; | ||
/** Write a file from either a buffer or stream */ | ||
write(filePath: string, buffer: Buffer | Readable | string): Promise<void>; | ||
/** Recursively list all files in path */ | ||
list(filePath: string): AsyncGenerator<string>; | ||
/** Recursively list all files in path with additional details */ | ||
details(filePath: string): AsyncGenerator<FileInfo>; | ||
/** Does the path exists */ | ||
exists(filePath: string): Promise<boolean>; | ||
/** Get information about the path */ | ||
head(filePath: string): Promise<FileInfo | null>; | ||
/** Create a file source to read chunks out of */ | ||
source(filePath: string): T | null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# @chunkd/fs | ||
|
||
Utility functions for working with files that could either reside on the local file system or other sources like AWS S3 | ||
|
||
## Usage | ||
|
||
```typescript | ||
import { fsa } from '@chunkd/fs'; | ||
|
||
for await (const file of fsa.list('s3://foo/bar')) { | ||
// ['s3://foo/bar/baz.html', 's3://foo/bar/index.html'] | ||
} | ||
|
||
for await (const file of fsa.list('/home/blacha')) { | ||
// '/home/blacha/index.html' | ||
} | ||
|
||
// Convert the generator to an array | ||
const files = await fsa.toArray(fsa.list('s3://foo/bar')); | ||
``` | ||
|
||
This is designed for use with multiple s3 credentials | ||
|
||
```typescript | ||
import {fsa, FsAwsS3} from '@chunkd/fs' | ||
|
||
const bucketA = new S3({ credentials: bucketACredentials }) | ||
const bucketB = new S3({ credentials: bucketBCredentials }) | ||
|
||
fsa.register('s3://bucket-a', new FsAwsS3(bucketA)) | ||
fsa.register('s3://bucket-b', new FsAwsS3(bucketB)) | ||
|
||
// Stream a file from bucketA to bucketB | ||
await fsa.write('s3://bucket-b/foo', fsa.stream('s3://bucket-a/foo')) | ||
``` | ||
|
||
Or even any s3 compatible api | ||
|
||
```typescript | ||
import {fsa, FsAwsS3} from '@chunkd/fs' | ||
|
||
const bucketA = new S3({ endpoint: 'http://10.0.0.1:8080' }) | ||
const bucketB = new S3({ endpoint: 'http://10.0.0.99:8080' }) | ||
|
||
fsa.register('s3://bucket-a', new FsAwsS3(bucketA)) | ||
fsa.register('s3://bucket-b', new FsAwsS3(bucketB)) | ||
|
||
// Stream a file from bucketA (10.0.0.1) to bucketB (10.0.0.99) | ||
await fsa.write('s3://bucket-b/foo', fsa.stream('s3://bucket-a/foo')) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"name": "@chunkd/fs", | ||
"version": "6.0.0", | ||
"type": "module", | ||
"engines": { | ||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/blacha/chunkd.git", | ||
"directory": "packages/fs" | ||
}, | ||
"author": "Blayne Chard", | ||
"main": "./build/index.node.js", | ||
"browser": "./build/index.js", | ||
"types": "./build/index.d.ts", | ||
"license": "MIT", | ||
"scripts": {}, | ||
"dependencies": { | ||
"@chunkd/core": "^6.0.0", | ||
"@chunkd/source-file": "^6.0.0", | ||
"@chunkd/source-aws": "^6.0.0" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^14.14.31" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { FsFile } from '@chunkd/source-file'; | ||
import o from 'ospec'; | ||
import { FileSystemAbstraction } from '../fs.abstraction.js'; | ||
|
||
export class FakeSystem extends FsFile { | ||
constructor(protocol = 'fake') { | ||
super(); | ||
this.protocol = protocol; | ||
} | ||
} | ||
|
||
o.spec('FileSystemAbstraction', () => { | ||
o('should find file systems', () => { | ||
const fsa = new FileSystemAbstraction(); | ||
|
||
o(fsa.get('/foo').protocol).equals('file'); | ||
o(fsa.get('/').protocol).equals('file'); | ||
o(fsa.get('./').protocol).equals('file'); | ||
}); | ||
|
||
o('should register new file systems', () => { | ||
const fsa = new FileSystemAbstraction(); | ||
|
||
const fakeLocal = new FakeSystem('fake'); | ||
fsa.register('fake://', fakeLocal); | ||
|
||
o(fsa.get('/').protocol).equals('file'); | ||
o(fsa.get('//').protocol).equals('file'); | ||
|
||
o(fsa.get('fake://foo').protocol).equals('fake'); | ||
o(fsa.get('fake:/foo').protocol).equals('file'); | ||
o(fsa.get('fake//foo').protocol).equals('file'); | ||
o(fsa.get('fake').protocol).equals('file'); | ||
}); | ||
|
||
o('should find file systems in order they were registered', () => { | ||
const fakeA = new FakeSystem('fake'); | ||
const fakeB = new FakeSystem('fakeSpecific'); | ||
const fsa = new 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('should order file systems by length', () => { | ||
const fakeA = new FakeSystem('fake'); | ||
const fakeB = new FakeSystem('fakeSpecific'); | ||
const fsa = new 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'); | ||
}); | ||
}); |
Oops, something went wrong.