Skip to content

Commit

Permalink
feat: create Option and Result types
Browse files Browse the repository at this point in the history
  • Loading branch information
lukemorales committed Aug 19, 2024
1 parent 403926c commit 0d3c217
Show file tree
Hide file tree
Showing 20 changed files with 3,147 additions and 1,402 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-falcons-arrive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'funkcia': minor
---

Create `Option` and `Result` types
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</p>

<p align="center">
`Result` and `Option` types inspired by the best parts of Rust, OCaml, Nim, Scala, and Haskell
<code>Result</code> and <code>Option</code> types inspired by the best parts of Rust, OCaml, Nim, Scala, and Haskell
<br> providing a type-safe way to build your applications with better DX
</p>

Expand Down
24 changes: 23 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,20 @@
"types": "./dist/index.d.ts",
"module": "./dist/index.mjs"
},
"./exceptions": {
"default": "./dist/exceptions.mjs",
"import": "./dist/exceptions.mjs",
"require": "./dist/exceptions.js",
"types": "./dist/exceptions.d.ts",
"module": "./dist/exceptions.mjs"
},
"./functions": {
"default": "./dist/functions.mjs",
"import": "./dist/functions.mjs",
"require": "./dist/functions.js",
"types": "./dist/functions.d.ts",
"module": "./dist/functions.mjs"
},
"./json": {
"default": "./dist/json.mjs",
"import": "./dist/json.mjs",
Expand All @@ -41,6 +55,13 @@
"types": "./dist/option.d.ts",
"module": "./dist/option.mjs"
},
"./predicate": {
"default": "./dist/predicate.mjs",
"import": "./dist/predicate.mjs",
"require": "./dist/predicate.js",
"types": "./dist/predicate.d.ts",
"module": "./dist/predicate.mjs"
},
"./result": {
"default": "./dist/result.mjs",
"import": "./dist/result.mjs",
Expand Down Expand Up @@ -71,7 +92,8 @@
],
"rules": {
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/member-ordering": "off"
"@typescript-eslint/member-ordering": "off",
"@typescript-eslint/strict-boolean-expressions": "off"
}
},
"scripts": {
Expand Down
96 changes: 96 additions & 0 deletions src/exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
export abstract class TaggedError extends TypeError {
abstract readonly _tag: string;

constructor(message?: string) {
super(message);
this.name = this.constructor.name;
}
}

// -----------------------------
// ---MARK: COMMON EXCEPTIONS---
// -----------------------------

export class UnwrapError extends TaggedError {
readonly _tag = 'UnwrapError';

constructor(type: 'Option' | 'Result' | 'ResultError') {
switch (type) {
case 'Option':
super('called "Option.unwrap()" on a "None" value');

Check failure on line 20 in src/exceptions.ts

View workflow job for this annotation

GitHub Actions / 🏗️ Build package

A 'super' call must be a root-level statement within a constructor of a derived class that contains initialized properties, parameter properties, or private identifiers.
break;
case 'Result':
super('called "Result.unwrap()" on an "Error" value');
break;
case 'ResultError':
super('called "Result.unwrapError()" on an "Ok" value');
break;
default: {
const _: never = type;
throw new Error(`invalid value passed to UnwrapError: "${_}"`);
}
}
}
}

// ---MARK: OPTION EXCEPTIONS---

export class UnexpectedOptionException extends TaggedError {
readonly _tag = 'UnexpectedOption';
}

// ---MARK: RESULT EXCEPTIONS---

export class UnknownError extends TaggedError {
readonly _tag = 'UnknownError';

readonly thrownError: unknown;

constructor(error: unknown) {
let message: string | undefined;
let stack: string | undefined;
let cause: unknown;

if (error instanceof Error) {
message = error.message;
stack = error.stack;
cause = error.cause;
} else {
message = typeof error === 'string' ? error : JSON.stringify(error);
}

super(message);
this.thrownError = error;

if (stack != null) {
this.stack = stack;
}

if (cause != null) {
this.cause = cause;
}
}
}

export class MissingValueError extends TaggedError {
readonly _tag = 'MissingValue';
}

export class FailedPredicateError<T> extends TaggedError {
readonly _tag = 'FailedPredicate';

constructor(readonly value: T) {
super('Predicate not fulfilled for Result value');
}
}

export class UnexpectedResultError extends TaggedError {
readonly _tag = 'UnexpectedResultError';

constructor(
override readonly cause: string,
readonly value: unknown,
) {
super('Expected Result to be "Ok", but it was "Error"');
}
}
14 changes: 14 additions & 0 deletions src/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* Returns the provided value.
*
* @example
* ```ts
* import { identity } from 'funkcia/functions';
*
* // Output: 10
* const result = identity(10);
* ```
*/
export function identity<T>(value: T): T {
return value;
}
28 changes: 3 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,3 @@
import * as A from './array';
import * as N from './number';
import * as O from './option';
import * as P from './predicate';
import * as R from './result';
import * as S from './string';

export * from './functions';
export {
A,
N,
O,
P,
R,
S,
A as array,
N as number,
O as option,
P as predicate,
R as result,
S as string,
};

export type { AsyncOption, None, Option, Some } from './option';
export type { AsyncResult, Err, Ok, Result } from './result';
export { Option } from './option';
export { Result } from './result';
export * from './types';
3 changes: 3 additions & 0 deletions src/internals/equality.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function refEquality<A>(a: A, b: A): boolean {
return a === b;
}
1 change: 1 addition & 0 deletions src/internals/inspect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const INSPECT_SYMBOL = Symbol.for('nodejs.util.inspect.custom');
3 changes: 3 additions & 0 deletions src/internals/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type Falsy = false | '' | 0 | 0n | null | undefined;

export type Task<A> = () => Promise<A>;
25 changes: 25 additions & 0 deletions src/json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Result } from './result';

export class SafeJSON {
/**
* Converts a JavaScript Object Notation (JSON) string into an object.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
*/
static parse = Result.produce<
Parameters<typeof JSON.parse>,
unknown,
SyntaxError
>(JSON.parse, (e) => e as SyntaxError);

/**
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
*/
static stringify = Result.produce<
Parameters<typeof JSON.stringify>,
unknown,
TypeError
>(JSON.stringify, (e) => e as TypeError);
}
Loading

0 comments on commit 0d3c217

Please sign in to comment.