diff --git a/package.json b/package.json index ee97aee..5a04153 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@types/react-dom": "^17.0.9", "@types/sinon": "^10.0.4", "@types/sinon-chai": "^3.2.5", + "@types/wicg-file-system-access": "^2020.9.4", "@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/parser": "4.33.0", "chai": "^4.3.4", @@ -66,6 +67,7 @@ "trailingComma": "es5" }, "dependencies": { + "buffer": "^6.0.3", "tiny-invariant": "1.1.0", "tslib": "2.3.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 218aa7f..01a23ec 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,8 +13,10 @@ specifiers: '@types/react-dom': ^17.0.9 '@types/sinon': ^10.0.4 '@types/sinon-chai': ^3.2.5 + '@types/wicg-file-system-access': ^2020.9.4 '@typescript-eslint/eslint-plugin': 4.33.0 '@typescript-eslint/parser': 4.33.0 + buffer: ^6.0.3 chai: ^4.3.4 chai-as-promised: ^7.1.1 chalk: ^4.1.2 @@ -49,6 +51,7 @@ specifiers: typescript: ^4.4.3 dependencies: + buffer: 6.0.3 tiny-invariant: 1.1.0 tslib: 2.3.1 @@ -65,6 +68,7 @@ devDependencies: '@types/react-dom': 17.0.9 '@types/sinon': 10.0.4 '@types/sinon-chai': 3.2.5 + '@types/wicg-file-system-access': 2020.9.4 '@typescript-eslint/eslint-plugin': 4.33.0_d753869925cce96d3eb2141eeedafe57 '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.4.3 chai: 4.3.4 @@ -1591,6 +1595,10 @@ packages: resolution: {integrity: sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==} dev: true + /@types/wicg-file-system-access/2020.9.4: + resolution: {integrity: sha512-o43jUljwP0ZrQ927mPjGdJaBMfS12nf3VPj6Z52fMucxILrSs8tnfLbMDSn6cP3hrrLChc3SYneeEvecknNVtA==} + dev: true + /@typescript-eslint/eslint-plugin/4.33.0_d753869925cce96d3eb2141eeedafe57: resolution: {integrity: sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -1920,7 +1928,6 @@ packages: /base64-js/1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true /binary-extensions/2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} @@ -2074,7 +2081,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true /builtin-status-codes/3.0.0: resolution: {integrity: sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=} @@ -3514,7 +3520,6 @@ packages: /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true /ignore/4.0.6: resolution: {integrity: sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==} diff --git a/src/adapters/browser.ts b/src/adapters/browser.ts index e69de29..61a04cf 100644 --- a/src/adapters/browser.ts +++ b/src/adapters/browser.ts @@ -0,0 +1,89 @@ +import { promises as fs, existsSync } from "fs" +import { Buffer } from "buffer" + +import type { IAdapter, IFileSystem } from "./index" + +function splitPath(path: string) { + const segments = path.split("/") + const filename = segments.pop()! + return [segments, filename] as const +} + +export class BrowserAdapter implements IFileSystem { + constructor(private handle: FileSystemDirectoryHandle) {} + + static async create() { + const handle = await showDirectoryPicker() + return new BrowserAdapter(handle) + } + + private async getDirectoryHandle([first, ...pathSegments]: string[]) { + const handle = await pathSegments.reduce( + async (accum, next) => (await accum).getDirectoryHandle(next), + this.handle.getDirectoryHandle(first) + ) + return handle + } + + private async getFileHandle(path: string) { + const [segments, filename] = splitPath(path) + const dirHandle = await this.getDirectoryHandle(segments) + const fileHandle = await dirHandle.getFileHandle(filename) + return fileHandle + } + + async readFile(path: string) { + const handle = await this.getFileHandle(path) + const file = await handle.getFile() + return file.text() + } + + async existsSync(path: string): Promise { + const [segments, filename] = splitPath(path) + let handle = this.handle + for (const segment of segments) { + handle.values() + const next = await handle.getDirectoryHandle() + } + + throw new Error("Method not implemented.") + } + + async readBuffer(path: string): Promise { + const handle = await this.getFileHandle(path) + const file = await handle.getFile() + return Buffer.from(await file.arrayBuffer()) + } + + async writeFile(path: string, data: string): Promise { + const handle = await this.getFileHandle(path) + const writable = await handle.createWritable() + await writable.write(data) + await writable.close() + } + + readdir(path: string): Promise { + throw new Error("Method not implemented.") + } + + stat(path: string): Promise<{ isDirectory(): boolean }> { + throw new Error("Method not implemented.") + } +} + +/** + * Default Node.js adapter. This can be used while using `opvault.js` + * in a Node.js environment. + */ +const nodeAdapter: IAdapter = { + fs: { + readFile: path => fs.readFile(path, "utf-8"), + readBuffer: path => fs.readFile(path), + writeFile: fs.writeFile, + readdir: fs.readdir, + stat: fs.stat, + existsSync, + }, +} + +export default nodeAdapter