diff --git a/index.ts b/index.ts index e69b7f5..bd55078 100644 --- a/index.ts +++ b/index.ts @@ -1,3 +1,3 @@ -export { Enum, type EnumChecked, pack, match } from "./src/enum"; +export { Enum, pack, match } from "./src/enum"; export { type Option, Some, None } from "./src/option"; export { type Result, Ok, Err } from "./src/result"; diff --git a/playground.ts b/playground.ts index 4b0fcc2..c0d9b8e 100644 --- a/playground.ts +++ b/playground.ts @@ -1,44 +1,12 @@ -// enums are defined as object types, like so: -type Colors = { - Rgb: [number, number, number]; - Raw: string; -}; +import { Enum } from "./src/enum"; +import { Err, Ok, type Result } from "./src/result"; -// the enum instance type iterates through all keys of the enum, and pulls out the key and type into all possible tuples -type Enum = { [K in keyof E]: [K, E[K]] }[keyof E]; +function mightError(): Result<"Success!", "Error Here!"> { + if (Math.random() > 0.5) { + return Err("Error Here!"); + } -type ColorsEnum = Enum; -// ^? + return Ok("Success!"); +} -const red: Enum = ["Raw", "#fff"]; -const black = ["Rgb", [0, 0, 0]] as Enum; - -export const pack = (...entry: Enum) => entry satisfies Enum; //=> - -const white = pack("Raw", ""); - -// type to create the matching arms used in the match function -// "-?" removes any optional parameters, making all cases required -type Arms = { - // biome-ignore lint/suspicious/noExplicitAny: type checking is handled externally - [K in keyof E]-?: (x: E[K]) => any; -}; - -type PartialArms = { - [K in keyof E]?: (x: E[K]) => T; -}; - -export const match = >( - pattern: Enum, - arms: Fn, -): ReturnType<(typeof arms)[keyof typeof arms]> => - // biome-ignore lint/suspicious/noExplicitAny: required - arms[pattern[0]](pattern[1] as any); - -export const matchPartial = ( - pattern: Enum, - arms: PartialArms, - // biome-ignore lint/suspicious/noExplicitAny: laziness, this can get typed with a union of the arm types - fallback: (x: any) => T, - // biome-ignore lint/suspicious/noExplicitAny: required -): T => ((arms[pattern[0]] as any) || fallback)(pattern[1] as any); +console.log(mightError()); diff --git a/src/enum.ts b/src/enum.ts index cbc1a28..e3ae75e 100644 --- a/src/enum.ts +++ b/src/enum.ts @@ -1,8 +1,14 @@ -// enum objects are simply a tuple containing a key and a value which are derived from the enum template E +/** + * Represents an enum object as a tuple containing a key and a value derived from the enum template E. + * @template E - The enum type + */ export type Enum = { [K in keyof E]: [K, E[K]] }[keyof E]; -// type to create the matching arms used in the match function -// "-?" removes any optional parameters, making all cases required +/** + * Defines the structure for matching arms used in the match function. + * All cases are required, unless a default case is provided with `_`. + * @template E - The enum type + */ export type Arms = | { [K in keyof E]-?: (x: E[K]) => unknown; @@ -10,19 +16,37 @@ export type Arms = | ({ [L in keyof E]?: (x: E[L]) => unknown; } & { + // make this show only remaining types instead of all types at some point _: (x: Enum[1]) => unknown; }); -// this is a generic way to create enum instances, but the Enum() factory function is preferred +/** + * Creates enum instances. The Enum() factory function is preferred over this method. + * @template E - The enum type + * @param {...Enum} e - The enum entries + * @returns {E} The created enum + */ export const pack = (...e: Enum) => e as E; +/** + * Factory function to create enum instances. + * @template E - The enum type + * @returns {(...e: Enum) => E} A function used to create instances of the enum + */ export const Enum = () => (...e: Enum) => e as unknown as E; +/** + * Matches a pattern against a set of arms and executes the corresponding function. + * @param {E} pattern - The enum to match + * @param {Fn} arms - The object containing matching arms + * @returns {ReturnType>} The result of the matched function + */ export const match = >(pattern: E, arms: Fn) => // biome-ignore lint/suspicious/noExplicitAny: return type is handled externally (((arms as any)[(pattern as any[])[0]] as any) || (arms as any)._)( + // biome-ignore lint/suspicious/noExplicitAny: return type is handled externally (pattern as any)[1] as unknown, ) as ReturnType>; diff --git a/src/fetch.ts b/src/fetch.ts index 910b5ae..d8ab90b 100644 --- a/src/fetch.ts +++ b/src/fetch.ts @@ -2,41 +2,39 @@ import { Enum, match, pack } from "./enum"; import { Err, Ok, type Result } from "./result"; type FetchErr = { - AbortError: DOMException; - NotAllowedError: DOMException; - TypeError: TypeError; - Redirect: Response; - ClientError: Response; - ServerError: Response; + AbortError: DOMException; + NotAllowedError: DOMException; + TypeError: TypeError; + Redirect: Response; + ClientError: Response; + ServerError: Response; }; -const todo = (x?: unknown) => console.trace("todo!", x); const FetchErr = Enum(); export async function f( - input: string | URL | globalThis.Request, - init?: RequestInit, + input: string | URL | globalThis.Request, + init?: RequestInit, ): Promise> { - try { - const res = await fetch(input, init); + try { + const res = await fetch(input, init); - return res.ok - ? Ok(res) - : res.status < 400 - ? Err(FetchErr("Redirect", res)) - : res.status < 500 - ? Err(FetchErr("ClientError", res)) - : Err(FetchErr("ServerError", res)); + return res.ok + ? Ok(res) + : res.status < 400 + ? Err(FetchErr("Redirect", res)) + : res.status < 500 + ? Err(FetchErr("ClientError", res)) + : Err(FetchErr("ServerError", res)); + } catch (e) { + if (e instanceof TypeError) { + return Err(FetchErr("TypeError", e)); + } - } catch (e) { - if (e instanceof TypeError) { - return Err(FetchErr("TypeError", e)); - } + if (e instanceof DOMException) { + return Err(FetchErr("AbortError", e)); + } - if (e instanceof DOMException) { - return Err(FetchErr("AbortError", e)); - } - - throw e; - } + throw e; + } } diff --git a/src/option.ts b/src/option.ts index a982b7d..9d9870e 100644 --- a/src/option.ts +++ b/src/option.ts @@ -6,5 +6,14 @@ export type Option = { None: undefined; }; +/** + * @returns Option + * @param some + */ export const Some = (v: T): Option => ["Some", v] as unknown as Option; + +/** + * @returns Creates a None instance of Option + * @param this Option + */ export const None = (_v?: T): Option => ["None"] as unknown as Option; diff --git a/src/pipe.ts b/src/pipe.ts deleted file mode 100644 index e69de29..0000000 diff --git a/src/result.ts b/src/result.ts index 56477d9..8019486 100644 --- a/src/result.ts +++ b/src/result.ts @@ -1,12 +1,46 @@ import type { Enum } from "./enum"; import "./unwrap"; +/** + * Represents a result that can be either Ok(T) or Err(E). + * @template T - The type of the value when the result is Ok + * @template E - The type of the error when the result is Err + * @property {T} Ok - The success value + * @property {E} Err - The error value + */ export type Result = { Ok: T; Err: E; }; +/** + * Creates an Ok variant of the Result type, containing a success value. + * @template T - The type of the success value + * @template E - The type of the error (unused in Ok, but needed for type consistency) + * @param {T} ok - The success value to wrap in Ok + * @param {E} [_err] - Optional parameter, ignored in the implementation + * @returns {Result} A Result containing the success value + * @example + * const Result = Ok(42); + * @remarks + * Generic parameters are required! Make sure they get inferred correctly, + * or set them manually. DO NOT RELY ON FUNCTION RETURN INFERENCE. + */ export const Ok = (ok: T, _err?: E): Result => ["Ok", ok] as unknown as Result; + +/** + * Creates an Err variant of the Result type, containing an error value. + * @template T - The type of the success value (unused in Err, but needed for type consistency) + * @template E - The type of the error + * @param {E} err - The error value to wrap in Err + * @param {T} [_ok] - Optional parameter, ignored in the implementation + * @returns {Result} A Result containing the error value + * @example + * const errorResult = Err("An error occurred"); + * @remarks + * Generic parameters are required! Make sure they get inferred correctly, + * or set them manually. DO NOT RELY ON FUNCTION RETURN INFERENCE. + */ export const Err = (err: E, _ok?: T): Result => ["Err", err] as unknown as Result; diff --git a/src/unwrap.ts b/src/unwrap.ts index 25e4b8f..612cbe6 100644 --- a/src/unwrap.ts +++ b/src/unwrap.ts @@ -1,27 +1,58 @@ // this adds the unwrap function to array prototypes +import type { Enum } from "./enum"; import type { Option } from "./option"; import type { Result } from "./result"; declare global { interface Object { - // we lie about the enum array being an object here for autocomplete - // biome-ignore lint/suspicious/noExplicitAny: function doesn't use E - unwrap(this: Option | Result): U; - // biome-ignore lint/suspicious/noExplicitAny: fucntion doesn't use E + /** + * Unwraps the value from a Some or Ok variant. + * @template U - The type of the wrapped value + * @template E - The type of the error (unused in the function, but needed for type consistency) + * @returns {U} The unwrapped value + * @throws {TypeError} If the variant is None or Err + * @example + * const result = Ok(42); + * console.log(result.unwrap()); // 42 + * + * const option = Some("hello"); + * console.log(option.unwrap()); // "hello" + */ + // biome-ignore lint/suspicious/noExplicitAny: function doesn't use Err + unwrap(this: Option | Result): U; + /** + * Returns the value of Some/Ok, or the fallback value if None/Err. + * @template U - The type of the wrapped value + * @template E - The type of the fallback value + * @param {E} fallback - The value to return if the variant is None or Err + * @returns {U | E} The unwrapped value or the fallback + * @example + * const result = Err("error"); + * console.log(result.or(0)); // 0 + * + * const option = None(); + * console.log(option.or("default")); // "default" + */ + // biome-ignore lint/suspicious/noExplicitAny: fucntion doesn't use Err or(this: Option | Result, fallback: E): U | E; } } const a = Array.prototype; -a.unwrap = function () { +// @ts-expect-error +a.unwrap = function (this: Enum> | Enum>) { if (this[0] === "Some" || this[0] === "Ok") { return this[1]; } throw TypeError(`${this[0]}(): ${this[1]}`); }; -a.or = function (fallback) { +// @ts-expect-error +a.or = function ( + this: Enum> | Enum>, + fallback: U, +) { if (this[0] === "Some" || this[0] === "Ok") { return this[1]; }