Skip to content

Commit

Permalink
feat: 🎸 improve writing to file
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Jun 20, 2023
1 parent 71b6afc commit 1de0e06
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 6 deletions.
25 changes: 20 additions & 5 deletions src/node-to-fsa/NodeFileSystemWritableFileStream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { NodeFsaFs } from './types';
interface Ref {
handle: IFileHandle | undefined;
offset: number;
open?: Promise<void>;
}

/**
Expand All @@ -20,7 +21,9 @@ export class NodeFileSystemWritableFileStream extends WritableStream {
const ref: Ref = { handle: undefined, offset: 0 };
super({
async start() {
ref.handle = await fs.promises.open(path, keepExistingData ? 'a+' : 'w');
const open = fs.promises.open(path, keepExistingData ? 'a+' : 'w');
ref.open = open.then(() => undefined);
ref.handle = await open;
},
async write(chunk: Data) {
const handle = ref.handle;
Expand Down Expand Up @@ -55,7 +58,11 @@ export class NodeFileSystemWritableFileStream extends WritableStream {
* @param size An `unsigned long` of the amount of bytes to resize the stream to.
*/
public async truncate(size: number): Promise<void> {
throw new Error('Not implemented');
await this.ref.open;
const handle = this.ref.handle;
if (!handle) throw new Error('Invalid state');
await handle.truncate(size);
if (this.ref.offset > size) this.ref.offset = size;
}

protected async writeBase(chunk: Data): Promise<void> {
Expand Down Expand Up @@ -88,11 +95,19 @@ export class NodeFileSystemWritableFileStream extends WritableStream {
default: {
if (ArrayBuffer.isView(params)) return this.writeBase(params);
else {
switch (params.type) {
case 'write':
const options = params as FileSystemWritableFileStreamParams;
switch (options.type) {
case 'write': {
if (typeof options.position === 'number')
await this.seek(options.position);
return this.writeBase(params.data);
case 'truncate':
}
case 'truncate': {
if (typeof params.size !== 'number')
throw new TypeError('Missing required argument: size');
if (this.ref.offset > params.size) this.ref.offset = params.size;
return this.truncate(params.size);
}
case 'seek':
return this.seek(params.position);
default:
Expand Down
87 changes: 86 additions & 1 deletion src/node-to-fsa/__tests__/NodeFileSystemFileHandle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,53 @@ maybe('NodeFileSystemFileHandle', () => {
});

describe('.createWritable()', () => {
describe('.truncate()', () => {
test('can truncate file', async () => {
const { dir, fs } = setup({
'file.txt': '012345',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.truncate(3);
await writable.close();
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('012');
});

test('can truncate file - 2', async () => {
const { dir, fs } = setup({
'file.txt': '012345',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.write({type: 'truncate', size: 3})
await writable.close();
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('012');
});

test('can truncate up', async () => {
const { dir, fs } = setup({
'file.txt': '012345',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.write({type: 'truncate', size: 10})
await writable.close();
expect(fs.readFileSync('/file.txt').length).toBe(10);
expect(fs.readFileSync('/file.txt')[8]).toBe(0);
});

test('on up truncation bytes are nulled', async () => {
const { dir, fs } = setup({
'file.txt': '012345',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.write({type: 'truncate', size: 10})
await writable.close();
expect(fs.readFileSync('/file.txt')[8]).toBe(0);
});
});

describe('.write(chunk)', () => {
test('overwrites the file when write is being executed', async () => {
const { dir, fs } = setup({
Expand All @@ -38,6 +85,17 @@ maybe('NodeFileSystemFileHandle', () => {
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('...');
});

test('writes at file start', async () => {
const { dir, fs } = setup({
'file.txt': '...',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.write('1');
await writable.close();
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('1..');
});

test('can seek and then write', async () => {
const { dir, fs } = setup({
'file.txt': '...',
Expand All @@ -47,8 +105,35 @@ maybe('NodeFileSystemFileHandle', () => {
writable.seek(1);
await writable.write('1');
await writable.write('2');
await writable.close();
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('.12');
writable.seek(0);
await writable.write('0');
await writable.close();
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('012');
});
});

describe('.write(options)', () => {
test('can write at offset, when providing position in write call', async () => {
const { dir, fs } = setup({
'file.txt': '...',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.write({type: 'write', position: 1, data: '1'});
await writable.close();
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('.1.');
});

test('can seek and then write', async () => {
const { dir, fs } = setup({
'file.txt': '...',
});
const entry = await dir.getFileHandle('file.txt');
const writable = await entry.createWritable({keepExistingData: true});
await writable.write({type: 'seek', position: 1});
await writable.write({type: 'write', data: Buffer.from('1')});
expect(fs.readFileSync('/file.txt', 'utf8')).toBe('.1.');
});
});
});
Expand Down

0 comments on commit 1de0e06

Please sign in to comment.