Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge develop into main #65

Merged
merged 3 commits into from
Aug 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/chilly-walls-kneel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'feature-fetch': patch
'@blgc/utils': patch
---

Custom and very barebone Result implementation to not bloat bundle
3 changes: 1 addition & 2 deletions packages/feature-fetch/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@
"homepage": "https://builder.group/?source=package-json",
"dependencies": {
"@blgc/types": "workspace:*",
"@blgc/utils": "workspace:*",
"ts-results-es": "4.2.0"
"@blgc/utils": "workspace:*"
},
"devDependencies": {
"@blgc/config": "workspace:*",
Expand Down
3 changes: 2 additions & 1 deletion packages/feature-fetch/src/create-fetch-client.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll, describe, expect, it } from 'vitest';
import { unwrapErr } from '@blgc/utils';

import { createFetchClient } from './create-fetch-client';

Expand Down Expand Up @@ -54,6 +55,6 @@ describe('createFetchClient function', () => {
const result = await client._baseFetch('/test', 'GET', {});

expect(result.isErr()).toBe(true);
expect(result.unwrapErr()).toBeInstanceOf(Error);
expect(unwrapErr(result)).toBeInstanceOf(Error);
});
});
2 changes: 1 addition & 1 deletion packages/feature-fetch/src/create-fetch-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Err, Ok } from 'ts-results-es';
import { Err, Ok } from '@blgc/utils';

import { FetchError } from './exceptions';
import {
Expand Down
10 changes: 9 additions & 1 deletion packages/feature-fetch/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,12 @@ export * from './features';
export * from './helper';
export * from './types';

export * from 'ts-results-es';
export {
Err,
Ok,
unwrapErr,
unwrapOk,
type TErrResult,
type TOkResult,
type TResult
} from '@blgc/utils';
4 changes: 2 additions & 2 deletions packages/feature-fetch/src/types/fetch-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Result } from 'ts-results-es';
import { type TResult } from '@blgc/utils';

import type { FetchError, NetworkError, RequestError } from '../exceptions';
import type { FetchHeaders } from '../helper';
Expand Down Expand Up @@ -124,7 +124,7 @@ export type TFetchResponse<
GSuccessResponseBody,
GErrorResponseBody,
GParseAs extends TParseAs
> = Result<
> = TResult<
TFetchResponseSuccess<GSuccessResponseBody, GParseAs>,
TFetchResponseError<GErrorResponseBody>
>;
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export * from './multiply-vec2';
export * from './not-empty';
export * from './pick-properties';
export * from './random-hex';
export * from './result';
export * from './rgb-to-hex';
export * from './rgb-to-rgba';
export * from './rgba-to-rgb';
Expand Down
82 changes: 82 additions & 0 deletions packages/utils/src/result.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, expect, it } from 'vitest';

import { Err, Ok, unwrapErr, unwrapOk, type TResult } from './result';

describe('Result implementation', () => {
it('should create an Ok result correctly', () => {
const result = Ok(42);
expect(result._type).toBe('Ok');
expect(result.value).toBe(42);
expect(result.unwrap()).toBe(42);
expect(result.isOk()).toBe(true);
expect(result.isErr()).toBe(false);
});

it('should create an Err result correctly', () => {
const result = Err('Some error');
expect(result._type).toBe('Err');
expect(result.error).toBe('Some error');
expect(() => result.unwrap()).toThrowError();
expect(result.isOk()).toBe(false);
expect(result.isErr()).toBe(true);
});

it('should handle unwrap correctly for Ok result', () => {
const result = Ok('Success');
expect(result.unwrap()).toBe('Success');
});

it('should throw error on unwrap for Err result', () => {
const result = Err('Error occurred');
expect(() => result.unwrap()).toThrow('Error occurred');
});

it('should distinguish between Ok and Err using type guards', () => {
const okResult: TResult<number, string> = Ok(99);
const errResult: TResult<number, string> = Err('Failure');

if (okResult.isOk()) {
expect(okResult.value).toBe(99);
} else {
throw new Error('Expected okResult to be Ok');
}

if (errResult.isErr()) {
expect(errResult.error).toBe('Failure');
} else {
throw new Error('Expected errResult to be Err');
}
});

it('should handle multiple Ok and Err instances', () => {
const okResult1 = Ok(1);
const okResult2 = Ok(2);
const errResult1 = Err('First error');
const errResult2 = Err('Second error');

expect(okResult1.isOk()).toBe(true);
expect(okResult2.isOk()).toBe(true);
expect(errResult1.isErr()).toBe(true);
expect(errResult2.isErr()).toBe(true);
});

it('should handle unwrapOk correctly for Ok result', () => {
const result = Ok('Success');
expect(unwrapOk(result)).toBe('Success');
});

it('should throw error on unwrapErr for Ok result', () => {
const result = Ok('No error');
expect(() => unwrapErr(result)).toThrow('Expected an Err result');
});

it('should handle unwrapErr correctly for Err result', () => {
const result = Err('Error occurred');
expect(unwrapErr(result)).toBe('Error occurred');
});

it('should throw error on unwrapOk for Err result', () => {
const result = Err('Error occurred');
expect(() => unwrapOk(result)).toThrow('Expected an Ok result');
});
});
77 changes: 77 additions & 0 deletions packages/utils/src/result.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
export type TResultError = string | Error;

export interface TOkResult<T, E extends TResultError> {
_type: 'Ok';
value: T;
unwrap: () => T;
isOk: () => this is TOkResult<T, E>;
isErr: () => this is TErrResult<T, E>;
}

export interface TErrResult<T, E extends TResultError> {
_type: 'Err';
error: E;
unwrap: () => T;
isOk: () => this is TOkResult<T, E>;
isErr: () => this is TErrResult<T, E>;
}

export type TResult<T, E extends TResultError> = TOkResult<T, E> | TErrResult<T, E>;

// Factory function for creating an Ok result
export function Ok<T, E extends TResultError>(value: T): TOkResult<T, E> {
return {
_type: 'Ok',
value,
unwrap() {
return value;
},
// @ts-expect-error -- Assignable
isOk() {
return true;
},
// @ts-expect-error -- Assignable
isErr() {
return false;
}
};
}

// Factory function for creating an Err result
export function Err<T, E extends TResultError>(error: E): TErrResult<T, E> {
return {
_type: 'Err',
error,
unwrap() {
if (error instanceof Error) {
throw error;
} else {
throw new Error(error);
}
},
// @ts-expect-error -- Assignable
isOk() {
return false;
},
// @ts-expect-error -- Assignable
isErr() {
return true;
}
};
}

// Extracts value from an Ok result or throws an error if it's an Err
export function unwrapOk<T, E extends TResultError>(result: TResult<T, E>): T {
if (result.isOk()) {
return result.value;
}
throw new Error('Expected an Ok result');
}

// Extracts error from an Err result or throws an error if it's an Ok
export function unwrapErr<T, E extends TResultError>(result: TResult<T, E>): E {
if (result.isErr()) {
return result.error;
}
throw new Error('Expected an Err result');
}
8 changes: 0 additions & 8 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.