Skip to content

Commit

Permalink
Convert use of Buffer into ArrayBuffer
Browse files Browse the repository at this point in the history
  • Loading branch information
konker committed Feb 14, 2024
1 parent 872ecf7 commit 5a4b34f
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 28 deletions.
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,19 @@ export type TinyFileSystem = {
exists: (fileOrDirPath: string) => P.Effect.Effect<never, TinyFileSystemError, boolean>;

/**
* Read the content of the given file into a Buffer
* Read the content of the given file into a Uint8Array
*
* @param filePath - The full path of the file to read
*/
readFile: (filePath: string) => P.Effect.Effect<never, TinyFileSystemError, Buffer>;
readFile: (filePath: string) => P.Effect.Effect<never, TinyFileSystemError, Uint8Array>;

/**
* Write the given data into the given file
*
* @param filePath - The full path of the file to write
* @param data - The data to write
*/
writeFile: (filePath: string, data: string | Buffer) => P.Effect.Effect<never, TinyFileSystemError, void>;
writeFile: (filePath: string, data: string | ArrayBuffer) => P.Effect.Effect<never, TinyFileSystemError, void>;

/**
* Delete the given file
Expand Down
21 changes: 21 additions & 0 deletions src/lib/array.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as unit from './array';

describe('array utils', () => {
describe('stringToUint8Array', () => {
it('should work as expected', () => {
expect(unit.stringToUint8Array('hello')).toEqual(new Uint8Array([104, 101, 108, 108, 111]));
expect(unit.stringToUint8Array('Emoji 🤯')).toEqual(
new Uint8Array([69, 109, 111, 106, 105, 32, 240, 159, 164, 175])
);
});
});

describe('unit8ArrayToString', () => {
it('should work as expected', () => {
expect(unit.uint8ArrayToString(new Uint8Array([104, 101, 108, 108, 111]))).toEqual('hello');
expect(unit.uint8ArrayToString(new Uint8Array([69, 109, 111, 106, 105, 32, 240, 159, 164, 175]))).toEqual(
'Emoji 🤯'
);
});
});
});
7 changes: 7 additions & 0 deletions src/lib/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export function stringToUint8Array(s: string | Buffer): Uint8Array {
return new TextEncoder().encode(String(s));
}

export function uint8ArrayToString(a: Uint8Array): string {
return new TextDecoder('utf-8').decode(a);
}
26 changes: 25 additions & 1 deletion src/lib/stream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,40 @@ import { PassThrough, Readable } from 'node:stream';

import * as P from '@konker.dev/effect-ts-prelude';

import { uint8ArrayToString } from './array';
import * as unit from './stream';

describe('stream utils', () => {
describe('readStreamToBuffer', () => {
it('should resolve as expected', async () => {
const readStream = Readable.from('konker');
const data = await P.Effect.runPromise(unit.readStreamToBuffer(readStream));
expect(uint8ArrayToString(data)).toEqual('konker');
});

it('should resolve as expected', async () => {
const readStream = Readable.from(Buffer.from('konker'));
const data = await P.Effect.runPromise(unit.readStreamToBuffer(readStream));
expect(uint8ArrayToString(data)).toEqual('konker');
});

it('should reject as expected', async () => {
const readStream = Readable.from('konker');
readStream.on('data', () => {
readStream.emit('error', new Error('Boom!'));
});

await expect(P.Effect.runPromise(unit.readStreamToBuffer(readStream))).rejects.toThrow('Boom!');
});
});

describe('waitForStreamPipe', () => {
it('should resolve as expected', async () => {
const readStream = Readable.from('konker');
const writeStream = new PassThrough();

const data = await P.Effect.runPromise(unit.waitForStreamPipe(readStream, writeStream));
expect(data).toBe(6);
expect(data).toEqual(6);
});

it('should reject as expected', async () => {
Expand Down
11 changes: 7 additions & 4 deletions src/lib/stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,27 @@ import * as P from '@konker.dev/effect-ts-prelude';
import { toError } from '@konker.dev/effect-ts-prelude';
import readline from 'readline';

import { stringToUint8Array } from './array';
import type { TinyFileSystemError } from './error';
import { toTinyFileSystemError } from './error';

/**
* Consume a readStream
* @param readStream
*/
export function readStreamToBuffer(readStream: Readable | ReadableStream): P.Effect.Effect<never, Error, Buffer> {
export function readStreamToBuffer(readStream: Readable | ReadableStream): P.Effect.Effect<never, Error, Uint8Array> {
return P.Effect.tryPromise({
try: async () => {
const chunks: Array<Buffer> = [];
const chunks: Array<Uint8Array> = [];
// FIXME: disabled lint
// eslint-disable-next-line fp/no-loops,fp/no-nil
for await (const chunk of readStream) {
// eslint-disable-next-line fp/no-mutating-methods,fp/no-unused-expression
chunks.push(Buffer.from(chunk));
chunks.push(typeof chunk === 'string' ? stringToUint8Array(chunk) : new Uint8Array(chunk));
}
return Buffer.concat(chunks);

// Merge chunks
return chunks.reduce((acc, val) => new Uint8Array([...acc, ...val]), new Uint8Array());
},
catch: toError,
});
Expand Down
26 changes: 21 additions & 5 deletions src/memfs/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PassThrough, Readable, Writable } from 'stream';

import type { DirectoryPath } from '../index';
import { FileType } from '../index';
import { stringToUint8Array, uint8ArrayToString } from '../lib/array';
import * as memFs1Fixture from '../test/fixtures/memfs-1.json';
import * as unit from './index';

Expand Down Expand Up @@ -266,19 +267,26 @@ describe('MemFsTinyFileSystem', () => {

describe('readFile', () => {
let stub1: jest.SpyInstance;
beforeEach(() => {
stub1 = jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from('some test text'));
});
afterEach(() => {
stub1.mockClear();
});

it('should function correctly', async () => {
stub1 = jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from('some test text'));
const data = await P.Effect.runPromise(memFsTinyFileSystem.readFile('/foo/bar.txt'));

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(data.toString()).toBe('some test text');
expect(uint8ArrayToString(data)).toBe('some test text');
});

it('should function correctly', async () => {
stub1 = jest.spyOn(fs.promises, 'readFile').mockResolvedValue('some test text');
const data = await P.Effect.runPromise(memFsTinyFileSystem.readFile('/foo/bar.txt'));

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(uint8ArrayToString(data)).toBe('some test text');
});
});

Expand All @@ -296,7 +304,15 @@ describe('MemFsTinyFileSystem', () => {

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(stub1.mock.calls[0][1]).toBe('some test text');
expect(stub1.mock.calls[0][1]).toStrictEqual('some test text');
});

it('should function correctly', async () => {
await P.Effect.runPromise(memFsTinyFileSystem.writeFile('/foo/bar.txt', stringToUint8Array('some test text')));

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(stub1.mock.calls[0][1]).toStrictEqual(Buffer.from(stringToUint8Array('some test text')));
});
});

