From bebd7d303479a07e7c09378890659ac7ffd32fcb Mon Sep 17 00:00:00 2001 From: bkiac Date: Thu, 2 Nov 2023 13:01:00 +0100 Subject: [PATCH 01/30] Add result error abstract class --- src/result_error.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/result_error.ts diff --git a/src/result_error.ts b/src/result_error.ts new file mode 100644 index 0000000..54a9ef9 --- /dev/null +++ b/src/result_error.ts @@ -0,0 +1,15 @@ +abstract class ResultError extends Error { + abstract readonly tag: string +} + +class StdError extends ResultError { + readonly tag = "std" +} + +class IoError extends ResultError { + readonly tag = "io" +} + +class HttpError extends ResultError { + readonly tag = "http" +} From ca031b5ed65a98a12ef7dc4f3299bbf2f6c086bc Mon Sep 17 00:00:00 2001 From: bkiac Date: Thu, 2 Nov 2023 13:47:10 +0100 Subject: [PATCH 02/30] Add try errors --- src/result_error.ts | 25 +++++++++++++++-------- src/try.ts | 49 +++++++++++++++++++++++++++++++++------------ test/try.test.ts | 20 +++++++++--------- 3 files changed, 63 insertions(+), 31 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index 54a9ef9..7e1ce4a 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -1,15 +1,24 @@ -abstract class ResultError extends Error { +import {InvalidErrorPanic, Panic} from "./panic" + +// TODO: Capture original error if possible +export abstract class ResultError extends Error { abstract readonly tag: string } -class StdError extends ResultError { - readonly tag = "std" +export class StdError extends ResultError { + static readonly tag = "std" + readonly tag = StdError.tag } -class IoError extends ResultError { - readonly tag = "io" -} +// TODO: Maybe arg should be StdError, and don't let consumer override panics? +export type ErrorHandler = (error: unknown) => E -class HttpError extends ResultError { - readonly tag = "http" +export function defaultErrorHandler(error: unknown): StdError { + if (error instanceof Panic) { + throw error + } + if (error instanceof Error) { + return new StdError() + } + throw new InvalidErrorPanic(error) } diff --git a/src/try.ts b/src/try.ts index 521f2bc..6696602 100644 --- a/src/try.ts +++ b/src/try.ts @@ -1,26 +1,42 @@ import {PromiseResult} from "./promise_result" import {Ok, type Result, Err} from "./result" -import {InvalidErrorPanic, Panic} from "./panic" +import {ErrorHandler, ResultError, StdError, defaultErrorHandler} from "./result_error" -export function handleError(error: unknown) { - if (error instanceof Panic) { - throw error - } - if (error instanceof Error) { - return error +// Couldn't figure out how to overload these functions without a TypeScript error and making +// the error handler required if the error template param is defined. + +export function tryFn(f: () => T): Result { + try { + return Ok(f()) + } catch (error) { + return Err(defaultErrorHandler(error)) } - throw new InvalidErrorPanic(error) } -export function tryFn(fn: () => T): Result { +export function tryFnWith( + f: () => T, + handleError: ErrorHandler, +): Result { try { - return Ok(fn()) + return Ok(f()) } catch (error) { return Err(handleError(error)) } } -export function tryPromise(promise: Promise): PromiseResult { +export function tryPromise(promise: Promise): PromiseResult { + return new PromiseResult( + promise.then( + (value) => Ok(value), + (error) => Err(defaultErrorHandler(error)), + ), + ) +} + +export function tryPromiseWith( + promise: Promise, + handleError: ErrorHandler, +): PromiseResult { return new PromiseResult( promise.then( (value) => Ok(value), @@ -29,6 +45,13 @@ export function tryPromise(promise: Promise): PromiseResult { ) } -export function tryAsyncFn(fn: () => Promise): PromiseResult { - return tryPromise(fn()) +export function tryAsyncFn(f: () => Promise): PromiseResult { + return tryPromise(f()) +} + +export function tryAsyncFnWith( + f: () => Promise, + handleError: ErrorHandler, +): PromiseResult { + return tryPromiseWith(f(), handleError) } diff --git a/test/try.test.ts b/test/try.test.ts index aa23526..1943e98 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -1,28 +1,28 @@ import {describe, expect, it} from "vitest" -import {InvalidErrorPanic, Panic, handleError, tryAsyncFn, tryFn, tryPromise} from "../src" +import {InvalidErrorPanic, Panic, defaultErrorHandler, tryAsyncFn, tryFn, tryPromise} from "../src" describe.concurrent("handleError", () => { it("returns an Error when given an Error", () => { class TestError extends Error {} const error = new TestError("Test error") - const err = handleError(error) + const err = defaultErrorHandler(error) expect(err).to.be.instanceof(TestError) }) it("throws a Panic when given a Panic", () => { const msg = "Test panic" const panic = new Panic(msg) - expect(() => handleError(panic)).to.throw(Panic, msg) + expect(() => defaultErrorHandler(panic)).to.throw(Panic, msg) }) it("throws a Panic when given an unknown value", () => { - expect(() => handleError(0)).to.throw(InvalidErrorPanic) - expect(() => handleError("")).to.throw(InvalidErrorPanic) - expect(() => handleError(true)).to.throw(InvalidErrorPanic) - expect(() => handleError(undefined)).to.throw(InvalidErrorPanic) - expect(() => handleError(null)).to.throw(InvalidErrorPanic) - expect(() => handleError({})).to.throw(InvalidErrorPanic) - expect(() => handleError([])).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler(0)).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler("")).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler(true)).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler(undefined)).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler(null)).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler({})).to.throw(InvalidErrorPanic) + expect(() => defaultErrorHandler([])).to.throw(InvalidErrorPanic) }) }) From 8ad5fd0b98fc15496a47ee11d8bbeccd7d890fd6 Mon Sep 17 00:00:00 2001 From: bkiac Date: Thu, 2 Nov 2023 13:51:50 +0100 Subject: [PATCH 03/30] Add from, into --- src/option.ts | 16 +++++++++------- src/promise_option.ts | 4 ++-- src/promise_result.ts | 4 ++-- src/result.ts | 30 +++++++++++++++++------------- test/option.test.ts | 6 +++--- test/promise_option.test.ts | 6 +++--- test/promise_result.test.ts | 6 +++--- test/result.test.ts | 6 +++--- 8 files changed, 42 insertions(+), 36 deletions(-) diff --git a/src/option.ts b/src/option.ts index 25a4e48..1cebfc5 100644 --- a/src/option.ts +++ b/src/option.ts @@ -19,7 +19,7 @@ export interface OptionMethods { unwrapOrElse(defaultValue: () => U): T | U xor(other: Option): Option - get(): T | null + into(): T | null match(some: (value: T) => A, none: () => B): A | B toString(): `Some(${string})` | "None" @@ -105,7 +105,7 @@ export class SomeImpl implements OptionMethods { return other.isSome() ? None : this } - get() { + into() { return this.value } @@ -124,12 +124,14 @@ export class SomeImpl implements OptionMethods { toJSON() { return {meta: "Some", data: this.value} as const } + + static from(value: T): Some { + return new SomeImpl(value) + } } export interface Some extends SomeImpl {} -export function Some(value: T): Some { - return new SomeImpl(value) -} +export const Some = SomeImpl.from export class NoneImpl implements OptionMethods { readonly some = false @@ -204,7 +206,7 @@ export class NoneImpl implements OptionMethods { return other } - get() { + into() { return null } @@ -226,6 +228,6 @@ export class NoneImpl implements OptionMethods { } export interface None extends NoneImpl {} -export const None: None = Object.freeze(new NoneImpl()) +export const None = new NoneImpl() export type Option = Some | None diff --git a/src/promise_option.ts b/src/promise_option.ts index df52ce3..cf36262 100644 --- a/src/promise_option.ts +++ b/src/promise_option.ts @@ -102,8 +102,8 @@ export class PromiseOption implements PromiseLike> { ) } - async get() { - return (await this).get() + async into() { + return (await this).into() } async match(some: (value: T) => A, none: () => B) { diff --git a/src/promise_result.ts b/src/promise_result.ts index 86a1189..7b79dbd 100644 --- a/src/promise_result.ts +++ b/src/promise_result.ts @@ -116,8 +116,8 @@ export class PromiseResult implements PromiseLike> { return (await this).unwrapOrElse(defaultValue) } - async get() { - return (await this).get() + async into() { + return (await this).into() } async match(ok: (value: T) => A, err: (error: E) => B) { diff --git a/src/result.ts b/src/result.ts index 12c1432..9e7dea9 100644 --- a/src/result.ts +++ b/src/result.ts @@ -22,7 +22,7 @@ export interface ResultMethods { unwrapOr(defaultValue: U): T | U unwrapOrElse(defaultValue: (error: E) => U): T | U - get(): T | E + into(): T | E match(ok: (value: T) => A, err: (error: E) => B): A | B toString(): `Ok(${string})` | `Err(${string})` @@ -124,7 +124,7 @@ export class OkImpl implements ResultMethods { return this.value } - get() { + into() { return this.value } @@ -143,14 +143,16 @@ export class OkImpl implements ResultMethods { toJSON() { return {meta: "Ok", data: this.value} as const } + + static from(): Ok + static from(value: T): Ok + static from(value?: T): Ok { + return new OkImpl(value ? value : null) as Ok + } } export interface Ok extends OkImpl {} -export function Ok(): Ok -export function Ok(value: T): Ok -export function Ok(value?: T): Ok { - return new OkImpl(value ? value : null) as Ok -} +export const Ok = OkImpl.from export class ErrImpl implements ResultMethods { readonly ok = false @@ -246,7 +248,7 @@ export class ErrImpl implements ResultMethods { return defaultValue(this.error) } - get() { + into() { return this.error } @@ -265,13 +267,15 @@ export class ErrImpl implements ResultMethods { toJSON() { return {meta: "Err", data: this.error} as const } + + static from(): Err + static from(error: E): Err + static from(error?: E): Err { + return new ErrImpl(error ? error : null) as Err + } } export interface Err extends ErrImpl {} -export function Err(): Err -export function Err(error: E): Err -export function Err(error?: E): Err { - return new ErrImpl(error ? error : null) as Err -} +export const Err = ErrImpl.from export type Result = Ok | Err diff --git a/test/option.test.ts b/test/option.test.ts index bacc835..baef9ca 100644 --- a/test/option.test.ts +++ b/test/option.test.ts @@ -287,15 +287,15 @@ describe.concurrent("xor", () => { }) }) -describe.concurrent("get", () => { +describe.concurrent("into", () => { it("returns the value when called on a Some option", () => { const option = Some(42) - expect(option.get()).toEqual(42) + expect(option.into()).toEqual(42) }) it("throws when called on a None option", () => { const option = None - expect(option.get()).toEqual(null) + expect(option.into()).toEqual(null) }) }) diff --git a/test/promise_option.test.ts b/test/promise_option.test.ts index 6aa7a03..9a94b63 100644 --- a/test/promise_option.test.ts +++ b/test/promise_option.test.ts @@ -279,15 +279,15 @@ describe.concurrent("xor", () => { }) }) -describe.concurrent("get", () => { +describe.concurrent("into", () => { it("returns the value when called on a Some option", async () => { const option = promiseSome(42) - await expect(option.get()).resolves.toEqual(42) + await expect(option.into()).resolves.toEqual(42) }) it("returns null when called on a None option", async () => { const option = promiseNone() - await expect(option.get()).resolves.toEqual(null) + await expect(option.into()).resolves.toEqual(null) }) }) diff --git a/test/promise_result.test.ts b/test/promise_result.test.ts index 688d0a0..04d1933 100644 --- a/test/promise_result.test.ts +++ b/test/promise_result.test.ts @@ -363,15 +363,15 @@ describe.concurrent("unwrapOrElse", () => { }) }) -describe.concurrent("get", () => { +describe.concurrent("into", () => { it("returns the value for an Ok result", async () => { const result = promiseOk(42) - await expect(result.get()).resolves.toEqual(42) + await expect(result.into()).resolves.toEqual(42) }) it("returns the err for an Err result", async () => { const result = promiseErr("error") - await expect(result.get()).resolves.toEqual("error") + await expect(result.into()).resolves.toEqual("error") }) }) diff --git a/test/result.test.ts b/test/result.test.ts index 3981b0f..a449f53 100644 --- a/test/result.test.ts +++ b/test/result.test.ts @@ -352,15 +352,15 @@ describe.concurrent("unwrapOrElse", () => { }) }) -describe.concurrent("get", () => { +describe.concurrent("into", () => { it("returns the value for an Ok result", () => { const result = Ok(42) as Result - expect(result.get()).toEqual(42) + expect(result.into()).toEqual(42) }) it("returns the err for an Err result", () => { const result = Err(42) as Result - expect(result.get()).toEqual(42) + expect(result.into()).toEqual(42) }) }) From 5f78d19a1ce4e060569f34294ca0f9cb17642d83 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 08:22:52 +0100 Subject: [PATCH 04/30] Update handler type --- src/result_error.ts | 5 ++--- src/try.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index 7e1ce4a..81db94f 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -10,10 +10,9 @@ export class StdError extends ResultError { readonly tag = StdError.tag } -// TODO: Maybe arg should be StdError, and don't let consumer override panics? -export type ErrorHandler = (error: unknown) => E +export type ErrorHandler = (error: StdError) => E -export function defaultErrorHandler(error: unknown): StdError { +export function toStdError(error: unknown): StdError { if (error instanceof Panic) { throw error } diff --git a/src/try.ts b/src/try.ts index 6696602..a59cd7c 100644 --- a/src/try.ts +++ b/src/try.ts @@ -1,6 +1,6 @@ import {PromiseResult} from "./promise_result" import {Ok, type Result, Err} from "./result" -import {ErrorHandler, ResultError, StdError, defaultErrorHandler} from "./result_error" +import {ErrorHandler, ResultError, StdError, toStdError} from "./result_error" // Couldn't figure out how to overload these functions without a TypeScript error and making // the error handler required if the error template param is defined. @@ -9,7 +9,7 @@ export function tryFn(f: () => T): Result { try { return Ok(f()) } catch (error) { - return Err(defaultErrorHandler(error)) + return Err(toStdError(error)) } } @@ -20,7 +20,7 @@ export function tryFnWith( try { return Ok(f()) } catch (error) { - return Err(handleError(error)) + return Err(handleError(toStdError(error))) } } @@ -28,7 +28,7 @@ export function tryPromise(promise: Promise): PromiseResult { return new PromiseResult( promise.then( (value) => Ok(value), - (error) => Err(defaultErrorHandler(error)), + (error: unknown) => Err(toStdError(error)), ), ) } @@ -37,10 +37,10 @@ export function tryPromiseWith( promise: Promise, handleError: ErrorHandler, ): PromiseResult { - return new PromiseResult( + return new PromiseResult( promise.then( (value) => Ok(value), - (error) => Err(handleError(error)), + (error: unknown) => Err(handleError(toStdError(error))), ), ) } From 9fb1a0ba43b943080155b3ba085c3f2e8d8a3e7a Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 08:23:06 +0100 Subject: [PATCH 05/30] Fix none type --- src/option.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/option.ts b/src/option.ts index 1cebfc5..824493f 100644 --- a/src/option.ts +++ b/src/option.ts @@ -228,6 +228,6 @@ export class NoneImpl implements OptionMethods { } export interface None extends NoneImpl {} -export const None = new NoneImpl() +export const None: None = new NoneImpl() export type Option = Some | None From e97691910e9cf619cbb3a93c1d36088d5ba35784 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 08:58:51 +0100 Subject: [PATCH 06/30] Add stack and name --- src/result_error.ts | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index 81db94f..3e494c1 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -1,13 +1,33 @@ import {InvalidErrorPanic, Panic} from "./panic" -// TODO: Capture original error if possible export abstract class ResultError extends Error { - abstract readonly tag: string + abstract override readonly name: string + origin?: Error + + constructor(messageOrError?: string | Error) { + if (messageOrError instanceof Error) { + super(messageOrError.message) + this.origin = messageOrError + } else { + super(messageOrError) + } + + // Set the prototype explicitly to support subclassing built-in classes in TypeScript. + Object.setPrototypeOf(this, ResultError.prototype) + } + + override get stack() { + // Try to update the stack trace to include the subclass error name. + // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, + // meaning different JavaScript engines might produce different stack traces. + // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack + return this.origin?.stack?.replace(/^Error/, this.name) + } } export class StdError extends ResultError { - static readonly tag = "std" - readonly tag = StdError.tag + static readonly tag = "StdError" + readonly name = StdError.tag } export type ErrorHandler = (error: StdError) => E From 599e44eaea174b3a4f2b5ad8f22215bf160f11d6 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 08:59:02 +0100 Subject: [PATCH 07/30] Add tsx pkg --- package.json | 1 + pnpm-lock.yaml | 44 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/package.json b/package.json index 18fda5b..45683be 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "prettier": "^3.0.2", "prettier-plugin-pkg": "^0.18.0", "tsup": "^7.2.0", + "tsx": "^3.14.0", "typescript": "^5.0.4", "vitest": "^0.31.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96d292a..d725939 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + devDependencies: husky: specifier: ^8.0.3 @@ -16,6 +20,9 @@ devDependencies: tsup: specifier: ^7.2.0 version: 7.2.0(typescript@5.0.4) + tsx: + specifier: ^3.14.0 + version: 3.14.0 typescript: specifier: ^5.0.4 version: 5.0.4 @@ -413,6 +420,10 @@ packages: fill-range: 7.0.1 dev: true + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + /bundle-require@4.0.1(esbuild@0.18.20): resolution: {integrity: sha512-9NQkRHlNdNpDBGmLpngF3EFDcwodhMUuLz9PaWYciVcQF9SE4LFjM2DB/xV1Li5JiuDMv7ZUWuC3rGbqR0MAXQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -680,6 +691,12 @@ packages: engines: {node: '>=10'} dev: true + /get-tsconfig@4.7.2: + resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -1142,6 +1159,10 @@ packages: engines: {node: '>=8'} dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /restore-cursor@4.0.0: resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -1219,6 +1240,18 @@ packages: engines: {node: '>=0.10.0'} dev: true + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + /source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -1375,6 +1408,17 @@ packages: - ts-node dev: true + /tsx@3.14.0: + resolution: {integrity: sha512-xHtFaKtHxM9LOklMmJdI3BEnQq/D5F73Of2E1GDrITi9sgoVkvIsrQUTY1G8FlmGtA+awCI4EBlTRRYxkL2sRg==} + hasBin: true + dependencies: + esbuild: 0.18.20 + get-tsconfig: 4.7.2 + source-map-support: 0.5.21 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /type-detect@4.0.8: resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} engines: {node: '>=4'} From 191123ce871d68e92b649c2f271e8aae0bb6c4b1 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 09:01:00 +0100 Subject: [PATCH 08/30] Fix index --- src/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/index.ts b/src/index.ts index 66671ad..63a85ac 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,10 @@ export * from "./fn" export * from "./guard" +export * from "./option" export * from "./panic" +export * from "./promise_option" export * from "./promise_result" +export * from "./result_error" export * from "./result" export * from "./try" export * from "./util" From 2db6bdeccc823b8cbb3d7cd9e1e48bf4b415fdb7 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 09:19:13 +0100 Subject: [PATCH 09/30] Update vitest --- package.json | 2 +- pnpm-lock.yaml | 340 +++++++++++++++++++++---------------------------- 2 files changed, 144 insertions(+), 198 deletions(-) diff --git a/package.json b/package.json index 45683be..e53b6ef 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "tsup": "^7.2.0", "tsx": "^3.14.0", "typescript": "^5.0.4", - "vitest": "^0.31.0" + "vitest": "^0.34.6" }, "lint-staged": { "*.{ts,yaml,json,md}": "prettier --write" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d725939..1717c5e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,8 +27,8 @@ devDependencies: specifier: ^5.0.4 version: 5.0.4 vitest: - specifier: ^0.31.0 - version: 0.31.0 + specifier: ^0.34.6 + version: 0.34.6 packages: @@ -230,6 +230,13 @@ packages: dev: true optional: true + /@jest/schemas@29.6.3: + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dependencies: + '@sinclair/typebox': 0.27.8 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -281,66 +288,71 @@ packages: fastq: 1.15.0 dev: true - /@types/chai-subset@1.3.3: - resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + /@sinclair/typebox@0.27.8: + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + dev: true + + /@types/chai-subset@1.3.4: + resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} dependencies: - '@types/chai': 4.3.5 + '@types/chai': 4.3.9 dev: true - /@types/chai@4.3.5: - resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} + /@types/chai@4.3.9: + resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} dev: true - /@types/node@18.16.3: - resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==} + /@types/node@20.8.10: + resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} + dependencies: + undici-types: 5.26.5 dev: true - /@vitest/expect@0.31.0: - resolution: {integrity: sha512-Jlm8ZTyp6vMY9iz9Ny9a0BHnCG4fqBa8neCF6Pk/c/6vkUk49Ls6UBlgGAU82QnzzoaUs9E/mUhq/eq9uMOv/g==} + /@vitest/expect@0.34.6: + resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} dependencies: - '@vitest/spy': 0.31.0 - '@vitest/utils': 0.31.0 - chai: 4.3.7 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + chai: 4.3.10 dev: true - /@vitest/runner@0.31.0: - resolution: {integrity: sha512-H1OE+Ly7JFeBwnpHTrKyCNm/oZgr+16N4qIlzzqSG/YRQDATBYmJb/KUn3GrZaiQQyL7GwpNHVZxSQd6juLCgw==} + /@vitest/runner@0.34.6: + resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} dependencies: - '@vitest/utils': 0.31.0 - concordance: 5.0.4 + '@vitest/utils': 0.34.6 p-limit: 4.0.0 - pathe: 1.1.0 + pathe: 1.1.1 dev: true - /@vitest/snapshot@0.31.0: - resolution: {integrity: sha512-5dTXhbHnyUMTMOujZPB0wjFjQ6q5x9c8TvAsSPUNKjp1tVU7i9pbqcKPqntyu2oXtmVxKbuHCqrOd+Ft60r4tg==} + /@vitest/snapshot@0.34.6: + resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} dependencies: - magic-string: 0.30.0 - pathe: 1.1.0 - pretty-format: 27.5.1 + magic-string: 0.30.5 + pathe: 1.1.1 + pretty-format: 29.7.0 dev: true - /@vitest/spy@0.31.0: - resolution: {integrity: sha512-IzCEQ85RN26GqjQNkYahgVLLkULOxOm5H/t364LG0JYb3Apg0PsYCHLBYGA006+SVRMWhQvHlBBCyuByAMFmkg==} + /@vitest/spy@0.34.6: + resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} dependencies: - tinyspy: 2.1.0 + tinyspy: 2.2.0 dev: true - /@vitest/utils@0.31.0: - resolution: {integrity: sha512-kahaRyLX7GS1urekRXN2752X4gIgOGVX4Wo8eDUGUkTWlGpXzf5ZS6N9RUUS+Re3XEE8nVGqNyxkSxF5HXlGhQ==} + /@vitest/utils@0.34.6: + resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} dependencies: - concordance: 5.0.4 - loupe: 2.3.6 - pretty-format: 27.5.1 + diff-sequences: 29.6.3 + loupe: 2.3.7 + pretty-format: 29.7.0 dev: true - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} + /acorn-walk@8.3.0: + resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} engines: {node: '>=0.4.0'} dev: true - /acorn@8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + /acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true dev: true @@ -352,11 +364,6 @@ packages: type-fest: 1.4.0 dev: true - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - /ansi-regex@6.0.1: resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} engines: {node: '>=12'} @@ -402,10 +409,6 @@ packages: engines: {node: '>=8'} dev: true - /blueimp-md5@2.19.0: - resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==} - dev: true - /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -439,15 +442,15 @@ packages: engines: {node: '>=8'} dev: true - /chai@4.3.7: - resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} + /chai@4.3.10: + resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} engines: {node: '>=4'} dependencies: assertion-error: 1.1.0 - check-error: 1.0.2 + check-error: 1.0.3 deep-eql: 4.1.3 - get-func-name: 2.0.0 - loupe: 2.3.6 + get-func-name: 2.0.2 + loupe: 2.3.7 pathval: 1.1.1 type-detect: 4.0.8 dev: true @@ -457,8 +460,10 @@ packages: engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} dev: true - /check-error@1.0.2: - resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} + /check-error@1.0.3: + resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + dependencies: + get-func-name: 2.0.2 dev: true /chokidar@3.5.3: @@ -509,20 +514,6 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /concordance@5.0.4: - resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==} - engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'} - dependencies: - date-time: 3.1.0 - esutils: 2.0.3 - fast-diff: 1.2.0 - js-string-escape: 1.0.1 - lodash: 4.17.21 - md5-hex: 3.0.1 - semver: 7.5.0 - well-known-symbols: 2.0.0 - dev: true - /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -532,13 +523,6 @@ packages: which: 2.0.2 dev: true - /date-time@3.1.0: - resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==} - engines: {node: '>=6'} - dependencies: - time-zone: 1.0.0 - dev: true - /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -558,6 +542,11 @@ packages: type-detect: 4.0.8 dev: true + /diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -603,11 +592,6 @@ packages: '@esbuild/win32-x64': 0.18.20 dev: true - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - /eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} dev: true @@ -642,10 +626,6 @@ packages: strip-final-newline: 3.0.0 dev: true - /fast-diff@1.2.0: - resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} - dev: true - /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -682,8 +662,8 @@ packages: dev: true optional: true - /get-func-name@2.0.0: - resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} + /get-func-name@2.0.2: + resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} dev: true /get-stream@6.0.1: @@ -807,11 +787,6 @@ packages: engines: {node: '>=10'} dev: true - /js-string-escape@1.0.1: - resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==} - engines: {node: '>= 0.8'} - dev: true - /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true @@ -876,10 +851,6 @@ packages: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - dev: true - /log-update@5.0.1: resolution: {integrity: sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -891,33 +862,19 @@ packages: wrap-ansi: 8.1.0 dev: true - /loupe@2.3.6: - resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + /loupe@2.3.7: + resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} dependencies: - get-func-name: 2.0.0 + get-func-name: 2.0.2 dev: true - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true - - /magic-string@0.30.0: - resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} + /magic-string@0.30.5: + resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 dev: true - /md5-hex@3.0.1: - resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==} - engines: {node: '>=8'} - dependencies: - blueimp-md5: 2.19.0 - dev: true - /merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} dev: true @@ -951,13 +908,13 @@ packages: brace-expansion: 1.1.11 dev: true - /mlly@1.2.0: - resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==} + /mlly@1.4.2: + resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} dependencies: - acorn: 8.8.2 - pathe: 1.1.0 + acorn: 8.11.2 + pathe: 1.1.1 pkg-types: 1.0.3 - ufo: 1.1.1 + ufo: 1.3.1 dev: true /ms@2.1.2: @@ -1049,8 +1006,8 @@ packages: engines: {node: '>=8'} dev: true - /pathe@1.1.0: - resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} dev: true /pathval@1.1.1: @@ -1081,8 +1038,8 @@ packages: resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} dependencies: jsonc-parser: 3.2.0 - mlly: 1.2.0 - pathe: 1.1.0 + mlly: 1.4.2 + pathe: 1.1.1 dev: true /postcss-load-config@4.0.1: @@ -1101,8 +1058,8 @@ packages: yaml: 2.3.1 dev: true - /postcss@8.4.28: - resolution: {integrity: sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==} + /postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -1125,13 +1082,13 @@ packages: hasBin: true dev: true - /pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + /pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: - ansi-regex: 5.0.1 + '@jest/schemas': 29.6.3 ansi-styles: 5.2.0 - react-is: 17.0.2 + react-is: 18.2.0 dev: true /punycode@2.3.0: @@ -1143,8 +1100,8 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true /readdirp@3.6.0: @@ -1188,20 +1145,20 @@ packages: fsevents: 2.3.3 dev: true + /rollup@3.29.4: + resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.3 + dev: true + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 dev: true - /semver@7.5.0: - resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1263,8 +1220,8 @@ packages: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} dev: true - /std-env@3.3.2: - resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} + /std-env@3.4.3: + resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} dev: true /string-argv@0.3.2: @@ -1298,10 +1255,10 @@ packages: engines: {node: '>=12'} dev: true - /strip-literal@1.0.1: - resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} dependencies: - acorn: 8.8.2 + acorn: 8.11.2 dev: true /sucrase@3.34.0: @@ -1331,22 +1288,17 @@ packages: any-promise: 1.3.0 dev: true - /time-zone@1.0.0: - resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==} - engines: {node: '>=4'} - dev: true - - /tinybench@2.5.0: - resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} + /tinybench@2.5.1: + resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} dev: true - /tinypool@0.5.0: - resolution: {integrity: sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ==} + /tinypool@0.7.0: + resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} engines: {node: '>=14.0.0'} dev: true - /tinyspy@2.1.0: - resolution: {integrity: sha512-7eORpyqImoOvkQJCSkL0d0mB4NHHIFAy4b1u8PHdDa7SjGS2njzl6/lyGoZLm+eyYEtlUmFGE0rFj66SWxZgQQ==} + /tinyspy@2.2.0: + resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} engines: {node: '>=14.0.0'} dev: true @@ -1435,21 +1387,25 @@ packages: hasBin: true dev: true - /ufo@1.1.1: - resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} + /ufo@1.3.1: + resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} dev: true - /vite-node@0.31.0(@types/node@18.16.3): - resolution: {integrity: sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g==} + /undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + dev: true + + /vite-node@0.34.6(@types/node@20.8.10): + resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} engines: {node: '>=v14.18.0'} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 - mlly: 1.2.0 - pathe: 1.1.0 + mlly: 1.4.2 + pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.9(@types/node@18.16.3) + vite: 4.5.0(@types/node@20.8.10) transitivePeerDependencies: - '@types/node' - less @@ -1461,8 +1417,8 @@ packages: - terser dev: true - /vite@4.4.9(@types/node@18.16.3): - resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} + /vite@4.5.0(@types/node@20.8.10): + resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -1489,16 +1445,16 @@ packages: terser: optional: true dependencies: - '@types/node': 18.16.3 + '@types/node': 20.8.10 esbuild: 0.18.20 - postcss: 8.4.28 - rollup: 3.28.1 + postcss: 8.4.31 + rollup: 3.29.4 optionalDependencies: fsevents: 2.3.3 dev: true - /vitest@0.31.0: - resolution: {integrity: sha512-JwWJS9p3GU9GxkG7eBSmr4Q4x4bvVBSswaCFf1PBNHiPx00obfhHRJfgHcnI0ffn+NMlIh9QGvG75FlaIBdKGA==} + /vitest@0.34.6: + resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} engines: {node: '>=v14.18.0'} hasBin: true peerDependencies: @@ -1528,30 +1484,29 @@ packages: webdriverio: optional: true dependencies: - '@types/chai': 4.3.5 - '@types/chai-subset': 1.3.3 - '@types/node': 18.16.3 - '@vitest/expect': 0.31.0 - '@vitest/runner': 0.31.0 - '@vitest/snapshot': 0.31.0 - '@vitest/spy': 0.31.0 - '@vitest/utils': 0.31.0 - acorn: 8.8.2 - acorn-walk: 8.2.0 + '@types/chai': 4.3.9 + '@types/chai-subset': 1.3.4 + '@types/node': 20.8.10 + '@vitest/expect': 0.34.6 + '@vitest/runner': 0.34.6 + '@vitest/snapshot': 0.34.6 + '@vitest/spy': 0.34.6 + '@vitest/utils': 0.34.6 + acorn: 8.11.2 + acorn-walk: 8.3.0 cac: 6.7.14 - chai: 4.3.7 - concordance: 5.0.4 + chai: 4.3.10 debug: 4.3.4 local-pkg: 0.4.3 - magic-string: 0.30.0 - pathe: 1.1.0 + magic-string: 0.30.5 + pathe: 1.1.1 picocolors: 1.0.0 - std-env: 3.3.2 - strip-literal: 1.0.1 - tinybench: 2.5.0 - tinypool: 0.5.0 - vite: 4.4.9(@types/node@18.16.3) - vite-node: 0.31.0(@types/node@18.16.3) + std-env: 3.4.3 + strip-literal: 1.3.0 + tinybench: 2.5.1 + tinypool: 0.7.0 + vite: 4.5.0(@types/node@20.8.10) + vite-node: 0.34.6(@types/node@20.8.10) why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -1567,11 +1522,6 @@ packages: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} dev: true - /well-known-symbols@2.0.0: - resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==} - engines: {node: '>=6'} - dev: true - /whatwg-url@7.1.0: resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} dependencies: @@ -1610,10 +1560,6 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true - /yaml@2.3.1: resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==} engines: {node: '>= 14'} From 8680436bbb41fd75a9076535936c0911eae6e494 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 09:19:33 +0100 Subject: [PATCH 10/30] Add result error tests --- src/result_error.ts | 2 +- test/result_error.test.ts | 59 +++++++++++++++++++++++++++++++++++++++ test/try.test.ts | 27 +----------------- 3 files changed, 61 insertions(+), 27 deletions(-) create mode 100644 test/result_error.test.ts diff --git a/src/result_error.ts b/src/result_error.ts index 3e494c1..acde1eb 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -37,7 +37,7 @@ export function toStdError(error: unknown): StdError { throw error } if (error instanceof Error) { - return new StdError() + return new StdError(error) } throw new InvalidErrorPanic(error) } diff --git a/test/result_error.test.ts b/test/result_error.test.ts new file mode 100644 index 0000000..a5be17d --- /dev/null +++ b/test/result_error.test.ts @@ -0,0 +1,59 @@ +import {describe, expect, it} from "vitest" +import {InvalidErrorPanic, Panic, ResultError, toStdError, StdError} from "../src" + +describe.concurrent("ResultError", () => { + class CustomError extends ResultError { + static readonly tag = "CustomError" + readonly name = CustomError.tag + } + + it("returns name", () => { + const error = new CustomError() + expect(error.name).toEqual(CustomError.tag) + }) + + it("returns message", () => { + const error = new CustomError("Test error") + expect(error.message).toEqual("Test error") + }) + + it("returns stack", () => { + const error = new CustomError("Test error") + expect(error.stack).toContain("CustomError: Test error") + }) + + it("returns the origin", () => { + const origin = new Error("Origin error") + const error = new CustomError(origin) + expect(error.stack).toContain("CustomError: Origin error") + expect(error.stack).toContain("Origin error") + expect(error.origin).toEqual(origin) + }) +}) + +describe.concurrent("toStdError", () => { + it("returns an StdError when given an Error", () => { + class TestError extends Error {} + const error = new TestError("Test error") + const stdError = toStdError(error) + expect(stdError.name).toEqual("StdError") + expect(stdError).toBeInstanceOf(StdError) + expect(stdError.origin).toEqual(error) + }) + + it("throws a Panic when given a Panic", () => { + const msg = "Test panic" + const panic = new Panic(msg) + expect(() => toStdError(panic)).toThrow(panic) + }) + + it("throws a Panic when given an unknown value", () => { + expect(() => toStdError(0)).toThrow(InvalidErrorPanic) + expect(() => toStdError("")).toThrow(InvalidErrorPanic) + expect(() => toStdError(true)).toThrow(InvalidErrorPanic) + expect(() => toStdError(undefined)).toThrow(InvalidErrorPanic) + expect(() => toStdError(null)).toThrow(InvalidErrorPanic) + expect(() => toStdError({})).toThrow(InvalidErrorPanic) + expect(() => toStdError([])).toThrow(InvalidErrorPanic) + }) +}) diff --git a/test/try.test.ts b/test/try.test.ts index 1943e98..e749296 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -1,30 +1,5 @@ import {describe, expect, it} from "vitest" -import {InvalidErrorPanic, Panic, defaultErrorHandler, tryAsyncFn, tryFn, tryPromise} from "../src" - -describe.concurrent("handleError", () => { - it("returns an Error when given an Error", () => { - class TestError extends Error {} - const error = new TestError("Test error") - const err = defaultErrorHandler(error) - expect(err).to.be.instanceof(TestError) - }) - - it("throws a Panic when given a Panic", () => { - const msg = "Test panic" - const panic = new Panic(msg) - expect(() => defaultErrorHandler(panic)).to.throw(Panic, msg) - }) - - it("throws a Panic when given an unknown value", () => { - expect(() => defaultErrorHandler(0)).to.throw(InvalidErrorPanic) - expect(() => defaultErrorHandler("")).to.throw(InvalidErrorPanic) - expect(() => defaultErrorHandler(true)).to.throw(InvalidErrorPanic) - expect(() => defaultErrorHandler(undefined)).to.throw(InvalidErrorPanic) - expect(() => defaultErrorHandler(null)).to.throw(InvalidErrorPanic) - expect(() => defaultErrorHandler({})).to.throw(InvalidErrorPanic) - expect(() => defaultErrorHandler([])).to.throw(InvalidErrorPanic) - }) -}) +import {tryAsyncFn, tryFn, tryPromise} from "../src" describe.concurrent("tryPromise", () => { it("settles a Promise to an Ok result", async () => { From 7e50b81c3d305aa8b5459c4bafe7daaf9504c03e Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 09:35:38 +0100 Subject: [PATCH 11/30] Remove test cache --- vitest.config.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/vitest.config.ts b/vitest.config.ts index 4b550ba..f09ed3f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -3,5 +3,6 @@ import type {UserConfig} from "vitest/config" export default { test: { include: ["**/*.test.ts"], + cache: false, }, } satisfies UserConfig From ee11357c042923b4292cb407b0fdebe2207e55af Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 09:57:21 +0100 Subject: [PATCH 12/30] Remove error extends Apparently it's not easy to built-ins across every environment. See: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-doesnt-extending-built-ins-like-error-array-and-map-work --- src/result_error.ts | 25 +++++++++++++------------ test/result_error.test.ts | 33 +++++++++++++++------------------ 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index acde1eb..9c00732 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -1,33 +1,34 @@ import {InvalidErrorPanic, Panic} from "./panic" -export abstract class ResultError extends Error { - abstract override readonly name: string - origin?: Error +export abstract class ResultError implements Error { + abstract readonly tag: string - constructor(messageOrError?: string | Error) { + readonly name = "ResultError" as const + readonly message: string + readonly origin?: Error + + constructor(messageOrError: string | Error = "") { if (messageOrError instanceof Error) { - super(messageOrError.message) + this.message = messageOrError.message this.origin = messageOrError } else { - super(messageOrError) + this.message = messageOrError } - - // Set the prototype explicitly to support subclassing built-in classes in TypeScript. - Object.setPrototypeOf(this, ResultError.prototype) } - override get stack() { + // TODO: How to get own stack? + get stack() { // Try to update the stack trace to include the subclass error name. // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, // meaning different JavaScript engines might produce different stack traces. // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack - return this.origin?.stack?.replace(/^Error/, this.name) + return this.origin?.stack?.replace(/^Error/, this.tag) } } export class StdError extends ResultError { static readonly tag = "StdError" - readonly name = StdError.tag + readonly tag = StdError.tag } export type ErrorHandler = (error: StdError) => E diff --git a/test/result_error.test.ts b/test/result_error.test.ts index a5be17d..e8c272f 100644 --- a/test/result_error.test.ts +++ b/test/result_error.test.ts @@ -2,33 +2,31 @@ import {describe, expect, it} from "vitest" import {InvalidErrorPanic, Panic, ResultError, toStdError, StdError} from "../src" describe.concurrent("ResultError", () => { - class CustomError extends ResultError { - static readonly tag = "CustomError" - readonly name = CustomError.tag - } - - it("returns name", () => { - const error = new CustomError() - expect(error.name).toEqual(CustomError.tag) + it("returns instance", () => { + const error = new StdError() + expect(error).toBeInstanceOf(ResultError) + expect(error).toBeInstanceOf(StdError) + expect(error.name).toEqual("ResultError") + expect(error.tag).toEqual(StdError.tag) }) it("returns message", () => { - const error = new CustomError("Test error") + const error = new StdError("Test error") expect(error.message).toEqual("Test error") }) - it("returns stack", () => { - const error = new CustomError("Test error") - expect(error.stack).toContain("CustomError: Test error") - }) - it("returns the origin", () => { const origin = new Error("Origin error") - const error = new CustomError(origin) - expect(error.stack).toContain("CustomError: Origin error") - expect(error.stack).toContain("Origin error") + const error = new StdError(origin) + expect(error.stack).toContain("StdError: Origin error") expect(error.origin).toEqual(origin) }) + + it("returns stack", () => { + const error = new StdError("Test error") + expect(error.stack).toBeDefined() + expect(error.stack).toContain("StdError: Test error") + }) }) describe.concurrent("toStdError", () => { @@ -36,7 +34,6 @@ describe.concurrent("toStdError", () => { class TestError extends Error {} const error = new TestError("Test error") const stdError = toStdError(error) - expect(stdError.name).toEqual("StdError") expect(stdError).toBeInstanceOf(StdError) expect(stdError.origin).toEqual(error) }) From 5e66c4cd4e240a5582b078358e80ed5514c46199 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 10:02:06 +0100 Subject: [PATCH 13/30] Add stack --- src/result_error.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index 9c00732..da29cdc 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -6,6 +6,7 @@ export abstract class ResultError implements Error { readonly name = "ResultError" as const readonly message: string readonly origin?: Error + readonly ownStack?: string // TODO: This seems weird to have two stacks constructor(messageOrError: string | Error = "") { if (messageOrError instanceof Error) { @@ -14,15 +15,15 @@ export abstract class ResultError implements Error { } else { this.message = messageOrError } + this.ownStack = new Error(this.message).stack } - // TODO: How to get own stack? get stack() { // Try to update the stack trace to include the subclass error name. // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, // meaning different JavaScript engines might produce different stack traces. // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack - return this.origin?.stack?.replace(/^Error/, this.tag) + return (this.origin?.stack ?? this.ownStack)?.replace(/^Error/, this.tag) } } From 427a308c9213f6448c7bd870d96c951a14b4df44 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 12:53:23 +0100 Subject: [PATCH 14/30] Update ResultError stack --- src/result_error.ts | 27 +++++++++++++++++------ test/result_error.test.ts | 46 +++++++++++++++++++++++++++------------ 2 files changed, 52 insertions(+), 21 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index da29cdc..450999a 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -6,24 +6,37 @@ export abstract class ResultError implements Error { readonly name = "ResultError" as const readonly message: string readonly origin?: Error - readonly ownStack?: string // TODO: This seems weird to have two stacks + private readonly _stack?: string constructor(messageOrError: string | Error = "") { if (messageOrError instanceof Error) { this.message = messageOrError.message this.origin = messageOrError + if (this.origin.stack) { + this._stack = this.origin.stack + } } else { this.message = messageOrError } - this.ownStack = new Error(this.message).stack + if (!this._stack) { + this._stack = new Error(this.message).stack + } } get stack() { - // Try to update the stack trace to include the subclass error name. - // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, - // meaning different JavaScript engines might produce different stack traces. - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack - return (this.origin?.stack ?? this.ownStack)?.replace(/^Error/, this.tag) + return ResultError.updateStack(this.tag, this._stack) + } + + /** + * Tries to update the stack trace to include the subclass error name. + * + * May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, + * meaning different JavaScript engines might produce different stack traces. + * + * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack + */ + private static updateStack(tag: string, stack?: string) { + return stack?.replace(/^Error/, tag) } } diff --git a/test/result_error.test.ts b/test/result_error.test.ts index e8c272f..22d6207 100644 --- a/test/result_error.test.ts +++ b/test/result_error.test.ts @@ -1,31 +1,49 @@ import {describe, expect, it} from "vitest" import {InvalidErrorPanic, Panic, ResultError, toStdError, StdError} from "../src" -describe.concurrent("ResultError", () => { - it("returns instance", () => { +describe.concurrent("ResultError and StdError", () => { + it("returns instance with no args", () => { const error = new StdError() + expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) + expect(error.name).toEqual("ResultError") expect(error.tag).toEqual(StdError.tag) + + expect(error.message).toEqual("") + expect(error.stack).toContain("StdError: ") }) - it("returns message", () => { - const error = new StdError("Test error") - expect(error.message).toEqual("Test error") + it("returns instance with message", () => { + const msg = "msg" + const error = new StdError(msg) + + expect(error).toBeInstanceOf(ResultError) + expect(error).toBeInstanceOf(StdError) + + expect(error.name).toEqual("ResultError") + expect(error.tag).toEqual(StdError.tag) + + expect(error.message).toEqual(msg) + expect(error.stack).toContain(`StdError: ${msg}`) }) - it("returns the origin", () => { - const origin = new Error("Origin error") + it("returns instance with error", () => { + const origin = new Error("msg") const error = new StdError(origin) - expect(error.stack).toContain("StdError: Origin error") - expect(error.origin).toEqual(origin) - }) - it("returns stack", () => { - const error = new StdError("Test error") - expect(error.stack).toBeDefined() - expect(error.stack).toContain("StdError: Test error") + expect(error).toBeInstanceOf(ResultError) + expect(error).toBeInstanceOf(StdError) + + expect(error.name).toEqual("ResultError") + expect(error.tag).toEqual(StdError.tag) + + expect(error.origin).toEqual(origin) + expect(error.message).toEqual(origin.message) + // @ts-expect-error + expect(error._stack).toEqual(origin.stack) + expect(error.stack).toContain(`StdError: ${origin.message}`) }) }) From 228384865b9bdcb675943e0e8edf18ea2f964d56 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 13:11:35 +0100 Subject: [PATCH 15/30] Add origin name to stack trace --- src/result_error.ts | 15 +++++++-------- test/result_error.test.ts | 18 ++++++++++-------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index 450999a..f63d323 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -1,11 +1,10 @@ import {InvalidErrorPanic, Panic} from "./panic" export abstract class ResultError implements Error { - abstract readonly tag: string + abstract readonly name: string - readonly name = "ResultError" as const readonly message: string - readonly origin?: Error + readonly origin?: Readonly private readonly _stack?: string constructor(messageOrError: string | Error = "") { @@ -24,7 +23,7 @@ export abstract class ResultError implements Error { } get stack() { - return ResultError.updateStack(this.tag, this._stack) + return ResultError.updateStack(this.name, this.origin?.name, this._stack) } /** @@ -35,14 +34,14 @@ export abstract class ResultError implements Error { * * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack */ - private static updateStack(tag: string, stack?: string) { - return stack?.replace(/^Error/, tag) + private static updateStack(name: string, originName = "Error", stack?: string) { + const r = new RegExp(`^${originName}`) + return stack?.replace(r, originName !== "Error" ? `${name} from ${originName}` : name) } } export class StdError extends ResultError { - static readonly tag = "StdError" - readonly tag = StdError.tag + readonly name = "StdError" } export type ErrorHandler = (error: StdError) => E diff --git a/test/result_error.test.ts b/test/result_error.test.ts index 22d6207..854918e 100644 --- a/test/result_error.test.ts +++ b/test/result_error.test.ts @@ -8,8 +8,7 @@ describe.concurrent("ResultError and StdError", () => { expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) - expect(error.name).toEqual("ResultError") - expect(error.tag).toEqual(StdError.tag) + expect(error.name).toEqual("StdError") expect(error.message).toEqual("") expect(error.stack).toContain("StdError: ") @@ -22,28 +21,31 @@ describe.concurrent("ResultError and StdError", () => { expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) - expect(error.name).toEqual("ResultError") - expect(error.tag).toEqual(StdError.tag) + expect(error.name).toEqual("StdError") expect(error.message).toEqual(msg) expect(error.stack).toContain(`StdError: ${msg}`) }) it("returns instance with error", () => { - const origin = new Error("msg") - const error = new StdError(origin) + let origin = new Error("msg") + let error = new StdError(origin) expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) - expect(error.name).toEqual("ResultError") - expect(error.tag).toEqual(StdError.tag) + expect(error.name).toEqual("StdError") expect(error.origin).toEqual(origin) expect(error.message).toEqual(origin.message) // @ts-expect-error expect(error._stack).toEqual(origin.stack) expect(error.stack).toContain(`StdError: ${origin.message}`) + + origin = new Error("msg") + origin.name = "MyError" + error = new StdError(origin) + expect(error.stack).toContain(`StdError from MyError: ${origin.message}`) }) }) From 496a2b86e990a5cd89cd4bf36a0df0221508128c Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 13:24:18 +0100 Subject: [PATCH 16/30] Remove none value --- src/option.ts | 1 - test/option.test.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/option.ts b/src/option.ts index 824493f..d885d4e 100644 --- a/src/option.ts +++ b/src/option.ts @@ -136,7 +136,6 @@ export const Some = SomeImpl.from export class NoneImpl implements OptionMethods { readonly some = false readonly none = true - readonly value = null and(_other: Option) { return None diff --git a/test/option.test.ts b/test/option.test.ts index baef9ca..5518986 100644 --- a/test/option.test.ts +++ b/test/option.test.ts @@ -13,7 +13,6 @@ it("returns a None option", () => { const option = None expect(option.some).toEqual(false) expect(option.none).toEqual(true) - expect(option.value).toEqual(null) }) describe.concurrent("and", () => { From 142427a2086eef010a661df8ee48b7443b75a3bc Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 13:52:50 +0100 Subject: [PATCH 17/30] Refactor stack --- src/result_error.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index f63d323..ac2a67f 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -22,21 +22,21 @@ export abstract class ResultError implements Error { } } + // Tries to replace the stack trace to include the subclass error name. + // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, + // meaning different JavaScript engines might produce different stack traces. + // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack get stack() { - return ResultError.updateStack(this.name, this.origin?.name, this._stack) + const r = new RegExp(`^${this.originName}`) + return this._stack?.replace(r, this.expandedName) } - /** - * Tries to update the stack trace to include the subclass error name. - * - * May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, - * meaning different JavaScript engines might produce different stack traces. - * - * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack - */ - private static updateStack(name: string, originName = "Error", stack?: string) { - const r = new RegExp(`^${originName}`) - return stack?.replace(r, originName !== "Error" ? `${name} from ${originName}` : name) + get expandedName() { + return this.originName !== "Error" ? `${this.name} from ${this.originName}` : this.name + } + + get originName() { + return this.origin?.name ?? "Error" } } From b2358a3aa61a8ff5212e038ee2ceb6affba34913 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 14:08:28 +0100 Subject: [PATCH 18/30] Add inspectors --- src/option.ts | 10 ++++++++++ src/result.ts | 10 ++++++++++ src/result_error.ts | 9 +++++++++ src/util.ts | 2 ++ test/result_error.test.ts | 2 -- 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/option.ts b/src/option.ts index d885d4e..feb5ff9 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,4 +1,5 @@ import {Panic, UnwrapPanic} from "./panic" +import {inspectSymbol} from "./util" export interface OptionMethods { and(other: Option): Option @@ -23,6 +24,7 @@ export interface OptionMethods { match(some: (value: T) => A, none: () => B): A | B toString(): `Some(${string})` | "None" + [inspectSymbol](): ReturnType["toString"]> toObject(): {some: true; value: T} | {some: false; value: null} toJSON(): {meta: "Some"; data: T} | {meta: "None"} } @@ -117,6 +119,10 @@ export class SomeImpl implements OptionMethods { return `Some(${this.value})` as const } + [inspectSymbol]() { + return this.toString() + } + toObject() { return {some: true, value: this.value} as const } @@ -217,6 +223,10 @@ export class NoneImpl implements OptionMethods { return "None" as const } + [inspectSymbol]() { + return this.toString() + } + toObject() { return {some: false, value: null} as const } diff --git a/src/result.ts b/src/result.ts index 9e7dea9..5629c7b 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,3 +1,4 @@ +import {inspectSymbol} from "./util" import {Panic, UnwrapPanic} from "./panic" export interface ResultMethods { @@ -26,6 +27,7 @@ export interface ResultMethods { match(ok: (value: T) => A, err: (error: E) => B): A | B toString(): `Ok(${string})` | `Err(${string})` + [inspectSymbol](): ReturnType["toString"]> toObject(): {ok: true; value: T} | {ok: false; error: E} toJSON(): {meta: "Ok"; data: T} | {meta: "Err"; data: E} } @@ -136,6 +138,10 @@ export class OkImpl implements ResultMethods { return `Ok(${this.value})` as const } + [inspectSymbol]() { + return this.toString() + } + toObject() { return {ok: true, value: this.value} as const } @@ -260,6 +266,10 @@ export class ErrImpl implements ResultMethods { return `Err(${this.error})` as const } + [inspectSymbol]() { + return this.toString() + } + toObject() { return {ok: false, error: this.error} as const } diff --git a/src/result_error.ts b/src/result_error.ts index ac2a67f..832ed16 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -1,3 +1,4 @@ +import {inspectSymbol} from "./util" import {InvalidErrorPanic, Panic} from "./panic" export abstract class ResultError implements Error { @@ -38,6 +39,14 @@ export abstract class ResultError implements Error { get originName() { return this.origin?.name ?? "Error" } + + toString() { + return `${this.expandedName}: ${this.message}` + } + + [inspectSymbol]() { + return this.stack + } } export class StdError extends ResultError { diff --git a/src/util.ts b/src/util.ts index e6fabb1..c3e4386 100644 --- a/src/util.ts +++ b/src/util.ts @@ -3,6 +3,8 @@ import {Option} from "./option" import type {PromiseResult} from "./promise_result" import type {Result} from "./result" +export const inspectSymbol = Symbol.for("nodejs.util.inspect.custom") + export type ResultValueErrorType = T extends | Result | PromiseResult diff --git a/test/result_error.test.ts b/test/result_error.test.ts index 854918e..6ee5981 100644 --- a/test/result_error.test.ts +++ b/test/result_error.test.ts @@ -38,8 +38,6 @@ describe.concurrent("ResultError and StdError", () => { expect(error.origin).toEqual(origin) expect(error.message).toEqual(origin.message) - // @ts-expect-error - expect(error._stack).toEqual(origin.stack) expect(error.stack).toContain(`StdError: ${origin.message}`) origin = new Error("msg") From a6375adc79ff31c41ec149f615aa1bce9282a38d Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 17:30:56 +0100 Subject: [PATCH 19/30] Add guardWith --- src/guard.ts | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/guard.ts b/src/guard.ts index 3e29630..8c92675 100644 --- a/src/guard.ts +++ b/src/guard.ts @@ -1,13 +1,32 @@ -import {tryAsyncFn, tryFn} from "./try" +import {ErrorHandler, ResultError} from "./result_error" +import {tryAsyncFn, tryAsyncFnWith, tryFn, tryFnWith} from "./try" -export function guard any>(fn: T) { +type Fn = (...args: any[]) => any +type AsyncFn = (...args: any[]) => Promise + +export function guard(f: T) { + return function (...args: Parameters) { + return tryFn>(() => f(...args)) + } +} + +export function guardWith(f: T, handleError: ErrorHandler) { return function (...args: Parameters) { - return tryFn>(() => fn(...args)) + return tryFnWith, E>(() => f(...args), handleError) } } -export function guardAsync Promise>(fn: T) { +export function guardAsync(fn: T) { return function (...args: Parameters) { return tryAsyncFn>>(() => fn(...args)) } } + +export function guardAsyncWith( + fn: T, + handleError: ErrorHandler, +) { + return function (...args: Parameters) { + return tryAsyncFnWith>, E>(() => fn(...args), handleError) + } +} From 5b3826fb215e924ac67ddc382015b8ebaee47a04 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 17:39:51 +0100 Subject: [PATCH 20/30] Add tryWith tests --- test/try.test.ts | 57 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/test/try.test.ts b/test/try.test.ts index e749296..a67e513 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -1,5 +1,17 @@ import {describe, expect, it} from "vitest" -import {tryAsyncFn, tryFn, tryPromise} from "../src" +import { + ResultError, + tryAsyncFn, + tryAsyncFnWith, + tryFn, + tryFnWith, + tryPromise, + tryPromiseWith, +} from "../src" + +class MyError extends ResultError { + readonly name = "MyError" +} describe.concurrent("tryPromise", () => { it("settles a Promise to an Ok result", async () => { @@ -14,7 +26,18 @@ describe.concurrent("tryPromise", () => { const promise = Promise.reject(error) const result = await tryPromise(promise) expect(result.ok).toEqual(false) - expect(result.unwrapErr()).toEqual(error) + expect(result.unwrapErr().origin).toEqual(error) + }) +}) + +describe.concurrent("tryPromiseWith", () => { + it("settles a rejected Promise to an Err result", async () => { + const error = new Error("Test error") + const promise = Promise.reject(error) + const myError = new MyError() + const result = await tryPromiseWith(promise, () => myError) + expect(result.ok).toEqual(false) + expect(result.unwrapErr()).toEqual(myError) }) }) @@ -33,7 +56,20 @@ describe.concurrent("tryFn", () => { } const result = tryFn(fn) expect(result.ok).toEqual(false) - expect(result.unwrapErr()).toEqual(error) + expect(result.unwrapErr().origin).toEqual(error) + }) +}) + +describe.concurrent("tryFnWith", () => { + it("wraps a throwing function call into an Err result", () => { + const error = new Error("Test error") + const fn = () => { + throw error + } + const myError = new MyError() + const result = tryFnWith(fn, () => myError) + expect(result.ok).toEqual(false) + expect(result.unwrapErr()).toEqual(myError) }) }) @@ -52,6 +88,19 @@ describe.concurrent("tryAsyncFn", () => { } const result = await tryAsyncFn(fn) expect(result.ok).toEqual(false) - expect(result.unwrapErr()).toEqual(error) + expect(result.unwrapErr().origin).toEqual(error) + }) +}) + +describe.concurrent("tryAsyncFnWith", () => { + it("wraps a throwing async function call into an Err result", async () => { + const error = new Error("Test error") + const fn = async (): Promise => { + throw error + } + const myError = new MyError() + const result = await tryAsyncFnWith(fn, () => myError) + expect(result.ok).toEqual(false) + expect(result.unwrapErr()).toEqual(myError) }) }) From 8055478815ec3a73a02bc4dd507c0d2b7fe9cfff Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 17:47:42 +0100 Subject: [PATCH 21/30] Refactor name with tag --- src/result_error.ts | 24 ++++++++++++------------ test/result_error.test.ts | 6 +++--- test/try.test.ts | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/result_error.ts b/src/result_error.ts index 832ed16..d973f49 100644 --- a/src/result_error.ts +++ b/src/result_error.ts @@ -2,7 +2,7 @@ import {inspectSymbol} from "./util" import {InvalidErrorPanic, Panic} from "./panic" export abstract class ResultError implements Error { - abstract readonly name: string + abstract readonly tag: string readonly message: string readonly origin?: Readonly @@ -23,25 +23,25 @@ export abstract class ResultError implements Error { } } + get name() { + return this.originName !== "Error" ? `${this.tag} from ${this.originName}` : this.tag + } + + private get originName() { + return this.origin?.name ?? "Error" + } + // Tries to replace the stack trace to include the subclass error name. // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, // meaning different JavaScript engines might produce different stack traces. // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack get stack() { const r = new RegExp(`^${this.originName}`) - return this._stack?.replace(r, this.expandedName) - } - - get expandedName() { - return this.originName !== "Error" ? `${this.name} from ${this.originName}` : this.name - } - - get originName() { - return this.origin?.name ?? "Error" + return this._stack?.replace(r, this.name) } toString() { - return `${this.expandedName}: ${this.message}` + return `${this.name}: ${this.message}` } [inspectSymbol]() { @@ -50,7 +50,7 @@ export abstract class ResultError implements Error { } export class StdError extends ResultError { - readonly name = "StdError" + readonly tag = "StdError" } export type ErrorHandler = (error: StdError) => E diff --git a/test/result_error.test.ts b/test/result_error.test.ts index 6ee5981..39e686d 100644 --- a/test/result_error.test.ts +++ b/test/result_error.test.ts @@ -8,7 +8,7 @@ describe.concurrent("ResultError and StdError", () => { expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) - expect(error.name).toEqual("StdError") + expect(error.tag).toEqual("StdError") expect(error.message).toEqual("") expect(error.stack).toContain("StdError: ") @@ -21,7 +21,7 @@ describe.concurrent("ResultError and StdError", () => { expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) - expect(error.name).toEqual("StdError") + expect(error.tag).toEqual("StdError") expect(error.message).toEqual(msg) expect(error.stack).toContain(`StdError: ${msg}`) @@ -34,7 +34,7 @@ describe.concurrent("ResultError and StdError", () => { expect(error).toBeInstanceOf(ResultError) expect(error).toBeInstanceOf(StdError) - expect(error.name).toEqual("StdError") + expect(error.tag).toEqual("StdError") expect(error.origin).toEqual(origin) expect(error.message).toEqual(origin.message) diff --git a/test/try.test.ts b/test/try.test.ts index a67e513..e9a27ad 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -10,7 +10,7 @@ import { } from "../src" class MyError extends ResultError { - readonly name = "MyError" + readonly tag = "MyError" } describe.concurrent("tryPromise", () => { From 345cc0978205cccd1852deed49bd89e60973c1de Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 17:52:24 +0100 Subject: [PATCH 22/30] Add guardWith tests --- test/guard.test.ts | 54 +++++++++++++++++++++++++++++++++++++++++++--- test/try.test.ts | 21 ++++++++++++++++++ 2 files changed, 72 insertions(+), 3 deletions(-) diff --git a/test/guard.test.ts b/test/guard.test.ts index 0410c5a..2741f48 100644 --- a/test/guard.test.ts +++ b/test/guard.test.ts @@ -1,5 +1,9 @@ import {expect, it, describe} from "vitest" -import {guard, guardAsync} from "../src" +import {ResultError, guard, guardAsync, guardAsyncWith, guardWith} from "../src" + +class MyError extends ResultError { + readonly tag = "MyError" +} describe.concurrent("guard", () => { it("transforms a function into a function that returns a Result object", () => { @@ -18,7 +22,29 @@ describe.concurrent("guard", () => { const wrappedFn = guard(fn) const result = wrappedFn() expect(result.ok).toEqual(false) - expect(result.unwrapErr()).toEqual(error) + expect(result.unwrapErr().origin).toEqual(error) + }) +}) + +describe.concurrent("guardWith", () => { + it("transforms a function into a function that returns a Result object", () => { + const fn = (x: number, y: number) => x + y + const wrappedFn = guardWith(fn, () => new MyError()) + const result = wrappedFn(40, 2) + expect(result.ok).toEqual(true) + expect(result.unwrap()).toEqual(42) + }) + + it("transforms a throwing function into a function that returns an Err result", () => { + const error = new Error("Test error") + const fn = () => { + throw error + } + const myError = new MyError("My error") + const wrappedFn = guardWith(fn, () => myError) + const result = wrappedFn() + expect(result.ok).toEqual(false) + expect(result.unwrapErr()).toEqual(myError) }) }) @@ -39,6 +65,28 @@ describe.concurrent("guardAsync", () => { const wrappedFn = guardAsync(fn) const result = await wrappedFn() expect(result.ok).toEqual(false) - expect(result.unwrapErr()).toEqual(error) + expect(result.unwrapErr().origin).toEqual(error) + }) +}) + +describe.concurrent("guardAsyncWith", () => { + it("transforms an async function into a function that returns a Promise of a Result object", async () => { + const fn = async (x: number, y: number) => Promise.resolve(x + y) + const wrappedFn = guardAsyncWith(fn, () => new MyError()) + const result = await wrappedFn(40, 2) + expect(result.ok).toEqual(true) + expect(result.unwrap()).toEqual(42) + }) + + it("transforms a throwing async function into a function that returns a Promise of an Err result", async () => { + const error = new Error("Test error") + const fn = async (): Promise => { + throw error + } + const myError = new MyError("My error") + const wrappedFn = guardAsyncWith(fn, () => myError) + const result = await wrappedFn() + expect(result.ok).toEqual(false) + expect(result.unwrapErr()).toEqual(myError) }) }) diff --git a/test/try.test.ts b/test/try.test.ts index e9a27ad..52e66aa 100644 --- a/test/try.test.ts +++ b/test/try.test.ts @@ -31,6 +31,13 @@ describe.concurrent("tryPromise", () => { }) describe.concurrent("tryPromiseWith", () => { + it("settles a Promise to an Ok result", async () => { + const promise = Promise.resolve(42) + const result = await tryPromiseWith(promise, () => new MyError()) + expect(result.ok).toEqual(true) + expect(result.unwrap()).toEqual(42) + }) + it("settles a rejected Promise to an Err result", async () => { const error = new Error("Test error") const promise = Promise.reject(error) @@ -61,6 +68,13 @@ describe.concurrent("tryFn", () => { }) describe.concurrent("tryFnWith", () => { + it("wraps a function call into a Result object", () => { + const fn = () => 42 + const result = tryFnWith(fn, () => new MyError()) + expect(result.ok).toEqual(true) + expect(result.unwrap()).toEqual(42) + }) + it("wraps a throwing function call into an Err result", () => { const error = new Error("Test error") const fn = () => { @@ -93,6 +107,13 @@ describe.concurrent("tryAsyncFn", () => { }) describe.concurrent("tryAsyncFnWith", () => { + it("wraps an async function call into a Result object", async () => { + const fn = async () => Promise.resolve(42) + const result = await tryAsyncFnWith(fn, () => new MyError()) + expect(result.ok).toEqual(true) + expect(result.unwrap()).toEqual(42) + }) + it("wraps a throwing async function call into an Err result", async () => { const error = new Error("Test error") const fn = async (): Promise => { From 23289dd9560690936e86ad2533fd15262bbb8f71 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 18:16:44 +0100 Subject: [PATCH 23/30] Refactor dirs --- src/error/index.ts | 2 + src/{ => error}/panic.ts | 0 {test => src/error}/result_error.test.ts | 2 +- src/{ => error}/result_error.ts | 2 +- {test => src/helpers}/fn.test.ts | 2 +- src/{ => helpers}/fn.ts | 5 +- {test => src/helpers}/guard.test.ts | 2 +- src/{ => helpers}/guard.ts | 2 +- src/helpers/index.ts | 3 + {test => src/helpers}/try.test.ts | 2 +- src/{ => helpers}/try.ts | 5 +- src/index.ts | 9 +- src/option.ts | 242 --------------- src/option/index.ts | 4 + src/option/interface.ts | 34 +++ src/option/none.ts | 104 +++++++ {test => src/option}/option.test.ts | 3 +- src/{promise_option.ts => option/promise.ts} | 4 +- {test => src/option}/promise_option.test.ts | 4 +- src/option/some.ts | 114 ++++++++ src/result.ts | 291 ------------------- src/result/err.ts | 132 +++++++++ src/result/index.ts | 4 + src/result/interface.ts | 37 +++ src/result/ok.ts | 132 +++++++++ src/{promise_result.ts => result/promise.ts} | 4 +- {test => src/result}/promise_result.test.ts | 2 +- {test => src/result}/result.test.ts | 2 +- src/util.ts | 6 +- 29 files changed, 588 insertions(+), 567 deletions(-) create mode 100644 src/error/index.ts rename src/{ => error}/panic.ts (100%) rename {test => src/error}/result_error.test.ts (99%) rename src/{ => error}/result_error.ts (97%) rename {test => src/helpers}/fn.test.ts (94%) rename src/{ => helpers}/fn.ts (74%) rename {test => src/helpers}/guard.test.ts (99%) rename src/{ => helpers}/guard.ts (94%) create mode 100644 src/helpers/index.ts rename {test => src/helpers}/try.test.ts (99%) rename src/{ => helpers}/try.ts (88%) delete mode 100644 src/option.ts create mode 100644 src/option/index.ts create mode 100644 src/option/interface.ts create mode 100644 src/option/none.ts rename {test => src/option}/option.test.ts (98%) rename src/{promise_option.ts => option/promise.ts} (97%) rename {test => src/option}/promise_option.test.ts (98%) create mode 100644 src/option/some.ts delete mode 100644 src/result.ts create mode 100644 src/result/err.ts create mode 100644 src/result/index.ts create mode 100644 src/result/interface.ts create mode 100644 src/result/ok.ts rename src/{promise_result.ts => result/promise.ts} (97%) rename {test => src/result}/promise_result.test.ts (99%) rename {test => src/result}/result.test.ts (99%) diff --git a/src/error/index.ts b/src/error/index.ts new file mode 100644 index 0000000..b18c305 --- /dev/null +++ b/src/error/index.ts @@ -0,0 +1,2 @@ +export * from "./panic" +export * from "./result_error" diff --git a/src/panic.ts b/src/error/panic.ts similarity index 100% rename from src/panic.ts rename to src/error/panic.ts diff --git a/test/result_error.test.ts b/src/error/result_error.test.ts similarity index 99% rename from test/result_error.test.ts rename to src/error/result_error.test.ts index 39e686d..43b090a 100644 --- a/test/result_error.test.ts +++ b/src/error/result_error.test.ts @@ -1,5 +1,5 @@ import {describe, expect, it} from "vitest" -import {InvalidErrorPanic, Panic, ResultError, toStdError, StdError} from "../src" +import {InvalidErrorPanic, Panic, ResultError, toStdError, StdError} from ".." describe.concurrent("ResultError and StdError", () => { it("returns instance with no args", () => { diff --git a/src/result_error.ts b/src/error/result_error.ts similarity index 97% rename from src/result_error.ts rename to src/error/result_error.ts index d973f49..bd95817 100644 --- a/src/result_error.ts +++ b/src/error/result_error.ts @@ -1,4 +1,4 @@ -import {inspectSymbol} from "./util" +import {inspectSymbol} from "../util" import {InvalidErrorPanic, Panic} from "./panic" export abstract class ResultError implements Error { diff --git a/test/fn.test.ts b/src/helpers/fn.test.ts similarity index 94% rename from test/fn.test.ts rename to src/helpers/fn.test.ts index 94c5616..d5c10e7 100644 --- a/test/fn.test.ts +++ b/src/helpers/fn.test.ts @@ -1,5 +1,5 @@ import {describe, expect, it} from "vitest" -import {asyncFn, fn, Ok, Err} from "../src" +import {asyncFn, fn, Ok, Err} from ".." describe.concurrent("fn", () => { it("returns Ok result when provided function does not throw", () => { diff --git a/src/fn.ts b/src/helpers/fn.ts similarity index 74% rename from src/fn.ts rename to src/helpers/fn.ts index 9d614ed..9212502 100644 --- a/src/fn.ts +++ b/src/helpers/fn.ts @@ -1,6 +1,5 @@ -import type {ResultValueType, ResultErrorType} from "./util" -import {type Result} from "./result" -import {PromiseResult} from "./promise_result" +import type {ResultValueType, ResultErrorType} from "../util" +import {type Result, PromiseResult} from "../result" export function fn Result>( f: T, diff --git a/test/guard.test.ts b/src/helpers/guard.test.ts similarity index 99% rename from test/guard.test.ts rename to src/helpers/guard.test.ts index 2741f48..bbcbb2a 100644 --- a/test/guard.test.ts +++ b/src/helpers/guard.test.ts @@ -1,5 +1,5 @@ import {expect, it, describe} from "vitest" -import {ResultError, guard, guardAsync, guardAsyncWith, guardWith} from "../src" +import {ResultError, guard, guardAsync, guardAsyncWith, guardWith} from ".." class MyError extends ResultError { readonly tag = "MyError" diff --git a/src/guard.ts b/src/helpers/guard.ts similarity index 94% rename from src/guard.ts rename to src/helpers/guard.ts index 8c92675..eadc653 100644 --- a/src/guard.ts +++ b/src/helpers/guard.ts @@ -1,4 +1,4 @@ -import {ErrorHandler, ResultError} from "./result_error" +import type {ErrorHandler, ResultError} from "../error" import {tryAsyncFn, tryAsyncFnWith, tryFn, tryFnWith} from "./try" type Fn = (...args: any[]) => any diff --git a/src/helpers/index.ts b/src/helpers/index.ts new file mode 100644 index 0000000..3038d75 --- /dev/null +++ b/src/helpers/index.ts @@ -0,0 +1,3 @@ +export * from "./fn" +export * from "./guard" +export * from "./try" diff --git a/test/try.test.ts b/src/helpers/try.test.ts similarity index 99% rename from test/try.test.ts rename to src/helpers/try.test.ts index 52e66aa..cf0bdb2 100644 --- a/test/try.test.ts +++ b/src/helpers/try.test.ts @@ -7,7 +7,7 @@ import { tryFnWith, tryPromise, tryPromiseWith, -} from "../src" +} from ".." class MyError extends ResultError { readonly tag = "MyError" diff --git a/src/try.ts b/src/helpers/try.ts similarity index 88% rename from src/try.ts rename to src/helpers/try.ts index a59cd7c..14b6515 100644 --- a/src/try.ts +++ b/src/helpers/try.ts @@ -1,6 +1,5 @@ -import {PromiseResult} from "./promise_result" -import {Ok, type Result, Err} from "./result" -import {ErrorHandler, ResultError, StdError, toStdError} from "./result_error" +import {Err, Ok, PromiseResult, type Result} from "../result" +import {type ErrorHandler, type ResultError, type StdError, toStdError} from "../error" // Couldn't figure out how to overload these functions without a TypeScript error and making // the error handler required if the error template param is defined. diff --git a/src/index.ts b/src/index.ts index 63a85ac..7f49d1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,5 @@ -export * from "./fn" -export * from "./guard" +export * from "./error" +export * from "./helpers" export * from "./option" -export * from "./panic" -export * from "./promise_option" -export * from "./promise_result" -export * from "./result_error" export * from "./result" -export * from "./try" export * from "./util" diff --git a/src/option.ts b/src/option.ts deleted file mode 100644 index feb5ff9..0000000 --- a/src/option.ts +++ /dev/null @@ -1,242 +0,0 @@ -import {Panic, UnwrapPanic} from "./panic" -import {inspectSymbol} from "./util" - -export interface OptionMethods { - and(other: Option): Option - andThen(f: (value: T) => Option): Option - expect(panic: string | Panic): T - filter(f: (value: T) => boolean): Option - inspect(f: (value: T) => void): Option - isNone(): this is None - isSome(): this is Some - isSomeAnd(f: (value: T) => boolean): this is Some - map(f: (value: T) => U): Option - mapOr(defaultValue: A, f: (value: T) => B): A | B - mapOrElse(defaultValue: () => A, f: (value: T) => B): A | B - or(other: Option): Option - orElse(f: () => Option): Option - unwrap(): T - unwrapOr(defaultValue: U): T | U - unwrapOrElse(defaultValue: () => U): T | U - xor(other: Option): Option - - into(): T | null - match(some: (value: T) => A, none: () => B): A | B - - toString(): `Some(${string})` | "None" - [inspectSymbol](): ReturnType["toString"]> - toObject(): {some: true; value: T} | {some: false; value: null} - toJSON(): {meta: "Some"; data: T} | {meta: "None"} -} - -export class SomeImpl implements OptionMethods { - readonly some = true - readonly none = false - readonly value: T - - constructor(value: T) { - this.value = value - } - - and(other: Option) { - return other - } - - andThen(f: (value: T) => Option) { - return f(this.value) - } - - expect(_panic: string | Panic) { - return this.value - } - - filter(f: (value: T) => boolean) { - return f(this.value) ? this : None - } - - inspect(f: (value: T) => void) { - f(this.value) - return this - } - - isNone(): this is None { - return false - } - - isSome(): this is Some { - return true - } - - isSomeAnd(f: (value: T) => boolean): this is Some { - return f(this.value) - } - - map(f: (value: T) => U) { - return Some(f(this.value)) - } - - mapOr(_defaultValue: A, f: (value: T) => B) { - return f(this.value) - } - - mapOrElse(_defaultValue: () => A, f: (value: T) => B) { - return f(this.value) - } - - or(_other: Option) { - return this - } - - orElse(_f: () => Option) { - return this - } - - unwrap() { - return this.value - } - - unwrapOr(_defaultValue: U) { - return this.value - } - - unwrapOrElse(_defaultValue: () => U) { - return this.value - } - - xor(other: Option) { - return other.isSome() ? None : this - } - - into() { - return this.value - } - - match(some: (value: T) => A, _none: () => B) { - return some(this.value) - } - - toString() { - return `Some(${this.value})` as const - } - - [inspectSymbol]() { - return this.toString() - } - - toObject() { - return {some: true, value: this.value} as const - } - - toJSON() { - return {meta: "Some", data: this.value} as const - } - - static from(value: T): Some { - return new SomeImpl(value) - } -} - -export interface Some extends SomeImpl {} -export const Some = SomeImpl.from - -export class NoneImpl implements OptionMethods { - readonly some = false - readonly none = true - - and(_other: Option) { - return None - } - - andThen(_f: (value: never) => Option) { - return None - } - - expect(panic: string | Panic): never { - throw typeof panic === "string" ? new Panic(panic) : panic - } - - filter(_f: (value: never) => boolean) { - return None - } - - inspect(_f: (value: never) => void) { - return this - } - - isNone(): this is None { - return true - } - - isSome(): this is Some { - return false - } - - isSomeAnd(_f: (value: never) => boolean): this is Some { - return false - } - - map(_f: (value: never) => U) { - return None - } - - mapOr(defaultValue: A, _f: (value: never) => B) { - return defaultValue - } - - mapOrElse(defaultValue: () => A, _f: (value: never) => B) { - return defaultValue() - } - - or(other: Option) { - return other - } - - orElse(f: () => Option) { - return f() - } - - unwrap(): never { - throw new UnwrapPanic("Cannot unwrap on a None") - } - - unwrapOr(defaultValue: U) { - return defaultValue - } - - unwrapOrElse(defaultValue: () => U) { - return defaultValue() - } - - xor(other: Option) { - return other - } - - into() { - return null - } - - match(_some: (value: never) => A, none: () => B) { - return none() - } - - toString() { - return "None" as const - } - - [inspectSymbol]() { - return this.toString() - } - - toObject() { - return {some: false, value: null} as const - } - - toJSON() { - return {meta: "None"} as const - } -} - -export interface None extends NoneImpl {} -export const None: None = new NoneImpl() - -export type Option = Some | None diff --git a/src/option/index.ts b/src/option/index.ts new file mode 100644 index 0000000..f882305 --- /dev/null +++ b/src/option/index.ts @@ -0,0 +1,4 @@ +export * from "./interface" +export * from "./none" +export * from "./some" +export * from "./promise" diff --git a/src/option/interface.ts b/src/option/interface.ts new file mode 100644 index 0000000..3a146c2 --- /dev/null +++ b/src/option/interface.ts @@ -0,0 +1,34 @@ +import type {Panic} from "../error" +import type {inspectSymbol} from "../util" +import type {None} from "./none" +import type {Some} from "./some" + +export interface OptionMethods { + and(other: Option): Option + andThen(f: (value: T) => Option): Option + expect(panic: string | Panic): T + filter(f: (value: T) => boolean): Option + inspect(f: (value: T) => void): Option + isNone(): this is None + isSome(): this is Some + isSomeAnd(f: (value: T) => boolean): this is Some + map(f: (value: T) => U): Option + mapOr(defaultValue: A, f: (value: T) => B): A | B + mapOrElse(defaultValue: () => A, f: (value: T) => B): A | B + or(other: Option): Option + orElse(f: () => Option): Option + unwrap(): T + unwrapOr(defaultValue: U): T | U + unwrapOrElse(defaultValue: () => U): T | U + xor(other: Option): Option + + into(): T | null + match(some: (value: T) => A, none: () => B): A | B + + toString(): `Some(${string})` | "None" + [inspectSymbol](): ReturnType["toString"]> + toObject(): {some: true; value: T} | {some: false; value: null} + toJSON(): {meta: "Some"; data: T} | {meta: "None"} +} + +export type Option = Some | None diff --git a/src/option/none.ts b/src/option/none.ts new file mode 100644 index 0000000..32cd65f --- /dev/null +++ b/src/option/none.ts @@ -0,0 +1,104 @@ +import {Panic, UnwrapPanic} from "../error" +import {inspectSymbol} from "../util" +import type {OptionMethods, Option} from "./interface" +import type {Some} from "./some" + +export class NoneImpl implements OptionMethods { + readonly some = false + readonly none = true + + and(_other: Option) { + return None + } + + andThen(_f: (value: never) => Option) { + return None + } + + expect(panic: string | Panic): never { + throw typeof panic === "string" ? new Panic(panic) : panic + } + + filter(_f: (value: never) => boolean) { + return None + } + + inspect(_f: (value: never) => void) { + return this + } + + isNone(): this is None { + return true + } + + isSome(): this is Some { + return false + } + + isSomeAnd(_f: (value: never) => boolean): this is Some { + return false + } + + map(_f: (value: never) => U) { + return None + } + + mapOr(defaultValue: A, _f: (value: never) => B) { + return defaultValue + } + + mapOrElse(defaultValue: () => A, _f: (value: never) => B) { + return defaultValue() + } + + or(other: Option) { + return other + } + + orElse(f: () => Option) { + return f() + } + + unwrap(): never { + throw new UnwrapPanic("Cannot unwrap on a None") + } + + unwrapOr(defaultValue: U) { + return defaultValue + } + + unwrapOrElse(defaultValue: () => U) { + return defaultValue() + } + + xor(other: Option) { + return other + } + + into() { + return null + } + + match(_some: (value: never) => A, none: () => B) { + return none() + } + + toString() { + return "None" as const + } + + [inspectSymbol]() { + return this.toString() + } + + toObject() { + return {some: false, value: null} as const + } + + toJSON() { + return {meta: "None"} as const + } +} + +export interface None extends NoneImpl {} +export const None: None = new NoneImpl() diff --git a/test/option.test.ts b/src/option/option.test.ts similarity index 98% rename from test/option.test.ts rename to src/option/option.test.ts index 5518986..ac4ef9a 100644 --- a/test/option.test.ts +++ b/src/option/option.test.ts @@ -1,6 +1,5 @@ import {describe, expect, it, vi} from "vitest" -import {None, Some} from "../src/option" -import {Panic, UnwrapPanic} from "../src" +import {Panic, UnwrapPanic, None, Some} from ".." it("returns a Some option", () => { const option = Some(42) diff --git a/src/promise_option.ts b/src/option/promise.ts similarity index 97% rename from src/promise_option.ts rename to src/option/promise.ts index cf36262..f951157 100644 --- a/src/promise_option.ts +++ b/src/option/promise.ts @@ -1,5 +1,5 @@ -import type {Option} from "./option" -import {Panic} from "./panic" +import type {Option} from "./interface" +import type {Panic} from "../error" export class PromiseOption implements PromiseLike> { constructor(readonly promise: Promise> | PromiseLike>) {} diff --git a/test/promise_option.test.ts b/src/option/promise_option.test.ts similarity index 98% rename from test/promise_option.test.ts rename to src/option/promise_option.test.ts index 9a94b63..8574c1a 100644 --- a/test/promise_option.test.ts +++ b/src/option/promise_option.test.ts @@ -1,7 +1,5 @@ import {describe, it, expect, vi} from "vitest" -import {Panic, UnwrapPanic} from "../src" -import {PromiseOption} from "../src/promise_option" -import {None, Some} from "../src/option" +import {Panic, UnwrapPanic, PromiseOption, Some, None} from ".." function promiseSome(value: T) { return new PromiseOption(Promise.resolve(Some(value))) diff --git a/src/option/some.ts b/src/option/some.ts new file mode 100644 index 0000000..aa677e3 --- /dev/null +++ b/src/option/some.ts @@ -0,0 +1,114 @@ +import type {Panic} from "../error" +import {inspectSymbol} from "../util" +import type {OptionMethods, Option} from "./interface" +import {None} from "./none" + +export class SomeImpl implements OptionMethods { + readonly some = true + readonly none = false + readonly value: T + + constructor(value: T) { + this.value = value + } + + and(other: Option) { + return other + } + + andThen(f: (value: T) => Option) { + return f(this.value) + } + + expect(_panic: string | Panic) { + return this.value + } + + filter(f: (value: T) => boolean) { + return f(this.value) ? this : None + } + + inspect(f: (value: T) => void) { + f(this.value) + return this + } + + isNone(): this is None { + return false + } + + isSome(): this is Some { + return true + } + + isSomeAnd(f: (value: T) => boolean): this is Some { + return f(this.value) + } + + map(f: (value: T) => U) { + return Some(f(this.value)) + } + + mapOr(_defaultValue: A, f: (value: T) => B) { + return f(this.value) + } + + mapOrElse(_defaultValue: () => A, f: (value: T) => B) { + return f(this.value) + } + + or(_other: Option) { + return this + } + + orElse(_f: () => Option) { + return this + } + + unwrap() { + return this.value + } + + unwrapOr(_defaultValue: U) { + return this.value + } + + unwrapOrElse(_defaultValue: () => U) { + return this.value + } + + xor(other: Option) { + return other.isSome() ? None : this + } + + into() { + return this.value + } + + match(some: (value: T) => A, _none: () => B) { + return some(this.value) + } + + toString() { + return `Some(${this.value})` as const + } + + [inspectSymbol]() { + return this.toString() + } + + toObject() { + return {some: true, value: this.value} as const + } + + toJSON() { + return {meta: "Some", data: this.value} as const + } + + static from(value: T): Some { + return new SomeImpl(value) + } +} + +export interface Some extends SomeImpl {} +export const Some = SomeImpl.from diff --git a/src/result.ts b/src/result.ts deleted file mode 100644 index 5629c7b..0000000 --- a/src/result.ts +++ /dev/null @@ -1,291 +0,0 @@ -import {inspectSymbol} from "./util" -import {Panic, UnwrapPanic} from "./panic" - -export interface ResultMethods { - and(other: Result): Result - andThen(f: (value: T) => Result): Result - expect(panic: string | Panic): T - expectErr(panic: string | Panic): E - inspect(f: (value: T) => void): Result - inspectErr(f: (error: E) => void): Result - isErr(): this is Err - isErrAnd(f: (error: E) => boolean): this is Err - isOk(): this is Ok - isOkAnd(f: (value: T) => boolean): this is Ok - map(f: (value: T) => U): Result - mapErr(f: (error: E) => F): Result - mapOr(defaultValue: A, f: (value: T) => B): A | B - mapOrElse(defaultValue: (error: E) => A, f: (value: T) => B): A | B - or(other: Result): Result - orElse(f: (error: E) => Result): Result - unwrap(): T - unwrapErr(): E - unwrapOr(defaultValue: U): T | U - unwrapOrElse(defaultValue: (error: E) => U): T | U - - into(): T | E - match(ok: (value: T) => A, err: (error: E) => B): A | B - - toString(): `Ok(${string})` | `Err(${string})` - [inspectSymbol](): ReturnType["toString"]> - toObject(): {ok: true; value: T} | {ok: false; error: E} - toJSON(): {meta: "Ok"; data: T} | {meta: "Err"; data: E} -} - -export class OkImpl implements ResultMethods { - readonly ok = true - readonly value: T - readonly err = false - readonly error?: never - - constructor(value: T) { - this.value = value - } - - and(other: Result) { - return other - } - - andThen(f: (value: T) => Result) { - return f(this.value) - } - - expect(_panic: string | Panic) { - return this.value - } - - expectErr(panic: string | Panic): never { - if (panic instanceof Panic) { - throw panic - } - throw new Panic(panic) - } - - inspect(f: (value: T) => void) { - f(this.value) - return this - } - - inspectErr(_f: (error: never) => void) { - return this - } - - isErr(): this is Err { - return false - } - - isErrAnd(_f: (error: never) => boolean): this is Err { - return false - } - - isOk(): this is Ok { - return true - } - - isOkAnd(f: (value: T) => boolean): this is Ok { - return f(this.value) - } - - map(f: (value: T) => U) { - return Ok(f(this.value)) - } - - mapErr(_f: (error: never) => F) { - return this - } - - mapOr(_defaultValue: A, f: (value: T) => B) { - return f(this.value) - } - - mapOrElse(_defaultValue: (error: never) => A, f: (value: T) => B) { - return f(this.value) - } - - or(_other: Result) { - return this - } - - orElse(_f: (error: never) => Result) { - return this - } - - unwrap() { - return this.value - } - - unwrapErr(): never { - throw new UnwrapPanic("Cannot unwrapErr on an Ok") - } - - unwrapOr(_defaultValue: U) { - return this.value - } - - unwrapOrElse(_defaultValue: (error: never) => U) { - return this.value - } - - into() { - return this.value - } - - match(ok: (value: T) => A, _err: (error: never) => B) { - return ok(this.value) - } - - toString() { - return `Ok(${this.value})` as const - } - - [inspectSymbol]() { - return this.toString() - } - - toObject() { - return {ok: true, value: this.value} as const - } - - toJSON() { - return {meta: "Ok", data: this.value} as const - } - - static from(): Ok - static from(value: T): Ok - static from(value?: T): Ok { - return new OkImpl(value ? value : null) as Ok - } -} - -export interface Ok extends OkImpl {} -export const Ok = OkImpl.from - -export class ErrImpl implements ResultMethods { - readonly ok = false - readonly value?: never - readonly err = true - readonly error: E - - constructor(error: E) { - this.error = error - } - - and(_other: Result) { - return this - } - - andThen(_f: (value: never) => Result) { - return this - } - - expect(panic: string | Panic): never { - if (panic instanceof Panic) { - throw panic - } - throw new Panic(panic) - } - - expectErr(_panic: string | Panic) { - return this.error - } - - inspect(_f: (value: never) => void) { - return this - } - - inspectErr(f: (error: E) => void) { - f(this.error) - return this - } - - isErr(): this is Err { - return true - } - - isErrAnd(f: (error: E) => boolean): this is Err { - return f(this.error) - } - - isOk(): this is Ok { - return false - } - - isOkAnd(_f: (value: never) => boolean): this is Ok { - return false - } - - map(_f: (value: never) => U) { - return this - } - - mapErr(f: (error: E) => F) { - return Err(f(this.error)) - } - - mapOr(defaultValue: A, _f: (value: never) => B) { - return defaultValue - } - - mapOrElse(defaultValue: (error: E) => A, _f: (value: never) => B) { - return defaultValue(this.error) - } - - or(other: Result) { - return other - } - - orElse(f: (error: E) => Result) { - return f(this.error) - } - - unwrap(): never { - throw new UnwrapPanic(`Cannot unwrap on an Err: ${this.error}`) - } - - unwrapErr() { - return this.error - } - - unwrapOr(defaultValue: U) { - return defaultValue - } - - unwrapOrElse(defaultValue: (error: E) => U) { - return defaultValue(this.error) - } - - into() { - return this.error - } - - match(_ok: (value: never) => A, err: (error: E) => B) { - return err(this.error) - } - - toString() { - return `Err(${this.error})` as const - } - - [inspectSymbol]() { - return this.toString() - } - - toObject() { - return {ok: false, error: this.error} as const - } - - toJSON() { - return {meta: "Err", data: this.error} as const - } - - static from(): Err - static from(error: E): Err - static from(error?: E): Err { - return new ErrImpl(error ? error : null) as Err - } -} - -export interface Err extends ErrImpl {} -export const Err = ErrImpl.from - -export type Result = Ok | Err diff --git a/src/result/err.ts b/src/result/err.ts new file mode 100644 index 0000000..a37cbf5 --- /dev/null +++ b/src/result/err.ts @@ -0,0 +1,132 @@ +import {Panic, UnwrapPanic} from "../error" +import {inspectSymbol} from "../util" +import type {Result, ResultMethods} from "./interface" +import type {Ok} from "./ok" + +export class ErrImpl implements ResultMethods { + readonly ok = false + readonly value?: never + readonly err = true + readonly error: E + + constructor(error: E) { + this.error = error + } + + and(_other: Result) { + return this + } + + andThen(_f: (value: never) => Result) { + return this + } + + expect(panic: string | Panic): never { + if (panic instanceof Panic) { + throw panic + } + throw new Panic(panic) + } + + expectErr(_panic: string | Panic) { + return this.error + } + + inspect(_f: (value: never) => void) { + return this + } + + inspectErr(f: (error: E) => void) { + f(this.error) + return this + } + + isErr(): this is Err { + return true + } + + isErrAnd(f: (error: E) => boolean): this is Err { + return f(this.error) + } + + isOk(): this is Ok { + return false + } + + isOkAnd(_f: (value: never) => boolean): this is Ok { + return false + } + + map(_f: (value: never) => U) { + return this + } + + mapErr(f: (error: E) => F) { + return Err(f(this.error)) + } + + mapOr(defaultValue: A, _f: (value: never) => B) { + return defaultValue + } + + mapOrElse(defaultValue: (error: E) => A, _f: (value: never) => B) { + return defaultValue(this.error) + } + + or(other: Result) { + return other + } + + orElse(f: (error: E) => Result) { + return f(this.error) + } + + unwrap(): never { + throw new UnwrapPanic(`Cannot unwrap on an Err: ${this.error}`) + } + + unwrapErr() { + return this.error + } + + unwrapOr(defaultValue: U) { + return defaultValue + } + + unwrapOrElse(defaultValue: (error: E) => U) { + return defaultValue(this.error) + } + + into() { + return this.error + } + + match(_ok: (value: never) => A, err: (error: E) => B) { + return err(this.error) + } + + toString() { + return `Err(${this.error})` as const + } + + [inspectSymbol]() { + return this.toString() + } + + toObject() { + return {ok: false, error: this.error} as const + } + + toJSON() { + return {meta: "Err", data: this.error} as const + } + + static from(): Err + static from(error: E): Err + static from(error?: E): Err { + return new ErrImpl(error ? error : null) as Err + } +} + +export interface Err extends ErrImpl {} +export const Err = ErrImpl.from diff --git a/src/result/index.ts b/src/result/index.ts new file mode 100644 index 0000000..c06da70 --- /dev/null +++ b/src/result/index.ts @@ -0,0 +1,4 @@ +export * from "./err" +export * from "./interface" +export * from "./ok" +export * from "./promise" diff --git a/src/result/interface.ts b/src/result/interface.ts new file mode 100644 index 0000000..47e57d2 --- /dev/null +++ b/src/result/interface.ts @@ -0,0 +1,37 @@ +import type {Panic} from "../error" +import type {inspectSymbol} from "../util" +import type {Err} from "./err" +import type {Ok} from "./ok" + +export interface ResultMethods { + and(other: Result): Result + andThen(f: (value: T) => Result): Result + expect(panic: string | Panic): T + expectErr(panic: string | Panic): E + inspect(f: (value: T) => void): Result + inspectErr(f: (error: E) => void): Result + isErr(): this is Err + isErrAnd(f: (error: E) => boolean): this is Err + isOk(): this is Ok + isOkAnd(f: (value: T) => boolean): this is Ok + map(f: (value: T) => U): Result + mapErr(f: (error: E) => F): Result + mapOr(defaultValue: A, f: (value: T) => B): A | B + mapOrElse(defaultValue: (error: E) => A, f: (value: T) => B): A | B + or(other: Result): Result + orElse(f: (error: E) => Result): Result + unwrap(): T + unwrapErr(): E + unwrapOr(defaultValue: U): T | U + unwrapOrElse(defaultValue: (error: E) => U): T | U + + into(): T | E + match(ok: (value: T) => A, err: (error: E) => B): A | B + + toString(): `Ok(${string})` | `Err(${string})` + [inspectSymbol](): ReturnType["toString"]> + toObject(): {ok: true; value: T} | {ok: false; error: E} + toJSON(): {meta: "Ok"; data: T} | {meta: "Err"; data: E} +} + +export type Result = Ok | Err diff --git a/src/result/ok.ts b/src/result/ok.ts new file mode 100644 index 0000000..edf21e1 --- /dev/null +++ b/src/result/ok.ts @@ -0,0 +1,132 @@ +import {Panic, UnwrapPanic} from "../error" +import {inspectSymbol} from "../util" +import type {Err} from "./err" +import type {Result, ResultMethods} from "./interface" + +export class OkImpl implements ResultMethods { + readonly ok = true + readonly value: T + readonly err = false + readonly error?: never + + constructor(value: T) { + this.value = value + } + + and(other: Result) { + return other + } + + andThen(f: (value: T) => Result) { + return f(this.value) + } + + expect(_panic: string | Panic) { + return this.value + } + + expectErr(panic: string | Panic): never { + if (panic instanceof Panic) { + throw panic + } + throw new Panic(panic) + } + + inspect(f: (value: T) => void) { + f(this.value) + return this + } + + inspectErr(_f: (error: never) => void) { + return this + } + + isErr(): this is Err { + return false + } + + isErrAnd(_f: (error: never) => boolean): this is Err { + return false + } + + isOk(): this is Ok { + return true + } + + isOkAnd(f: (value: T) => boolean): this is Ok { + return f(this.value) + } + + map(f: (value: T) => U) { + return Ok(f(this.value)) + } + + mapErr(_f: (error: never) => F) { + return this + } + + mapOr(_defaultValue: A, f: (value: T) => B) { + return f(this.value) + } + + mapOrElse(_defaultValue: (error: never) => A, f: (value: T) => B) { + return f(this.value) + } + + or(_other: Result) { + return this + } + + orElse(_f: (error: never) => Result) { + return this + } + + unwrap() { + return this.value + } + + unwrapErr(): never { + throw new UnwrapPanic("Cannot unwrapErr on an Ok") + } + + unwrapOr(_defaultValue: U) { + return this.value + } + + unwrapOrElse(_defaultValue: (error: never) => U) { + return this.value + } + + into() { + return this.value + } + + match(ok: (value: T) => A, _err: (error: never) => B) { + return ok(this.value) + } + + toString() { + return `Ok(${this.value})` as const + } + + [inspectSymbol]() { + return this.toString() + } + + toObject() { + return {ok: true, value: this.value} as const + } + + toJSON() { + return {meta: "Ok", data: this.value} as const + } + + static from(): Ok + static from(value: T): Ok + static from(value?: T): Ok { + return new OkImpl(value ? value : null) as Ok + } +} + +export interface Ok extends OkImpl {} +export const Ok = OkImpl.from diff --git a/src/promise_result.ts b/src/result/promise.ts similarity index 97% rename from src/promise_result.ts rename to src/result/promise.ts index 7b79dbd..a5dbf40 100644 --- a/src/promise_result.ts +++ b/src/result/promise.ts @@ -1,5 +1,5 @@ -import type {Result} from "./result" -import {type Panic} from "./panic" +import type {Result} from "./interface" +import type {Panic} from "../error" export class PromiseResult implements PromiseLike> { constructor(readonly promise: Promise> | PromiseLike>) {} diff --git a/test/promise_result.test.ts b/src/result/promise_result.test.ts similarity index 99% rename from test/promise_result.test.ts rename to src/result/promise_result.test.ts index 04d1933..af43fa3 100644 --- a/test/promise_result.test.ts +++ b/src/result/promise_result.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from "vitest" -import {Err, Panic, PromiseResult, Ok, UnwrapPanic} from "../src" +import {Err, Panic, PromiseResult, Ok, UnwrapPanic} from ".." function promiseOk(value: T) { return new PromiseResult(Promise.resolve(Ok(value))) diff --git a/test/result.test.ts b/src/result/result.test.ts similarity index 99% rename from test/result.test.ts rename to src/result/result.test.ts index a449f53..f4babee 100644 --- a/test/result.test.ts +++ b/src/result/result.test.ts @@ -1,5 +1,5 @@ import {describe, it, expect} from "vitest" -import {Panic, UnwrapPanic, Ok, Err, Result} from "../src" +import {Panic, UnwrapPanic, Ok, Err, Result} from ".." describe.concurrent("ok", () => { it("returns an Ok result", () => { diff --git a/src/util.ts b/src/util.ts index c3e4386..e82853b 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,7 +1,5 @@ -import {PromiseOption} from "./promise_option" -import {Option} from "./option" -import type {PromiseResult} from "./promise_result" -import type {Result} from "./result" +import type {PromiseOption, Option} from "./option" +import type {PromiseResult, Result} from "./result" export const inspectSymbol = Symbol.for("nodejs.util.inspect.custom") From 0ae03ba7e7a5e78591b88d2ad271710bdd14f272 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:04:12 +0100 Subject: [PATCH 24/30] Update panic --- src/error/panic.test.ts | 38 ++++++++++++++++++++++++++++ src/error/panic.ts | 53 +++++++++++++++++++++------------------ src/error/result_error.ts | 6 ++--- 3 files changed, 68 insertions(+), 29 deletions(-) create mode 100644 src/error/panic.test.ts diff --git a/src/error/panic.test.ts b/src/error/panic.test.ts new file mode 100644 index 0000000..91dc42b --- /dev/null +++ b/src/error/panic.test.ts @@ -0,0 +1,38 @@ +import {expect, it} from "vitest" +import {Panic} from "./panic" + +it("returns an instance without args", () => { + const panic = new Panic() + + expect(panic).toBeInstanceOf(Error) + expect(panic).toBeInstanceOf(Panic) + + expect(panic.message).toEqual("") + expect(panic.stack).toContain("Panic: ") +}) + +it("returns an instance with message", () => { + const msg = "msg" + const panic = new Panic(msg) + + expect(panic).toBeInstanceOf(Error) + expect(panic).toBeInstanceOf(Panic) + + expect(panic.message).toEqual(msg) + expect(panic.stack).toContain(`Panic: ${msg}`) +}) + +it("returns an instance with error", () => { + let origin = new Error("msg") + let panic = new Panic(origin) + expect(panic).toBeInstanceOf(Error) + expect(panic).toBeInstanceOf(Panic) + + expect(panic.origin).toEqual(origin) + expect(panic.message).toEqual(origin.message) + expect(panic.stack).toContain(`Panic: ${origin.message}`) + + origin.name = "MyError" + panic = new Panic(origin) + expect(panic.stack).toContain(`Panic from MyError: ${origin.message}`) +}) diff --git a/src/error/panic.ts b/src/error/panic.ts index 1e62319..ef6889b 100644 --- a/src/error/panic.ts +++ b/src/error/panic.ts @@ -1,49 +1,52 @@ -/** Extends Error, used for unrecoverable errors. */ +import {inspectSymbol} from "../util" + export class Panic extends Error { - private static defaultName = "Panic" + readonly origin?: Error + private readonly originName: string + private readonly _stack?: string - constructor(messageOrError: string | Error) { + constructor(messageOrError?: string | Error) { if (messageOrError instanceof Error) { - const error = messageOrError - super(error.message) - this.name = `${Panic.defaultName}: ${error.name}` - if (error.stack) { - this.stack = error.stack - } + super(messageOrError.message) + this.origin = messageOrError } else { - const message = messageOrError - super(message) - this.name = Panic.defaultName + super(messageOrError) } + this.originName = this.origin?.name ?? "Error" + this.name = this.originName !== "Error" ? `Panic from ${this.originName}` : "Panic" + this._stack = this.stack // Save a copy of the stack trace before it gets overridden. + } + + override get stack() { + const r = new RegExp(`^${this.originName}`) + return this._stack?.replace(r, this.name) + } + + [inspectSymbol]() { + return this.stack } } export class UnwrapPanic extends Panic { - constructor(messageOrError: string | Error) { - super(messageOrError) + constructor(msg: string) { + super(msg) } } export class InvalidErrorPanic extends Panic { - constructor(public error: unknown) { - super("Invalid Error value") + constructor(value: unknown) { + super(`Invalid error: "${value}"`) } } export class TodoPanic extends Panic { - constructor(message = "Todo") { - super(message) - } + override message = "Todo" } export class UnreachablePanic extends Panic { - constructor(message = "Unreachable") { - super(message) - } + override message = "Unreachable" } export class UnimplementedPanic extends Panic { - constructor(message = "Unimplemented") { - super(message) - } + override message = "Unimplemented" } diff --git a/src/error/result_error.ts b/src/error/result_error.ts index bd95817..6cbd1b7 100644 --- a/src/error/result_error.ts +++ b/src/error/result_error.ts @@ -6,6 +6,7 @@ export abstract class ResultError implements Error { readonly message: string readonly origin?: Readonly + private readonly originName: string private readonly _stack?: string constructor(messageOrError: string | Error = "") { @@ -21,16 +22,13 @@ export abstract class ResultError implements Error { if (!this._stack) { this._stack = new Error(this.message).stack } + this.originName = this.origin?.name ?? "Error" } get name() { return this.originName !== "Error" ? `${this.tag} from ${this.originName}` : this.tag } - private get originName() { - return this.origin?.name ?? "Error" - } - // Tries to replace the stack trace to include the subclass error name. // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, // meaning different JavaScript engines might produce different stack traces. From edfbe5dbe4995208ed485549582ec77ce0e41130 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:10:03 +0100 Subject: [PATCH 25/30] Add error util --- src/error/panic.ts | 6 +++--- src/error/result_error.ts | 12 ++++-------- src/error/util.ts | 21 +++++++++++++++++++++ 3 files changed, 28 insertions(+), 11 deletions(-) create mode 100644 src/error/util.ts diff --git a/src/error/panic.ts b/src/error/panic.ts index ef6889b..af2d202 100644 --- a/src/error/panic.ts +++ b/src/error/panic.ts @@ -1,4 +1,5 @@ import {inspectSymbol} from "../util" +import {getName, replaceStack} from "./util" export class Panic extends Error { readonly origin?: Error @@ -13,13 +14,12 @@ export class Panic extends Error { super(messageOrError) } this.originName = this.origin?.name ?? "Error" - this.name = this.originName !== "Error" ? `Panic from ${this.originName}` : "Panic" + this.name = getName("Panic", this.originName) this._stack = this.stack // Save a copy of the stack trace before it gets overridden. } override get stack() { - const r = new RegExp(`^${this.originName}`) - return this._stack?.replace(r, this.name) + return replaceStack(this.name, this.originName, this._stack) } [inspectSymbol]() { diff --git a/src/error/result_error.ts b/src/error/result_error.ts index 6cbd1b7..bef9615 100644 --- a/src/error/result_error.ts +++ b/src/error/result_error.ts @@ -1,5 +1,6 @@ import {inspectSymbol} from "../util" import {InvalidErrorPanic, Panic} from "./panic" +import {getName, getOriginName, replaceStack} from "./util" export abstract class ResultError implements Error { abstract readonly tag: string @@ -22,20 +23,15 @@ export abstract class ResultError implements Error { if (!this._stack) { this._stack = new Error(this.message).stack } - this.originName = this.origin?.name ?? "Error" + this.originName = getOriginName(this.origin) } get name() { - return this.originName !== "Error" ? `${this.tag} from ${this.originName}` : this.tag + return getName(this.tag, this.originName) } - // Tries to replace the stack trace to include the subclass error name. - // May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, - // meaning different JavaScript engines might produce different stack traces. - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack get stack() { - const r = new RegExp(`^${this.originName}`) - return this._stack?.replace(r, this.name) + return replaceStack(this.name, this.originName, this._stack) } toString() { diff --git a/src/error/util.ts b/src/error/util.ts new file mode 100644 index 0000000..920fe95 --- /dev/null +++ b/src/error/util.ts @@ -0,0 +1,21 @@ +/** + * Tries to replace the stack trace to include the subclass error name. + * + * May not work in every environment, since `stack` property is implementation-dependent and isn't standardized, + * + * meaning different JavaScript engines might produce different stack traces. + * + * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack + */ +export function replaceStack(name: string, originName: string, stack?: string) { + const r = new RegExp(`^${originName}`) + return stack?.replace(r, name) +} + +export function getOriginName(origin?: Error) { + return origin?.name ?? "Error" +} + +export function getName(name: string, originName: string) { + return originName !== "Error" ? `${name} from ${originName}` : name +} From 55d678144ce293a0f698360676ef0971f9043a0d Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:21:05 +0100 Subject: [PATCH 26/30] Add sideEffects false key to pkg json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index e53b6ef..be7009f 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "typescript": "^5.0.4", "vitest": "^0.34.6" }, + "sideEffects": false, "lint-staged": { "*.{ts,yaml,json,md}": "prettier --write" }, From 0cb0d782c4f62fc9bc287e20c9071bd5ce591780 Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:24:37 +0100 Subject: [PATCH 27/30] Remove barrel files --- src/error/index.ts | 2 -- src/helpers/index.ts | 3 --- src/index.ts | 19 +++++++++++++++---- src/option/index.ts | 4 ---- src/result/index.ts | 4 ---- 5 files changed, 15 insertions(+), 17 deletions(-) delete mode 100644 src/error/index.ts delete mode 100644 src/helpers/index.ts delete mode 100644 src/option/index.ts delete mode 100644 src/result/index.ts diff --git a/src/error/index.ts b/src/error/index.ts deleted file mode 100644 index b18c305..0000000 --- a/src/error/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./panic" -export * from "./result_error" diff --git a/src/helpers/index.ts b/src/helpers/index.ts deleted file mode 100644 index 3038d75..0000000 --- a/src/helpers/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./fn" -export * from "./guard" -export * from "./try" diff --git a/src/index.ts b/src/index.ts index 7f49d1e..a5bc199 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,16 @@ -export * from "./error" -export * from "./helpers" -export * from "./option" -export * from "./result" +export * from "./error/panic" +export * from "./error/result_error" + +export * from "./helpers/fn" +export * from "./helpers/guard" +export * from "./helpers/try" + +export * from "./option/none" +export * from "./option/promise" +export * from "./option/some" + +export * from "./result/err" +export * from "./result/ok" +export * from "./result/promise" + export * from "./util" diff --git a/src/option/index.ts b/src/option/index.ts deleted file mode 100644 index f882305..0000000 --- a/src/option/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./interface" -export * from "./none" -export * from "./some" -export * from "./promise" diff --git a/src/result/index.ts b/src/result/index.ts deleted file mode 100644 index c06da70..0000000 --- a/src/result/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from "./err" -export * from "./interface" -export * from "./ok" -export * from "./promise" From 694b8c76548549e09750a21ca6225f48e5a3638c Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:31:40 +0100 Subject: [PATCH 28/30] Fix imports --- src/helpers/fn.ts | 3 ++- src/helpers/guard.ts | 2 +- src/helpers/try.ts | 7 +++++-- src/option/interface.ts | 2 +- src/option/none.ts | 2 +- src/option/promise.ts | 2 +- src/option/some.ts | 2 +- src/result/err.ts | 2 +- src/result/interface.ts | 2 +- src/result/ok.ts | 2 +- src/result/promise.ts | 2 +- src/util.ts | 6 ++++-- 12 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/helpers/fn.ts b/src/helpers/fn.ts index 9212502..d3024c3 100644 --- a/src/helpers/fn.ts +++ b/src/helpers/fn.ts @@ -1,5 +1,6 @@ import type {ResultValueType, ResultErrorType} from "../util" -import {type Result, PromiseResult} from "../result" +import type {Result} from "../result/interface" +import {PromiseResult} from "../result/promise" export function fn Result>( f: T, diff --git a/src/helpers/guard.ts b/src/helpers/guard.ts index eadc653..ab3f2ac 100644 --- a/src/helpers/guard.ts +++ b/src/helpers/guard.ts @@ -1,4 +1,4 @@ -import type {ErrorHandler, ResultError} from "../error" +import type {ErrorHandler, ResultError} from "../error/result_error" import {tryAsyncFn, tryAsyncFnWith, tryFn, tryFnWith} from "./try" type Fn = (...args: any[]) => any diff --git a/src/helpers/try.ts b/src/helpers/try.ts index 14b6515..b9eecee 100644 --- a/src/helpers/try.ts +++ b/src/helpers/try.ts @@ -1,5 +1,8 @@ -import {Err, Ok, PromiseResult, type Result} from "../result" -import {type ErrorHandler, type ResultError, type StdError, toStdError} from "../error" +import {type ErrorHandler, type ResultError, type StdError, toStdError} from "../error/result_error" +import {Err} from "../result/err" +import type {Result} from "../result/interface" +import {Ok} from "../result/ok" +import {PromiseResult} from "../result/promise" // Couldn't figure out how to overload these functions without a TypeScript error and making // the error handler required if the error template param is defined. diff --git a/src/option/interface.ts b/src/option/interface.ts index 3a146c2..f25ddf5 100644 --- a/src/option/interface.ts +++ b/src/option/interface.ts @@ -1,4 +1,4 @@ -import type {Panic} from "../error" +import type {Panic} from "../error/panic" import type {inspectSymbol} from "../util" import type {None} from "./none" import type {Some} from "./some" diff --git a/src/option/none.ts b/src/option/none.ts index 32cd65f..6a4df12 100644 --- a/src/option/none.ts +++ b/src/option/none.ts @@ -1,4 +1,4 @@ -import {Panic, UnwrapPanic} from "../error" +import {Panic, UnwrapPanic} from "../error/panic" import {inspectSymbol} from "../util" import type {OptionMethods, Option} from "./interface" import type {Some} from "./some" diff --git a/src/option/promise.ts b/src/option/promise.ts index f951157..ee0a329 100644 --- a/src/option/promise.ts +++ b/src/option/promise.ts @@ -1,5 +1,5 @@ +import type {Panic} from "../error/panic" import type {Option} from "./interface" -import type {Panic} from "../error" export class PromiseOption implements PromiseLike> { constructor(readonly promise: Promise> | PromiseLike>) {} diff --git a/src/option/some.ts b/src/option/some.ts index aa677e3..35da0c3 100644 --- a/src/option/some.ts +++ b/src/option/some.ts @@ -1,4 +1,4 @@ -import type {Panic} from "../error" +import type {Panic} from "../error/panic" import {inspectSymbol} from "../util" import type {OptionMethods, Option} from "./interface" import {None} from "./none" diff --git a/src/result/err.ts b/src/result/err.ts index a37cbf5..0423f28 100644 --- a/src/result/err.ts +++ b/src/result/err.ts @@ -1,4 +1,4 @@ -import {Panic, UnwrapPanic} from "../error" +import {Panic, UnwrapPanic} from "../error/panic" import {inspectSymbol} from "../util" import type {Result, ResultMethods} from "./interface" import type {Ok} from "./ok" diff --git a/src/result/interface.ts b/src/result/interface.ts index 47e57d2..6916801 100644 --- a/src/result/interface.ts +++ b/src/result/interface.ts @@ -1,4 +1,4 @@ -import type {Panic} from "../error" +import type {Panic} from "../error/panic" import type {inspectSymbol} from "../util" import type {Err} from "./err" import type {Ok} from "./ok" diff --git a/src/result/ok.ts b/src/result/ok.ts index edf21e1..74bb70d 100644 --- a/src/result/ok.ts +++ b/src/result/ok.ts @@ -1,4 +1,4 @@ -import {Panic, UnwrapPanic} from "../error" +import {Panic, UnwrapPanic} from "../error/panic" import {inspectSymbol} from "../util" import type {Err} from "./err" import type {Result, ResultMethods} from "./interface" diff --git a/src/result/promise.ts b/src/result/promise.ts index a5dbf40..f9ebeff 100644 --- a/src/result/promise.ts +++ b/src/result/promise.ts @@ -1,5 +1,5 @@ import type {Result} from "./interface" -import type {Panic} from "../error" +import type {Panic} from "../error/panic" export class PromiseResult implements PromiseLike> { constructor(readonly promise: Promise> | PromiseLike>) {} diff --git a/src/util.ts b/src/util.ts index e82853b..511dd73 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,7 @@ -import type {PromiseOption, Option} from "./option" -import type {PromiseResult, Result} from "./result" +import type {Option} from "./option/interface" +import type {PromiseOption} from "./option/promise" +import type {Result} from "./result/interface" +import type {PromiseResult} from "./result/promise" export const inspectSymbol = Symbol.for("nodejs.util.inspect.custom") From 4a96c2d23f0666986d898c4636c09e48e3d5470a Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:32:04 +0100 Subject: [PATCH 29/30] Rename tests --- src/option/{promise_option.test.ts => promise.test.ts} | 0 src/result/{promise_result.test.ts => promise.test.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/option/{promise_option.test.ts => promise.test.ts} (100%) rename src/result/{promise_result.test.ts => promise.test.ts} (100%) diff --git a/src/option/promise_option.test.ts b/src/option/promise.test.ts similarity index 100% rename from src/option/promise_option.test.ts rename to src/option/promise.test.ts diff --git a/src/result/promise_result.test.ts b/src/result/promise.test.ts similarity index 100% rename from src/result/promise_result.test.ts rename to src/result/promise.test.ts From 0ec36001933b2da158b570dbeb9ac116d1d8c6ae Mon Sep 17 00:00:00 2001 From: bkiac Date: Fri, 3 Nov 2023 19:34:24 +0100 Subject: [PATCH 30/30] Fix export --- src/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.ts b/src/index.ts index a5bc199..f1fdcb0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,10 +5,12 @@ export * from "./helpers/fn" export * from "./helpers/guard" export * from "./helpers/try" +export * from "./option/interface" export * from "./option/none" export * from "./option/promise" export * from "./option/some" +export * from "./result/interface" export * from "./result/err" export * from "./result/ok" export * from "./result/promise"