@grahlnn/fn 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +21 -0
- package/dist/comb.d.ts +13 -0
- package/dist/comb.d.ts.map +1 -0
- package/dist/comb.js +25 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/matchable.d.ts +83 -0
- package/dist/matchable.d.ts.map +1 -0
- package/dist/matchable.js +125 -0
- package/dist/result.d.ts +38 -0
- package/dist/result.d.ts.map +1 -0
- package/dist/result.js +107 -0
- package/package.json +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 matchable contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# matchable
|
|
2
|
+
|
|
3
|
+
Tiny, type-safe helpers for matching enums, unions, and objects.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
```bash
|
|
7
|
+
bun add @grahlnn/matchable
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
```ts
|
|
12
|
+
import { me } from "@grahlnn/matchable";
|
|
13
|
+
|
|
14
|
+
const status = me<"idle" | "loading" | "error">("loading");
|
|
15
|
+
|
|
16
|
+
const label = status.match({
|
|
17
|
+
idle: () => "Idle",
|
|
18
|
+
loading: () => "Loading",
|
|
19
|
+
_: () => "Error",
|
|
20
|
+
});
|
|
21
|
+
```
|
package/dist/comb.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const I: <T>(x: T) => T;
|
|
2
|
+
export declare const K: <T, U>(x: T) => (_?: U) => T;
|
|
3
|
+
export declare const S: <A, B, C>(x: (a: A) => (b: B) => C) => (y: (a: A) => B) => (z: A) => C;
|
|
4
|
+
export declare const B: <A, B, C>(g: (a: A) => B) => (f: (b: B) => C) => (x: A) => C;
|
|
5
|
+
export declare const C: <A, B, C>(f: (a: A) => (b: B) => C) => (b: B) => (a: A) => C;
|
|
6
|
+
export declare const W: <A, B>(f: (a: A) => (b: A) => B) => (x: A) => B;
|
|
7
|
+
export declare const T: <A, B>(x: A) => (f: (a: A) => B) => B;
|
|
8
|
+
export declare const call0: <R>(f: () => R) => R;
|
|
9
|
+
export declare function vec(): never[];
|
|
10
|
+
export declare function udf(): undefined;
|
|
11
|
+
export declare function set(): {};
|
|
12
|
+
export declare function nul(): null;
|
|
13
|
+
//# sourceMappingURL=comb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comb.d.ts","sourceRoot":"","sources":["../src/comb.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,CAAC,GAAI,CAAC,EAAE,GAAG,CAAC,KAAG,CAAM,CAAC;AACnC,eAAO,MAAM,CAAC,GACX,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MACV,IAAI,CAAC,KAAG,CACN,CAAC;AACN,eAAO,MAAM,CAAC,GACX,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MACjC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MACd,GAAG,CAAC,KAAG,CACI,CAAC;AAMf,eAAO,MAAM,CAAC,GACX,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MACvB,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MACd,GAAG,CAAC,KAAG,CACC,CAAC;AAEZ,eAAO,MAAM,CAAC,GACX,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MACjC,GAAG,CAAC,MACJ,GAAG,CAAC,KAAG,CACC,CAAC;AAEZ,eAAO,MAAM,CAAC,GACX,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAC9B,GAAG,CAAC,KAAG,CACC,CAAC;AAEZ,eAAO,MAAM,CAAC,GACX,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MACV,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAG,CACZ,CAAC;AAET,eAAO,MAAM,KAAK,GAAI,CAAC,EAAE,GAAG,MAAM,CAAC,KAAG,CAAQ,CAAC;AAE/C,wBAAgB,GAAG,YAElB;AAED,wBAAgB,GAAG,cAElB;AAED,wBAAgB,GAAG,OAElB;AAED,wBAAgB,GAAG,SAElB"}
|
package/dist/comb.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const I = (x) => x;
|
|
2
|
+
export const K = (x) => (_) => x;
|
|
3
|
+
export const S = (x) => (y) => (z) => x(z)(y(z));
|
|
4
|
+
// export const B =
|
|
5
|
+
// <A, B, C>(f: (b: B) => C) =>
|
|
6
|
+
// (g: (a: A) => B) =>
|
|
7
|
+
// (x: A): C =>
|
|
8
|
+
// f(g(x));
|
|
9
|
+
export const B = (g) => (f) => (x) => f(g(x));
|
|
10
|
+
export const C = (f) => (b) => (a) => f(a)(b);
|
|
11
|
+
export const W = (f) => (x) => f(x)(x);
|
|
12
|
+
export const T = (x) => (f) => f(x);
|
|
13
|
+
export const call0 = (f) => f();
|
|
14
|
+
export function vec() {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
export function udf() {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
export function set() {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
export function nul() {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
type Nil = null | undefined;
|
|
2
|
+
type ToRecordKey<T> = T extends boolean ? `${T}` : T extends string | number ? T : never;
|
|
3
|
+
type Handler<R, V = any> = {
|
|
4
|
+
bivarianceHack(v: V): R;
|
|
5
|
+
}["bivarianceHack"];
|
|
6
|
+
type RequireAll<T extends string | number | boolean, R> = Record<ToRecordKey<T>, Handler<R>>;
|
|
7
|
+
type RequireDefault<T extends string | number | boolean, R> = (Partial<Record<ToRecordKey<T>, Handler<R>>> & {
|
|
8
|
+
_: Handler<R>;
|
|
9
|
+
}) | RequireAll<T, R>;
|
|
10
|
+
type MatchableEnum<T extends string | number | boolean> = {
|
|
11
|
+
__recognizer__: "enum";
|
|
12
|
+
value: T | Nil;
|
|
13
|
+
value_or(fallback: T): T;
|
|
14
|
+
value_or<F>(fallback: F): T | F;
|
|
15
|
+
value_or_else(fallback: () => T): T;
|
|
16
|
+
value_or_else<F>(fallback: () => F): T | F;
|
|
17
|
+
match<R>(handlers: RequireDefault<T, R>): R;
|
|
18
|
+
is(v: T): v is T;
|
|
19
|
+
not(v: T): v is T;
|
|
20
|
+
in(v: Array<T>): boolean;
|
|
21
|
+
not_in(v: Array<T>): boolean;
|
|
22
|
+
into(): <R>(fn: Handler<R>) => R | null;
|
|
23
|
+
catch<L extends Array<T>>(...arr_branch: L): <R>(fn: Handler<R>) => R | null;
|
|
24
|
+
};
|
|
25
|
+
type VariantTag<T> = T extends any ? keyof T : never;
|
|
26
|
+
type FullHandlers<T, R> = {
|
|
27
|
+
[K in VariantTag<T>]: (payload: Extract<T, Record<K, any>>[K]) => R;
|
|
28
|
+
};
|
|
29
|
+
type DefaultHandlers<T, R> = (Partial<FullHandlers<T, R>> & {
|
|
30
|
+
_: (p: T[keyof T]) => R;
|
|
31
|
+
}) | FullHandlers<T, R>;
|
|
32
|
+
type MatchableUnion<T extends Record<string, any>> = {
|
|
33
|
+
[K in VariantTag<T>]: {
|
|
34
|
+
__recognizer__: "union";
|
|
35
|
+
tag: K;
|
|
36
|
+
value: Extract<T, Record<K, any>>[K] | Nil;
|
|
37
|
+
match<R>(h: DefaultHandlers<T, R>): R;
|
|
38
|
+
is<L extends VariantTag<T>>(l: L): this is Extract<MatchableUnion<T>, {
|
|
39
|
+
tag: L;
|
|
40
|
+
}>;
|
|
41
|
+
not<L extends VariantTag<T>>(l: L): this is Extract<MatchableUnion<T>, {
|
|
42
|
+
tag: L;
|
|
43
|
+
}>;
|
|
44
|
+
in(arr: Array<VariantTag<T>>): boolean;
|
|
45
|
+
not_in(arr: Array<VariantTag<T>>): boolean;
|
|
46
|
+
into(): <R>(fn: (payload: Extract<T, Record<K, any>>[K]) => R) => R | null;
|
|
47
|
+
catch<K extends Array<VariantTag<T>>>(...arr_branch: K): <R>(fn: (payload: Extract<T, Record<K[number], any>>[K[number]]) => R) => R | null;
|
|
48
|
+
};
|
|
49
|
+
}[VariantTag<T>];
|
|
50
|
+
declare const emptyMatchable: {
|
|
51
|
+
__recognizer__: string;
|
|
52
|
+
value: null;
|
|
53
|
+
value_or: <T>(x: T) => T;
|
|
54
|
+
value_or_else: <R>(f: () => R) => R;
|
|
55
|
+
match: (_?: unknown) => null;
|
|
56
|
+
is: (_?: unknown) => boolean;
|
|
57
|
+
not: (_?: unknown) => boolean;
|
|
58
|
+
catch: (_?: unknown) => (_?: unknown) => null;
|
|
59
|
+
not_in: (_?: unknown) => boolean;
|
|
60
|
+
into: (_?: unknown) => (_?: unknown) => null;
|
|
61
|
+
in: (_?: unknown) => boolean;
|
|
62
|
+
};
|
|
63
|
+
type EmptyMatchable = typeof emptyMatchable;
|
|
64
|
+
type MatchableObj<T extends Record<string, any>> = {
|
|
65
|
+
__recognizer__: "object";
|
|
66
|
+
value: T;
|
|
67
|
+
into(): <R>(fn: (props: T) => R) => R | null;
|
|
68
|
+
catch<KS extends readonly (keyof T)[]>(...keys: KS): <R>(fn: (props: {
|
|
69
|
+
[P in KS[number]]: NonNullable<T[P]>;
|
|
70
|
+
}) => R) => R | null;
|
|
71
|
+
};
|
|
72
|
+
type IsUnion<T, U = T> = T extends any ? [U] extends [T] ? false : true : never;
|
|
73
|
+
type IsSingleKeyObj<O extends Record<string, any>> = IsUnion<keyof O> extends true ? false : true;
|
|
74
|
+
type IsUnionOfSingleKey<U extends Record<string, any>> = (U extends any ? IsSingleKeyObj<U> : never) extends false ? false : true;
|
|
75
|
+
type MatchableError<T> = {
|
|
76
|
+
__matchable_error__: `❌ match() only supports string | number | boolean | Record<string, any>, but got: ${Extract<T, string | number | bigint | boolean | null | undefined>}`;
|
|
77
|
+
value: T;
|
|
78
|
+
};
|
|
79
|
+
type _MatchableCore<T> = [T] extends [string | number | boolean] ? MatchableEnum<T> : [T] extends [Record<string, any>] ? IsUnionOfSingleKey<T> extends true ? MatchableUnion<T> : MatchableObj<T> : MatchableError<T>;
|
|
80
|
+
export type ME<T> = ([T] extends [null] ? EmptyMatchable : never) | ([T] extends [undefined] ? EmptyMatchable : never) | _MatchableCore<NonNullable<T>>;
|
|
81
|
+
export declare function me<T extends string | number | boolean | Record<string, any> | null | undefined>(value: T): ME<T>;
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=matchable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"matchable.d.ts","sourceRoot":"","sources":["../src/matchable.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import isEqual from "fast-deep-equal";
|
|
2
|
+
import { call0, I, K } from "./comb.js";
|
|
3
|
+
function matchableEnum(value) {
|
|
4
|
+
return {
|
|
5
|
+
__recognizer__: "enum",
|
|
6
|
+
value,
|
|
7
|
+
value_or(fallback) {
|
|
8
|
+
return value ?? fallback;
|
|
9
|
+
},
|
|
10
|
+
value_or_else(fallback) {
|
|
11
|
+
return value ?? fallback();
|
|
12
|
+
},
|
|
13
|
+
match(h) {
|
|
14
|
+
// 运行时:boolean 转成 "true"/"false"
|
|
15
|
+
const k = (typeof value === "boolean" ? String(value) : value);
|
|
16
|
+
const handler = (k in h ? h[k] : h._);
|
|
17
|
+
return handler(value);
|
|
18
|
+
},
|
|
19
|
+
is: (v) => value === v,
|
|
20
|
+
not: (v) => value !== v,
|
|
21
|
+
in(arr) {
|
|
22
|
+
return value != null ? arr.includes(value) : false;
|
|
23
|
+
},
|
|
24
|
+
not_in(arr) {
|
|
25
|
+
return !this.in(arr);
|
|
26
|
+
},
|
|
27
|
+
into() {
|
|
28
|
+
const self = this;
|
|
29
|
+
return function (fn) {
|
|
30
|
+
return fn(self.value);
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
catch(...arr_branch) {
|
|
34
|
+
const self = this;
|
|
35
|
+
return function (fn) {
|
|
36
|
+
return self.in(arr_branch) ? fn(self.value) : null;
|
|
37
|
+
};
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function matchableUnion(value) {
|
|
42
|
+
const tag = Object.keys(value)[0];
|
|
43
|
+
const payload = value[tag];
|
|
44
|
+
return {
|
|
45
|
+
__recognizer__: "union",
|
|
46
|
+
tag,
|
|
47
|
+
value: payload,
|
|
48
|
+
match(h) {
|
|
49
|
+
const fn = h[tag] ??
|
|
50
|
+
h._;
|
|
51
|
+
return fn(payload);
|
|
52
|
+
},
|
|
53
|
+
is(l) {
|
|
54
|
+
return l === tag;
|
|
55
|
+
},
|
|
56
|
+
not(l) {
|
|
57
|
+
return l !== tag;
|
|
58
|
+
},
|
|
59
|
+
in(arr) {
|
|
60
|
+
return arr.includes(tag);
|
|
61
|
+
},
|
|
62
|
+
not_in(arr) {
|
|
63
|
+
return !this.in(arr);
|
|
64
|
+
},
|
|
65
|
+
into() {
|
|
66
|
+
const self = this;
|
|
67
|
+
return function (fn) {
|
|
68
|
+
return self.value && !isEqual(self.value, []) ? fn(self.value) : null;
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
catch(...arr_branch) {
|
|
72
|
+
const self = this;
|
|
73
|
+
return function (fn) {
|
|
74
|
+
return self.in(arr_branch) && self.value && !isEqual(self.value, [])
|
|
75
|
+
? fn(self.value)
|
|
76
|
+
: null;
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const emptyMatchable = {
|
|
82
|
+
__recognizer__: "empty",
|
|
83
|
+
value: null,
|
|
84
|
+
value_or: I,
|
|
85
|
+
value_or_else: call0,
|
|
86
|
+
match: K(null),
|
|
87
|
+
is: K(false),
|
|
88
|
+
not: K(false),
|
|
89
|
+
catch: K(K(null)),
|
|
90
|
+
not_in: K(false),
|
|
91
|
+
into: K(K(null)),
|
|
92
|
+
in: K(false),
|
|
93
|
+
};
|
|
94
|
+
function matchableObj(value) {
|
|
95
|
+
return {
|
|
96
|
+
__recognizer__: "object",
|
|
97
|
+
value,
|
|
98
|
+
into() {
|
|
99
|
+
return (fn) => {
|
|
100
|
+
return fn(value);
|
|
101
|
+
};
|
|
102
|
+
},
|
|
103
|
+
catch(...keys) {
|
|
104
|
+
return (fn) => {
|
|
105
|
+
if (keys.some((k) => !value[k]))
|
|
106
|
+
return null;
|
|
107
|
+
const picked = {};
|
|
108
|
+
for (const k of keys)
|
|
109
|
+
picked[k] = value[k];
|
|
110
|
+
return fn(picked);
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function me(value) {
|
|
116
|
+
if (value == null)
|
|
117
|
+
return emptyMatchable;
|
|
118
|
+
if (["string", "number", "boolean"].includes(typeof value))
|
|
119
|
+
return matchableEnum(value);
|
|
120
|
+
// ⚠️ 如果想用 Union 模式,必须“单键封装”,因为等同于rust的enum:
|
|
121
|
+
if (Object.keys(value).length === 1)
|
|
122
|
+
return matchableUnion(value);
|
|
123
|
+
// 否则走对象模式
|
|
124
|
+
return matchableObj(value);
|
|
125
|
+
}
|
package/dist/result.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
type Ok<T> = {
|
|
2
|
+
ok: true;
|
|
3
|
+
value: T;
|
|
4
|
+
};
|
|
5
|
+
type Err<E> = {
|
|
6
|
+
ok: false;
|
|
7
|
+
error: E;
|
|
8
|
+
};
|
|
9
|
+
type RawResult<T, E = Error> = Ok<T> | Err<E>;
|
|
10
|
+
export declare const Ok: <T, E = never>(value: T) => Result<T, E>;
|
|
11
|
+
export declare const Err: <E, T = never>(error: E) => Result<T, E>;
|
|
12
|
+
export declare class Result<T, E = Error> {
|
|
13
|
+
private readonly result;
|
|
14
|
+
constructor(result: RawResult<T, E>);
|
|
15
|
+
match<U>(handlers: {
|
|
16
|
+
Ok: (value: T) => U;
|
|
17
|
+
Err: (error: E) => U;
|
|
18
|
+
}): U;
|
|
19
|
+
answer(): T | E;
|
|
20
|
+
name(): "Ok" | "Err";
|
|
21
|
+
isOk(): this is Result<T, E>;
|
|
22
|
+
isErr(): this is Result<never, E>;
|
|
23
|
+
unwrap(): T;
|
|
24
|
+
unwrap_err(): E;
|
|
25
|
+
unwrap_or(): T | undefined;
|
|
26
|
+
unwrap_or(defaultValue: NonNullable<T>): T;
|
|
27
|
+
or_else(fn: (error: E) => T): T;
|
|
28
|
+
map<U>(fn: (value: T) => U): Result<U, E>;
|
|
29
|
+
map_err<F>(fn: (error: E) => F): Result<T, F>;
|
|
30
|
+
to<U>(fn: (value: T) => Result<U, E>): Result<U, E>;
|
|
31
|
+
tap(fn: (value: T) => void): this;
|
|
32
|
+
tap_err(fn: (error: E) => void): this;
|
|
33
|
+
get raw(): RawResult<T, E>;
|
|
34
|
+
}
|
|
35
|
+
export declare function futry<T, E = Error>(promise: Promise<T>, mapErr?: (err: unknown) => E): Promise<Result<T, E>>;
|
|
36
|
+
export declare const tap: <A, E>(fn: (a: A) => void) => (r: Result<A, E>) => Result<A, E>;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=result.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../src/result.ts"],"names":[],"mappings":"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"}
|
package/dist/result.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export const Ok = (value) => new Result({ ok: true, value });
|
|
2
|
+
export const Err = (error) => new Result({ ok: false, error });
|
|
3
|
+
export class Result {
|
|
4
|
+
result;
|
|
5
|
+
constructor(result) {
|
|
6
|
+
this.result = result;
|
|
7
|
+
}
|
|
8
|
+
match(handlers) {
|
|
9
|
+
return this.result.ok
|
|
10
|
+
? handlers.Ok(this.result.value)
|
|
11
|
+
: handlers.Err(this.result.error);
|
|
12
|
+
}
|
|
13
|
+
answer() {
|
|
14
|
+
return this.result.ok ? this.result.value : this.result.error;
|
|
15
|
+
}
|
|
16
|
+
name() {
|
|
17
|
+
return this.result.ok ? "Ok" : "Err";
|
|
18
|
+
}
|
|
19
|
+
isOk() {
|
|
20
|
+
return this.result.ok;
|
|
21
|
+
}
|
|
22
|
+
isErr() {
|
|
23
|
+
return !this.result.ok;
|
|
24
|
+
}
|
|
25
|
+
// 如果成功,返回内部值;失败则抛出错误
|
|
26
|
+
unwrap() {
|
|
27
|
+
return this.match({
|
|
28
|
+
Ok: (value) => value,
|
|
29
|
+
Err: (error) => {
|
|
30
|
+
console.error("unwrap error:", error);
|
|
31
|
+
throw new Error(`${error}`);
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
// 如果失败,返回内部错误;成功则抛出错误
|
|
36
|
+
unwrap_err() {
|
|
37
|
+
return this.match({
|
|
38
|
+
Ok: (value) => {
|
|
39
|
+
console.error("unwrap_err value:", value);
|
|
40
|
+
throw new Error(`${value}`);
|
|
41
|
+
},
|
|
42
|
+
Err: (error) => error,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
unwrap_or(defaultValue) {
|
|
46
|
+
return this.match({
|
|
47
|
+
Ok: (value) => value,
|
|
48
|
+
Err: () => defaultValue, // defaultValue 存在时命中上面的重载
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// 如果成功,则返回内部值,否则调用传入函数
|
|
52
|
+
or_else(fn) {
|
|
53
|
+
return this.match({
|
|
54
|
+
Ok: (value) => value,
|
|
55
|
+
Err: (error) => fn(error),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
// 成功时对内部值做转换,失败时保持原错误
|
|
59
|
+
map(fn) {
|
|
60
|
+
return this.match({
|
|
61
|
+
Ok: (value) => Ok(fn(value)),
|
|
62
|
+
Err: (error) => Err(error),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// 如果失败,则对错误做转换;成功时保持内部值
|
|
66
|
+
map_err(fn) {
|
|
67
|
+
return this.match({
|
|
68
|
+
Ok: (value) => Ok(value),
|
|
69
|
+
Err: (error) => Err(fn(error)),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// 链式调用:如果成功,则调用传入函数并返回新 Result;失败时直接传递错误
|
|
73
|
+
to(fn) {
|
|
74
|
+
return this.match({
|
|
75
|
+
Ok: (value) => fn(value),
|
|
76
|
+
Err: (error) => Err(error),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// 如果成功,则调用传入函数
|
|
80
|
+
tap(fn) {
|
|
81
|
+
if (this.isOk())
|
|
82
|
+
fn(this.unwrap());
|
|
83
|
+
else
|
|
84
|
+
console.error("tap error:", this.unwrap_err());
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
// 如果失败,则调用传入函数
|
|
88
|
+
tap_err(fn) {
|
|
89
|
+
if (this.isErr())
|
|
90
|
+
fn(this.unwrap_err());
|
|
91
|
+
return this;
|
|
92
|
+
}
|
|
93
|
+
// 提供原始数据访问(如果有需要的话)
|
|
94
|
+
get raw() {
|
|
95
|
+
return this.result;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export async function futry(promise, mapErr) {
|
|
99
|
+
try {
|
|
100
|
+
const value = await promise;
|
|
101
|
+
return Ok(value);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return Err(mapErr ? mapErr(err) : err);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
export const tap = (fn) => (r) => r.tap(fn);
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grahlnn/fn",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Ergonomic functional programming helpers.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/index.js",
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p tsconfig.build.json",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"typescript": "^5.9.3"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"fast-deep-equal": "^3.1.3"
|
|
27
|
+
}
|
|
28
|
+
}
|