Skip to content

Commit 0d3c217

Browse files
committed
feat: create Option and Result types
1 parent 403926c commit 0d3c217

20 files changed

+3147
-1402
lines changed

.changeset/chatty-falcons-arrive.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'funkcia': minor
3+
---
4+
5+
Create `Option` and `Result` types

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
</p>
2121

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

package.json

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,20 @@
2727
"types": "./dist/index.d.ts",
2828
"module": "./dist/index.mjs"
2929
},
30+
"./exceptions": {
31+
"default": "./dist/exceptions.mjs",
32+
"import": "./dist/exceptions.mjs",
33+
"require": "./dist/exceptions.js",
34+
"types": "./dist/exceptions.d.ts",
35+
"module": "./dist/exceptions.mjs"
36+
},
37+
"./functions": {
38+
"default": "./dist/functions.mjs",
39+
"import": "./dist/functions.mjs",
40+
"require": "./dist/functions.js",
41+
"types": "./dist/functions.d.ts",
42+
"module": "./dist/functions.mjs"
43+
},
3044
"./json": {
3145
"default": "./dist/json.mjs",
3246
"import": "./dist/json.mjs",
@@ -41,6 +55,13 @@
4155
"types": "./dist/option.d.ts",
4256
"module": "./dist/option.mjs"
4357
},
58+
"./predicate": {
59+
"default": "./dist/predicate.mjs",
60+
"import": "./dist/predicate.mjs",
61+
"require": "./dist/predicate.js",
62+
"types": "./dist/predicate.d.ts",
63+
"module": "./dist/predicate.mjs"
64+
},
4465
"./result": {
4566
"default": "./dist/result.mjs",
4667
"import": "./dist/result.mjs",
@@ -71,7 +92,8 @@
7192
],
7293
"rules": {
7394
"@typescript-eslint/no-non-null-assertion": "off",
74-
"@typescript-eslint/member-ordering": "off"
95+
"@typescript-eslint/member-ordering": "off",
96+
"@typescript-eslint/strict-boolean-expressions": "off"
7597
}
7698
},
7799
"scripts": {

src/exceptions.ts

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
export abstract class TaggedError extends TypeError {
2+
abstract readonly _tag: string;
3+
4+
constructor(message?: string) {
5+
super(message);
6+
this.name = this.constructor.name;
7+
}
8+
}
9+
10+
// -----------------------------
11+
// ---MARK: COMMON EXCEPTIONS---
12+
// -----------------------------
13+
14+
export class UnwrapError extends TaggedError {
15+
readonly _tag = 'UnwrapError';
16+
17+
constructor(type: 'Option' | 'Result' | 'ResultError') {
18+
switch (type) {
19+
case 'Option':
20+
super('called "Option.unwrap()" on a "None" value');
21+
break;
22+
case 'Result':
23+
super('called "Result.unwrap()" on an "Error" value');
24+
break;
25+
case 'ResultError':
26+
super('called "Result.unwrapError()" on an "Ok" value');
27+
break;
28+
default: {
29+
const _: never = type;
30+
throw new Error(`invalid value passed to UnwrapError: "${_}"`);
31+
}
32+
}
33+
}
34+
}
35+
36+
// ---MARK: OPTION EXCEPTIONS---
37+
38+
export class UnexpectedOptionException extends TaggedError {
39+
readonly _tag = 'UnexpectedOption';
40+
}
41+
42+
// ---MARK: RESULT EXCEPTIONS---
43+
44+
export class UnknownError extends TaggedError {
45+
readonly _tag = 'UnknownError';
46+
47+
readonly thrownError: unknown;
48+
49+
constructor(error: unknown) {
50+
let message: string | undefined;
51+
let stack: string | undefined;
52+
let cause: unknown;
53+
54+
if (error instanceof Error) {
55+
message = error.message;
56+
stack = error.stack;
57+
cause = error.cause;
58+
} else {
59+
message = typeof error === 'string' ? error : JSON.stringify(error);
60+
}
61+
62+
super(message);
63+
this.thrownError = error;
64+
65+
if (stack != null) {
66+
this.stack = stack;
67+
}
68+
69+
if (cause != null) {
70+
this.cause = cause;
71+
}
72+
}
73+
}
74+
75+
export class MissingValueError extends TaggedError {
76+
readonly _tag = 'MissingValue';
77+
}
78+
79+
export class FailedPredicateError<T> extends TaggedError {
80+
readonly _tag = 'FailedPredicate';
81+
82+
constructor(readonly value: T) {
83+
super('Predicate not fulfilled for Result value');
84+
}
85+
}
86+
87+
export class UnexpectedResultError extends TaggedError {
88+
readonly _tag = 'UnexpectedResultError';
89+
90+
constructor(
91+
override readonly cause: string,
92+
readonly value: unknown,
93+
) {
94+
super('Expected Result to be "Ok", but it was "Error"');
95+
}
96+
}

src/functions.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Returns the provided value.
3+
*
4+
* @example
5+
* ```ts
6+
* import { identity } from 'funkcia/functions';
7+
*
8+
* // Output: 10
9+
* const result = identity(10);
10+
* ```
11+
*/
12+
export function identity<T>(value: T): T {
13+
return value;
14+
}

src/index.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,3 @@
1-
import * as A from './array';
2-
import * as N from './number';
3-
import * as O from './option';
4-
import * as P from './predicate';
5-
import * as R from './result';
6-
import * as S from './string';
7-
8-
export * from './functions';
9-
export {
10-
A,
11-
N,
12-
O,
13-
P,
14-
R,
15-
S,
16-
A as array,
17-
N as number,
18-
O as option,
19-
P as predicate,
20-
R as result,
21-
S as string,
22-
};
23-
24-
export type { AsyncOption, None, Option, Some } from './option';
25-
export type { AsyncResult, Err, Ok, Result } from './result';
1+
export { Option } from './option';
2+
export { Result } from './result';
3+
export * from './types';

src/internals/equality.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function refEquality<A>(a: A, b: A): boolean {
2+
return a === b;
3+
}

src/internals/inspect.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const INSPECT_SYMBOL = Symbol.for('nodejs.util.inspect.custom');

src/internals/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type Falsy = false | '' | 0 | 0n | null | undefined;
2+
3+
export type Task<A> = () => Promise<A>;

src/json.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Result } from './result';
2+
3+
export class SafeJSON {
4+
/**
5+
* Converts a JavaScript Object Notation (JSON) string into an object.
6+
*
7+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
8+
*/
9+
static parse = Result.produce<
10+
Parameters<typeof JSON.parse>,
11+
unknown,
12+
SyntaxError
13+
>(JSON.parse, (e) => e as SyntaxError);
14+
15+
/**
16+
* Converts a JavaScript value to a JavaScript Object Notation (JSON) string.
17+
*
18+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
19+
*/
20+
static stringify = Result.produce<
21+
Parameters<typeof JSON.stringify>,
22+
unknown,
23+
TypeError
24+
>(JSON.stringify, (e) => e as TypeError);
25+
}

0 commit comments

Comments
 (0)