Skip to content

Commit

Permalink
add inline documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
oofdere committed Aug 19, 2024
1 parent 5840fa0 commit 5796c80
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 80 deletions.
2 changes: 1 addition & 1 deletion index.ts
Original file line number Diff line number Diff line change
@@ -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";
50 changes: 9 additions & 41 deletions playground.ts
Original file line number Diff line number Diff line change
@@ -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<E> = { [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<Colors>;
// ^?
return Ok("Success!");
}

const red: Enum<Colors> = ["Raw", "#fff"];
const black = ["Rgb", [0, 0, 0]] as Enum<Colors>;

export const pack = <const E>(...entry: Enum<E>) => entry satisfies Enum<E>; //=>

const white = pack<Colors>("Raw", "");

// type to create the matching arms used in the match function
// "-?" removes any optional parameters, making all cases required
type Arms<E> = {
// biome-ignore lint/suspicious/noExplicitAny: type checking is handled externally
[K in keyof E]-?: (x: E[K]) => any;
};

type PartialArms<E, T> = {
[K in keyof E]?: (x: E[K]) => T;
};

export const match = <E, Fn extends Arms<E>>(
pattern: Enum<E>,
arms: Fn,
): ReturnType<(typeof arms)[keyof typeof arms]> =>
// biome-ignore lint/suspicious/noExplicitAny: required
arms[pattern[0]](pattern[1] as any);

export const matchPartial = <E, T>(
pattern: Enum<E>,
arms: PartialArms<E, T>,
// 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());
32 changes: 28 additions & 4 deletions src/enum.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,52 @@
// 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<E> = { [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<E> =
| {
[K in keyof E]-?: (x: E[K]) => unknown;
}
| ({
[L in keyof E]?: (x: E[L]) => unknown;
} & {
// make this show only remaining types instead of all types at some point
_: (x: Enum<E>[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>} e - The enum entries
* @returns {E} The created enum
*/
export const pack = <E>(...e: Enum<E>) => e as E;

/**
* Factory function to create enum instances.
* @template E - The enum type
* @returns {(...e: Enum<E>) => E} A function used to create instances of the enum
*/
export const Enum =
<E>() =>
(...e: Enum<E>) =>
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<Exclude<Fn[keyof Fn], undefined>>} The result of the matched function
*/
export const match = <E, Fn extends Arms<E>>(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<Exclude<(typeof arms)[keyof typeof arms], undefined>>;
54 changes: 26 additions & 28 deletions src/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<FetchErr>();

export async function f(
input: string | URL | globalThis.Request,
init?: RequestInit,
input: string | URL | globalThis.Request,
init?: RequestInit,
): Promise<Result<Response, FetchErr>> {
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;
}
}
9 changes: 9 additions & 0 deletions src/option.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,14 @@ export type Option<T> = {
None: undefined;
};

/**
* @returns Option<T>
* @param some
*/
export const Some = <T>(v: T): Option<T> => ["Some", v] as unknown as Option<T>;

/**
* @returns Creates a None instance of Option<T>
* @param this Option<T>
*/
export const None = <T>(_v?: T): Option<T> => ["None"] as unknown as Option<T>;
Empty file removed src/pipe.ts
Empty file.
34 changes: 34 additions & 0 deletions src/result.ts
Original file line number Diff line number Diff line change
@@ -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<T, E> = {
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<T, E>} A Result containing the success value
* @example
* const Result = Ok<number, string>(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 = <T, E>(ok: T, _err?: E): Result<T, E> =>
["Ok", ok] as unknown as Result<T, E>;

/**
* 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<T, E>} A Result containing the error value
* @example
* const errorResult = Err<number, string>("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 = <T, E>(err: E, _ok?: T): Result<T, E> =>
["Err", err] as unknown as Result<T, E>;
43 changes: 37 additions & 6 deletions src/unwrap.ts
Original file line number Diff line number Diff line change
@@ -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<U>(this: Option<U> | Result<U, any>): 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<U, E>(this: Option<U> | Result<U, any>): 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<U, E>(this: Option<U> | Result<U, any>, fallback: E): U | E;
}
}

const a = Array.prototype;

a.unwrap = function () {
// @ts-expect-error
a.unwrap = function <T, E>(this: Enum<Result<T, E>> | Enum<Option<T>>) {
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 <T, E, U>(
this: Enum<Result<T, E>> | Enum<Option<T>>,
fallback: U,
) {
if (this[0] === "Some" || this[0] === "Ok") {
return this[1];
}
Expand Down

0 comments on commit 5796c80

Please sign in to comment.