diff --git a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts index 42637b979..6b4721c14 100644 --- a/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts +++ b/src/node-to-fsa/NodeFileSystemDirectoryHandle.ts @@ -15,12 +15,14 @@ import type Dirent from '../Dirent'; * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle */ export class NodeFileSystemDirectoryHandle extends NodeFileSystemHandle { + protected readonly ctx: Partial; constructor( protected readonly fs: NodeFsaFs, public readonly __path: string, - protected readonly ctx: Partial = createCtx(ctx), + ctx: Partial = {}, ) { - super('directory', basename(__path, ctx.separator!)); + super('directory', basename(__path, ctx.separator || '/')); + this.ctx = createCtx(ctx); } /** diff --git a/src/node-to-fsa/NodeFileSystemFileHandle.ts b/src/node-to-fsa/NodeFileSystemFileHandle.ts index 186f6f51f..6038d4695 100644 --- a/src/node-to-fsa/NodeFileSystemFileHandle.ts +++ b/src/node-to-fsa/NodeFileSystemFileHandle.ts @@ -2,6 +2,7 @@ import { NodeFileSystemHandle } from './NodeFileSystemHandle'; import { NodeFileSystemSyncAccessHandle } from './NodeFileSystemSyncAccessHandle'; import { basename, ctx as createCtx, newNotAllowedError } from './util'; import type { NodeFsaContext, NodeFsaFs } from './types'; +import {NodeFileSystemWritableFileStream} from './NodeFileSystemWritableFileStream'; export class NodeFileSystemFileHandle extends NodeFileSystemHandle { constructor( @@ -51,7 +52,7 @@ export class NodeFileSystemFileHandle extends NodeFileSystemHandle { */ public async createWritable( { keepExistingData = false }: { keepExistingData?: boolean } = { keepExistingData: false }, - ): Promise { - throw new Error('Not implemented'); + ): Promise { + return new NodeFileSystemWritableFileStream(this.fs, this.__path); } } diff --git a/src/node-to-fsa/NodeFileSystemWritableFileStream.ts b/src/node-to-fsa/NodeFileSystemWritableFileStream.ts index 11ac4774d..c98e62846 100644 --- a/src/node-to-fsa/NodeFileSystemWritableFileStream.ts +++ b/src/node-to-fsa/NodeFileSystemWritableFileStream.ts @@ -1,4 +1,127 @@ +import type {IFileHandle} from "../promises"; +import type {NodeFsaFs} from "./types"; + /** + * Is a WritableStream object with additional convenience methods, which + * operates on a single file on disk. The interface is accessed through the + * `FileSystemFileHandle.createWritable()` method. + * * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream */ -export class NodeFileSystemWritableFileStream extends WritableStream {} +export class NodeFileSystemWritableFileStream extends WritableStream { + protected handle: IFileHandle | undefined = undefined; + + constructor( + protected readonly fs: NodeFsaFs, + protected readonly path: string, + ) { + const ref: {handle: IFileHandle | undefined} = {handle: undefined}; + super({ + async start() { + ref.handle = await fs.promises.open(path, 'w'); + }, + async write(chunk: Data) { + const handle = ref.handle; + if (!handle) throw new Error('Invalid state'); + const buffer = Buffer.from( + typeof chunk === 'string' + ? chunk + : chunk instanceof Blob + ? await chunk.arrayBuffer() + : chunk); + await handle.write(buffer); + }, + async close() { + if (ref.handle) await ref.handle.close(); + }, + async abort() { + if (ref.handle) await ref.handle.close(); + }, + }); + } + + /** + * @sse https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/seek + * @param position An `unsigned long` describing the byte position from the top + * (beginning) of the file. + */ + public async seek(position: number): Promise { + throw new Error('Not implemented'); + } + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/truncate + * @param size An `unsigned long` of the amount of bytes to resize the stream to. + */ + public async truncate(size: number): Promise { + throw new Error('Not implemented'); + } + + protected async writeBase(chunk: Data): Promise { + const writer = this.getWriter(); + try { + await writer.write(chunk); + } finally { + writer.releaseLock(); + } + } + + /** + * @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystemWritableFileStream/write + */ + public async write(chunk: Data): Promise; + public async write(params: FileSystemWritableFileStreamParams): Promise; + public async write(params): Promise { + if (!params) throw new TypeError('Missing required argument: params'); + switch (typeof params) { + case 'string': { + return this.writeBase(params); + } + case 'object': { + const constructor = params.constructor; + switch (constructor) { + case ArrayBuffer: + case Blob: + case DataView: return this.writeBase(params); + default: { + if (ArrayBuffer.isView(params)) return this.writeBase(params); + else { + switch (params.type) { + case 'write': return this.writeBase(params.data); + case 'truncate': return this.truncate(params.size); + case 'seek': return this.seek(params.position); + default: throw new TypeError('Invalid argument: params'); + } + } + } + } + } + default: throw new TypeError('Invalid argument: params'); + } + } +} + +export interface FileSystemWritableFileStreamParams { + type: 'write' | 'truncate' | 'seek'; + data?: Data; + position?: number; + size?: number; +} + +export type Data = + | ArrayBuffer + | ArrayBufferView + | Uint8Array + | Uint8ClampedArray + | Int8Array + | Uint16Array + | Int16Array + | Uint32Array + | Int32Array + | Float32Array + | Float64Array + | BigUint64Array + | BigInt64Array + | DataView + | Blob + | string; diff --git a/src/node-to-fsa/__tests__/NodeFileSystemFileHandle.test.ts b/src/node-to-fsa/__tests__/NodeFileSystemFileHandle.test.ts index 7a8b3ebaa..22f8e5ebc 100644 --- a/src/node-to-fsa/__tests__/NodeFileSystemFileHandle.test.ts +++ b/src/node-to-fsa/__tests__/NodeFileSystemFileHandle.test.ts @@ -24,4 +24,19 @@ maybe('NodeFileSystemFileHandle', () => { expect(contents).toBe('Hello, world!'); }); }); + + describe('.createWritable()', () => { + describe('.write(chunk)', () => { + test('can write to file', async () => { + const { dir, fs } = setup({ + 'file.txt': 'Hello, world!', + }); + const entry = await dir.getFileHandle('file.txt'); + const writable = await entry.createWritable(); + await writable.write('...'); + await writable.close(); + expect(fs.readFileSync('/file.txt', 'utf8')).toBe('...'); + }); + }); + }); }); diff --git a/src/promises.ts b/src/promises.ts index 9995eba90..7c8a2cc10 100644 --- a/src/promises.ts +++ b/src/promises.ts @@ -59,7 +59,7 @@ export interface IFileHandle { truncate(len?: number): Promise; utimes(atime: TTime, mtime: TTime): Promise; write( - buffer: Buffer | Uint8Array, + buffer: Buffer | ArrayBufferView | DataView, offset?: number, length?: number, position?: number, diff --git a/tsconfig.json b/tsconfig.json index 502b7083d..a1cab753f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "es5", + "target": "es6", "lib": ["ES2017", "dom"], "module": "commonjs", "removeComments": false,