Expand Down
16 changes: 12 additions & 4 deletions src/memfs/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'node:buffer';
import path from 'node:path';
import readline from 'node:readline';
import type { Readable, Writable } from 'node:stream';
Expand All @@ -8,6 +9,7 @@ import { fs, vol } from 'memfs';

import type { DirectoryPath, FileName, Path, Ref, TinyFileSystemAppendable, TinyFileSystemWithGlob } from '../index';
import { FileType, fileTypeIsFile } from '../index';
import { stringToUint8Array } from '../lib/array';
import type { TinyFileSystemError } from '../lib/error';
import { toTinyFileSystemError } from '../lib/error';

Expand Down Expand Up @@ -110,15 +112,21 @@ function removeDirectory(dirPath: string): P.Effect.Effect<never, TinyFileSystem
});
}

function readFile(filePath: string): P.Effect.Effect<never, TinyFileSystemError, Buffer> {
function readFile(filePath: string): P.Effect.Effect<never, TinyFileSystemError, Uint8Array> {
return P.Effect.tryPromise({
try: async () => Buffer.from(await fs.promises.readFile(filePath)),
try: async () => {
const data = await fs.promises.readFile(filePath);
return typeof data === 'string' ? stringToUint8Array(data) : new Uint8Array(data);
},
catch: toTinyFileSystemError,
});
}

function writeFile(filePath: string, data: Buffer | string): P.Effect.Effect<never, TinyFileSystemError, void> {
return P.Effect.tryPromise({ try: async () => fs.promises.writeFile(filePath, data), catch: toTinyFileSystemError });
function writeFile(filePath: string, data: ArrayBuffer | string): P.Effect.Effect<never, TinyFileSystemError, void> {
return P.Effect.tryPromise({
try: async () => fs.promises.writeFile(filePath, typeof data === 'string' ? data : Buffer.from(data)),
catch: toTinyFileSystemError,
});
}

function deleteFile(filePath: string): P.Effect.Effect<never, TinyFileSystemError, void> {
Expand Down
13 changes: 11 additions & 2 deletions src/node/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { PassThrough, Readable, Writable } from 'stream';

import type { DirectoryPath } from '../index';
import { FileType } from '../index';
import { stringToUint8Array, uint8ArrayToString } from '../lib/array';
import { NodeTinyFileSystem as unit } from './index';

describe('NodeTinyFileSystem', () => {
Expand Down Expand Up @@ -256,7 +257,7 @@ describe('NodeTinyFileSystem', () => {

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(data.toString()).toBe('some test text');
expect(uint8ArrayToString(data)).toBe('some test text');
});
});

Expand All @@ -274,7 +275,15 @@ describe('NodeTinyFileSystem', () => {

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(stub1.mock.calls[0][1]).toBe('some test text');
expect(stub1.mock.calls[0][1]).toStrictEqual(Buffer.from('some test text'));
});

it('should function correctly', async () => {
await P.Effect.runPromise(unit.writeFile('/foo/bar.txt', stringToUint8Array('some test text')));

expect(stub1).toHaveBeenCalledTimes(1);
expect(stub1.mock.calls[0][0]).toBe('/foo/bar.txt');
expect(stub1.mock.calls[0][1]).toStrictEqual(Buffer.from('some test text'));
});
});

Expand Down
15 changes: 11 additions & 4 deletions src/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'node:buffer';
import fs from 'node:fs';
import path from 'node:path';
import readline from 'node:readline';
Expand Down Expand Up @@ -110,12 +111,18 @@ function removeDirectory(dirPath: string): P.Effect.Effect<never, TinyFileSystem
});
}

function readFile(filePath: string): P.Effect.Effect<never, TinyFileSystemError, Buffer> {
return P.Effect.tryPromise({ try: async () => fs.promises.readFile(filePath), catch: toTinyFileSystemError });
function readFile(filePath: string): P.Effect.Effect<never, TinyFileSystemError, Uint8Array> {
return P.Effect.tryPromise({
try: async () => new Uint8Array(Buffer.from(await fs.promises.readFile(filePath))),
catch: toTinyFileSystemError,
});
}

function writeFile(filePath: string, data: Buffer | string): P.Effect.Effect<never, TinyFileSystemError, void> {
return P.Effect.tryPromise({ try: async () => fs.promises.writeFile(filePath, data), catch: toTinyFileSystemError });
function writeFile(filePath: string, data: ArrayBuffer | string): P.Effect.Effect<never, TinyFileSystemError, void> {
return P.Effect.tryPromise({
try: async () => fs.promises.writeFile(filePath, typeof data === 'string' ? Buffer.from(data) : Buffer.from(data)),
catch: toTinyFileSystemError,
});
}

function deleteFile(filePath: string): P.Effect.Effect<never, TinyFileSystemError, void> {
Expand Down
17 changes: 15 additions & 2 deletions src/s3/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PassThrough, Writable } from 'stream';

import type { TinyFileSystem } from '../index';
import { FileType } from '../index';
import { stringToUint8Array, uint8ArrayToString } from '../lib/array';
import * as unit from './index';

describe('S3TinyFileSystem', () => {
Expand Down Expand Up @@ -285,7 +286,7 @@ describe('S3TinyFileSystem', () => {
it('should function correctly', async () => {
const result = await P.Effect.runPromise(s3TinyFileSystem.readFile('s3://foobucket/bar/exists.txt'));
expect(s3GetObjectMock).toHaveBeenCalledTimes(1);
expect(result.toString()).toBe('test-file-data');
expect(uint8ArrayToString(result)).toBe('test-file-data');
});

it('should fail correctly', async () => {
Expand Down Expand Up @@ -339,7 +340,19 @@ describe('S3TinyFileSystem', () => {
Bucket: 'foobucket',
Key: 'bar/qux.txt',
});
expect(s3UploadObjectMock?.mock?.calls?.[0]?.[1]).toBe('wham-bam-thank-you-sam');
expect(s3UploadObjectMock?.mock?.calls?.[0]?.[1]).toStrictEqual(Buffer.from('wham-bam-thank-you-sam'));
});

it('should function correctly', async () => {
await P.Effect.runPromise(
s3TinyFileSystem.writeFile('s3://foobucket/bar/qux.txt', stringToUint8Array('wham-bam-thank-you-sam'))
);
expect(s3UploadObjectMock).toHaveBeenCalledTimes(1);
expect(s3UploadObjectMock?.mock?.calls?.[0]?.[0]).toStrictEqual({
Bucket: 'foobucket',
Key: 'bar/qux.txt',
});
expect(s3UploadObjectMock?.mock?.calls?.[0]?.[1]).toStrictEqual(Buffer.from('wham-bam-thank-you-sam'));
});

it('should fail correctly', async () => {
Expand Down
8 changes: 5 additions & 3 deletions src/s3/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Buffer } from 'node:buffer';
import type readline from 'node:readline';
import type { Readable, Writable } from 'node:stream';

Expand Down Expand Up @@ -159,7 +160,7 @@ const getFileType =

const readFile =
(s3Client: S3Client) =>
(s3url: string): P.Effect.Effect<never, TinyFileSystemError, Buffer> => {
(s3url: string): P.Effect.Effect<never, TinyFileSystemError, Uint8Array> => {
return P.pipe(
s3Utils.parseS3Url(s3url),
P.Effect.filterOrFail(s3UrlDataIsFile, () =>
Expand All @@ -177,14 +178,15 @@ const readFile =
)
),
P.Effect.flatMap((resp) => readStreamToBuffer(resp.Body as Readable)),
P.Effect.map((buffer) => new Uint8Array(buffer)),
P.Effect.mapError(toTinyFileSystemError),
P.Effect.provideService(S3ClientDeps, S3ClientDeps.of({ s3Client }))
);
};

const writeFile =
(s3Client: S3Client) =>
(s3url: string, data: Buffer | string): P.Effect.Effect<never, TinyFileSystemError, void> => {
(s3url: string, data: ArrayBuffer | string): P.Effect.Effect<never, TinyFileSystemError, void> => {
return P.pipe(
s3Utils.parseS3Url(s3url),
P.Effect.filterOrFail(s3UrlDataIsFile, () =>
Expand All @@ -196,7 +198,7 @@ const writeFile =
Bucket: parsed.Bucket,
Key: parsed.FullPath,
},
data
typeof data === 'string' ? Buffer.from(data) : Buffer.from(data)
)
),
P.Effect.mapError(toTinyFileSystemError),
Expand Down

0 comments on commit 5a4b34f

Please sign in to comment.