@terrygonguet/utils 0.0.1 → 0.0.2
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/package.json +20 -5
- package/src/async.test.js +9 -0
- package/src/async.ts +3 -0
- package/src/functional/index.test.js +11 -0
- package/src/functional/index.ts +7 -0
- package/src/functional/maybe.test.js +85 -0
- package/src/functional/maybe.ts +75 -0
- package/src/functional/result.test.js +150 -0
- package/src/functional/result.ts +96 -0
- package/src/main.test.js +43 -0
- package/src/main.ts +16 -1
- package/tsconfig.json +3 -3
- package/types/async.d.ts +1 -0
- package/types/functional/index.d.ts +4 -0
- package/types/functional/maybe.d.ts +20 -0
- package/types/functional/result.d.ts +28 -0
- package/types/main.d.ts +4 -0
- package/vite.config.js +15 -13
package/package.json
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@terrygonguet/utils",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
7
7
|
"build": "tsc && vite build",
|
|
8
|
-
"
|
|
8
|
+
"test": "vitest test --ui --watch --update",
|
|
9
|
+
"coverage": "vitest run --coverage"
|
|
9
10
|
},
|
|
10
11
|
"devDependencies": {
|
|
12
|
+
"@vitest/ui": "^0.32.0",
|
|
13
|
+
"prettier": "^2.8.8",
|
|
11
14
|
"typescript": "^5.0.2",
|
|
12
|
-
"vite": "^4.3.9"
|
|
15
|
+
"vite": "^4.3.9",
|
|
16
|
+
"vitest": "^0.32.0"
|
|
13
17
|
},
|
|
14
18
|
"exports": {
|
|
15
19
|
".": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"import": "./
|
|
20
|
+
"types": "./types/main.js",
|
|
21
|
+
"import": "./dist/main.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./async": {
|
|
24
|
+
"types": "./types/async.js",
|
|
25
|
+
"import": "./dist/async.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./functional": {
|
|
28
|
+
"types": "./types/functional/index.d.ts",
|
|
29
|
+
"import": "./dist/functional.js"
|
|
18
30
|
}
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"just-compose": "^2.3.0"
|
|
19
34
|
}
|
|
20
35
|
}
|
package/src/async.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { describe } from "vitest"
|
|
2
|
+
import { identity } from "./index.ts"
|
|
3
|
+
|
|
4
|
+
describe.concurrent("identity()", it => {
|
|
5
|
+
it("does nothing", ({ expect }) => {
|
|
6
|
+
expect(identity(5)).to.equal(5)
|
|
7
|
+
expect(identity("test")).to.equal("test")
|
|
8
|
+
const object = { prop: "value" }
|
|
9
|
+
expect(identity(object)).to.equal(object)
|
|
10
|
+
})
|
|
11
|
+
})
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe } from "vitest"
|
|
2
|
+
import { Some, None, Maybe } from "./maybe.ts"
|
|
3
|
+
|
|
4
|
+
describe.concurrent("Maybe", it => {
|
|
5
|
+
/** @type {Maybe<number>} */
|
|
6
|
+
const some = Some(5)
|
|
7
|
+
/** @type {Maybe<number>} */
|
|
8
|
+
const none = None
|
|
9
|
+
|
|
10
|
+
it("isSome()", ({ expect }) => {
|
|
11
|
+
expect(some.isSome()).to.be.true
|
|
12
|
+
expect(none.isSome()).to.be.false
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("isNone()", ({ expect }) => {
|
|
16
|
+
expect(some.isNone()).to.be.false
|
|
17
|
+
expect(none.isNone()).to.be.true
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("orDefault()", ({ expect }) => {
|
|
21
|
+
expect(some.orDefault(15)).to.equal(5)
|
|
22
|
+
expect(none.orDefault(15)).to.equal(15)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("map()", ({ expect }) => {
|
|
26
|
+
const mappedSome = some.map(n => n * 2)
|
|
27
|
+
expect(mappedSome.isSome()).to.be.true
|
|
28
|
+
expect(mappedSome.value).to.equal(10)
|
|
29
|
+
const mappedNone = none.map(n => n * 2)
|
|
30
|
+
expect(mappedNone.isNone()).to.be.true
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it("flatMap()", ({ expect }) => {
|
|
34
|
+
const some1 = some.flatMap(n => Some(n * 2))
|
|
35
|
+
expect(some1.isSome()).to.be.true
|
|
36
|
+
expect(some1.value).to.equal(10)
|
|
37
|
+
const none1 = some.flatMap(n => None)
|
|
38
|
+
expect(none1.isNone()).to.be.true
|
|
39
|
+
const some2 = none.flatMap(n => Some(n * 2))
|
|
40
|
+
expect(some2.isSome()).to.be.false
|
|
41
|
+
const none2 = some.flatMap(n => None)
|
|
42
|
+
expect(none2.isNone()).to.be.true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
it("toJSON()", ({ expect }) => {
|
|
46
|
+
expect(some.toJSON()).toMatchInlineSnapshot()
|
|
47
|
+
expect(none.toJSON()).toMatchInlineSnapshot()
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe.concurrent("Maybe.JSONReviver()", it => {
|
|
51
|
+
const $_kind = "@terrygonguet/utils/functional/maybe"
|
|
52
|
+
const $_variant_Some = "@terrygonguet/utils/functional/maybe/Some"
|
|
53
|
+
const $_variant_None = "@terrygonguet/utils/functional/maybe/None"
|
|
54
|
+
|
|
55
|
+
it("does nothing to normal objects", ({ expect }) => {
|
|
56
|
+
const json1 = `{"prop":"value"}`
|
|
57
|
+
const obj1 = JSON.parse(json1, Maybe.JSONReviver)
|
|
58
|
+
expect(obj1).to.deep.equal({ prop: "value" })
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it("revives a Some", ({ expect }) => {
|
|
62
|
+
const json2 = `{"prop":"value","some":{"$_kind":"${$_kind}","$_variant":"${$_variant_Some}","value":5}}`
|
|
63
|
+
const obj2 = JSON.parse(json2, Maybe.JSONReviver)
|
|
64
|
+
expect(obj2.prop).to.equal("value")
|
|
65
|
+
expect(obj2.some.isSome()).to.be.true
|
|
66
|
+
expect(obj2.some.value).to.equal(5)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it("revives a None", ({ expect }) => {
|
|
70
|
+
const json3 = `{"prop":"value","none":{"$_kind":"${$_kind}","$_variant":"${$_variant_None}"}}`
|
|
71
|
+
const obj3 = JSON.parse(json3, Maybe.JSONReviver)
|
|
72
|
+
expect(obj3.prop).to.equal("value")
|
|
73
|
+
expect(obj3.none).to.equal(None)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it("does a round trip", ({ expect }) => {
|
|
77
|
+
const json4 = JSON.stringify({ prop: "value", some: Some(5), none: None })
|
|
78
|
+
const obj4 = JSON.parse(json4, Maybe.JSONReviver)
|
|
79
|
+
expect(obj4.prop).to.equal("value")
|
|
80
|
+
expect(obj4.some.isSome()).to.be.true
|
|
81
|
+
expect(obj4.some.value).to.equal(5)
|
|
82
|
+
expect(obj4.none).to.equal(None)
|
|
83
|
+
})
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { identity } from "./index.ts"
|
|
2
|
+
|
|
3
|
+
interface API<T> {
|
|
4
|
+
isSome(this: Maybe<T>): this is Some<T>
|
|
5
|
+
isNone(this: Maybe<T>): this is None
|
|
6
|
+
orDefault(this: Maybe<T>, defaultValue: T): T
|
|
7
|
+
map<U>(this: Maybe<T>, f: (value: T) => U): Maybe<U>
|
|
8
|
+
flatMap<U>(this: Maybe<T>, f: (value: T) => Maybe<U>): Maybe<U>
|
|
9
|
+
toJSON(this: Maybe<T>): Object
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type Some<T> = { value: T } & API<T>
|
|
13
|
+
export type None = {} & API<never>
|
|
14
|
+
export type Maybe<T> = Some<T> | None
|
|
15
|
+
|
|
16
|
+
const $_kind = "@terrygonguet/utils/functional/maybe"
|
|
17
|
+
const $_variant_Some = "@terrygonguet/utils/functional/maybe/Some"
|
|
18
|
+
const $_variant_None = "@terrygonguet/utils/functional/maybe/None"
|
|
19
|
+
|
|
20
|
+
export function Some<T>(value: NonNullable<T>): Some<T> {
|
|
21
|
+
const SomePrototype: API<T> = {
|
|
22
|
+
isSome: () => true,
|
|
23
|
+
isNone: () => false,
|
|
24
|
+
orDefault(this: Some<T>) {
|
|
25
|
+
return this.value
|
|
26
|
+
},
|
|
27
|
+
map(this: Some<T>, f) {
|
|
28
|
+
return Some(f(this.value)!)
|
|
29
|
+
},
|
|
30
|
+
flatMap(this: Some<T>, f) {
|
|
31
|
+
return f(this.value)
|
|
32
|
+
},
|
|
33
|
+
toJSON(this: Some<T>) {
|
|
34
|
+
return { $_kind, $_variant: $_variant_Some, value: this.value }
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
return Object.create(SomePrototype, {
|
|
38
|
+
$_kind: { value: $_kind, enumerable: false, writable: false },
|
|
39
|
+
$_variant: { value: $_variant_Some, enumerable: false, writable: false },
|
|
40
|
+
value: { value, writable: false },
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const NonePrototype: API<never> = {
|
|
45
|
+
isSome: () => false,
|
|
46
|
+
isNone: () => true,
|
|
47
|
+
orDefault: identity,
|
|
48
|
+
map: () => None,
|
|
49
|
+
flatMap: () => None,
|
|
50
|
+
toJSON: () => ({ $_kind, $_variant: $_variant_None }),
|
|
51
|
+
}
|
|
52
|
+
export const None: None = Object.create(NonePrototype, {
|
|
53
|
+
$_kind: { value: $_kind, enumerable: false, writable: false },
|
|
54
|
+
$_variant: { value: $_variant_None, enumerable: false, writable: false },
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const Maybe = {
|
|
58
|
+
from<T>(value: T | undefined | null): Maybe<T> {
|
|
59
|
+
switch (value) {
|
|
60
|
+
case null:
|
|
61
|
+
case undefined:
|
|
62
|
+
return None
|
|
63
|
+
default:
|
|
64
|
+
return Some(value!)
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
JSONReviver(_key: string, value: any) {
|
|
68
|
+
if (value?.$_kind == $_kind) {
|
|
69
|
+
const $_variant = value?.$_variant
|
|
70
|
+
if ($_variant == $_variant_Some) return Some<unknown>(value?.value)
|
|
71
|
+
else if ($_variant == $_variant_None) return None
|
|
72
|
+
else return value
|
|
73
|
+
} else return value
|
|
74
|
+
},
|
|
75
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe } from "vitest"
|
|
2
|
+
import { Success, Failure, Result } from "./result.ts"
|
|
3
|
+
|
|
4
|
+
describe.concurrent("Result", it => {
|
|
5
|
+
/** @type {Result<number, string>} */
|
|
6
|
+
const success = Success(5)
|
|
7
|
+
/** @type {Result<number, string>} */
|
|
8
|
+
const failure = Failure("reason")
|
|
9
|
+
|
|
10
|
+
it("isSuccess()", ({ expect }) => {
|
|
11
|
+
expect(success.isSuccess()).to.be.true
|
|
12
|
+
expect(failure.isSuccess()).to.be.false
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it("isFailure()", ({ expect }) => {
|
|
16
|
+
expect(success.isFailure()).to.be.false
|
|
17
|
+
expect(failure.isFailure()).to.be.true
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it("map()", ({ expect }) => {
|
|
21
|
+
const mappedSuccess = success.map(n => n * 2)
|
|
22
|
+
expect(mappedSuccess.isSuccess()).to.be.true
|
|
23
|
+
expect(mappedSuccess.value).to.equal(10)
|
|
24
|
+
const mappedFailure = failure.map(n => n * 2)
|
|
25
|
+
expect(mappedFailure.isSuccess()).to.be.false
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it("flatMap()", ({ expect }) => {
|
|
29
|
+
const succ1 = success.flatMap(n => Success(n * 2))
|
|
30
|
+
expect(succ1.isSuccess()).to.be.true
|
|
31
|
+
expect(succ1.value).to.equal(10)
|
|
32
|
+
|
|
33
|
+
const fail1 = success.flatMap(n => Failure("fail"))
|
|
34
|
+
expect(fail1.isSuccess()).to.be.false
|
|
35
|
+
expect(fail1.reason).to.equal("fail")
|
|
36
|
+
|
|
37
|
+
const fail2 = failure.flatMap(n => Success(n * 2))
|
|
38
|
+
expect(fail2.isSuccess()).to.be.false
|
|
39
|
+
expect(fail2.reason).to.equal("reason")
|
|
40
|
+
|
|
41
|
+
const fail3 = failure.flatMap(n => Failure("fail"))
|
|
42
|
+
expect(fail3.isSuccess()).to.be.false
|
|
43
|
+
expect(fail3.reason).to.equal("reason")
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it("toJSON()", ({ expect }) => {
|
|
47
|
+
expect(success.toJSON()).toMatchInlineSnapshot(`
|
|
48
|
+
{
|
|
49
|
+
"$_kind": "@terrygonguet/utils/functional/result",
|
|
50
|
+
"$_variant": "@terrygonguet/utils/functional/result/Success",
|
|
51
|
+
"value": 5,
|
|
52
|
+
}
|
|
53
|
+
`)
|
|
54
|
+
expect(failure.toJSON()).toMatchInlineSnapshot(`
|
|
55
|
+
{
|
|
56
|
+
"$_kind": "@terrygonguet/utils/functional/result",
|
|
57
|
+
"$_variant": "@terrygonguet/utils/functional/result/Failure",
|
|
58
|
+
"reason": "reason",
|
|
59
|
+
}
|
|
60
|
+
`)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe.concurrent("Result.JSONReviver()", it => {
|
|
64
|
+
const $_kind = "@terrygonguet/utils/functional/result"
|
|
65
|
+
const $_variant_Success = "@terrygonguet/utils/functional/result/Success"
|
|
66
|
+
const $_variant_Failure = "@terrygonguet/utils/functional/result/Failure"
|
|
67
|
+
|
|
68
|
+
it("does nothing to normal objects", ({ expect }) => {
|
|
69
|
+
const json = `{"prop":"value"}`
|
|
70
|
+
const obj = JSON.parse(json, Result.JSONReviver)
|
|
71
|
+
expect(obj.prop).to.equal("value")
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it("revives a Success", ({ expect }) => {
|
|
75
|
+
const json = `{
|
|
76
|
+
"prop":"value",
|
|
77
|
+
"success": {
|
|
78
|
+
"$_kind": "@terrygonguet/utils/functional/result",
|
|
79
|
+
"$_variant": "@terrygonguet/utils/functional/result/Success",
|
|
80
|
+
"value": 5
|
|
81
|
+
}
|
|
82
|
+
}`
|
|
83
|
+
const obj = JSON.parse(json, Result.JSONReviver)
|
|
84
|
+
expect(obj.prop).to.equal("value")
|
|
85
|
+
expect(obj.success.isSuccess()).to.be.true
|
|
86
|
+
expect(obj.success.value).to.equal(5)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it("revives a Failure", ({ expect }) => {
|
|
90
|
+
const json = `{
|
|
91
|
+
"prop":"value",
|
|
92
|
+
"failure": {
|
|
93
|
+
"$_kind": "@terrygonguet/utils/functional/result",
|
|
94
|
+
"$_variant": "@terrygonguet/utils/functional/result/Failure",
|
|
95
|
+
"reason": "reason"
|
|
96
|
+
}
|
|
97
|
+
}`
|
|
98
|
+
const obj = JSON.parse(json, Result.JSONReviver)
|
|
99
|
+
expect(obj.prop).to.equal("value")
|
|
100
|
+
expect(obj.failure.isSuccess()).to.be.false
|
|
101
|
+
expect(obj.failure.reason).to.equal("reason")
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it("does a round trip", ({ expect }) => {
|
|
105
|
+
const json = JSON.stringify({ prop: "value", success, failure })
|
|
106
|
+
const obj = JSON.parse(json, Result.JSONReviver)
|
|
107
|
+
expect(obj.prop).to.equal("value")
|
|
108
|
+
expect(obj.success.isSuccess()).to.be.true
|
|
109
|
+
expect(obj.success.value).to.equal(5)
|
|
110
|
+
expect(obj.failure.isSuccess()).to.be.false
|
|
111
|
+
expect(obj.failure.reason).to.equal("reason")
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe("Result.try()", it => {
|
|
116
|
+
it("gets the value from the try function", ({ expect }) => {
|
|
117
|
+
const result = Result.try(() => {
|
|
118
|
+
const n = 5
|
|
119
|
+
return n * 2
|
|
120
|
+
}).exec()
|
|
121
|
+
expect(result.isSuccess()).to.be.true
|
|
122
|
+
expect(result.value).to.equal(10)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it("gets the value from catch on error", ({ expect }) => {
|
|
126
|
+
const result = Result.try(() => {
|
|
127
|
+
throw new Error("fail")
|
|
128
|
+
})
|
|
129
|
+
.catch(err => {
|
|
130
|
+
return err.message
|
|
131
|
+
})
|
|
132
|
+
.exec()
|
|
133
|
+
expect(result.isSuccess()).to.be.false
|
|
134
|
+
expect(result.reason).to.equal("fail")
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it("ignores catch if try does not throw", ({ expect }) => {
|
|
138
|
+
const result = Result.try(() => {
|
|
139
|
+
const n = 5
|
|
140
|
+
return n * 2
|
|
141
|
+
})
|
|
142
|
+
.catch(err => {
|
|
143
|
+
throw new Error("Should not run")
|
|
144
|
+
})
|
|
145
|
+
.exec()
|
|
146
|
+
expect(result.isSuccess()).to.be.true
|
|
147
|
+
expect(result.value).to.equal(10)
|
|
148
|
+
})
|
|
149
|
+
})
|
|
150
|
+
})
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { identity } from "./index.ts"
|
|
2
|
+
|
|
3
|
+
interface API<S, F> {
|
|
4
|
+
isSuccess(this: Result<S, F>): this is Success<S, F>
|
|
5
|
+
isFailure(this: Result<S, F>): this is Failure<S, F>
|
|
6
|
+
map<S2>(this: Result<S, F>, f: (value: S) => S2): Result<S2, F>
|
|
7
|
+
flatMap<S2, F2>(this: Result<S, F>, f: (value: S) => Result<S2, F | F2>): Result<S2, F | F2>
|
|
8
|
+
toJSON(this: Result<S, F>): Object
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type Success<S, F> = { value: S } & API<S, F>
|
|
12
|
+
export type Failure<S, F> = { reason: F } & API<S, F>
|
|
13
|
+
export type Result<S, F> = Success<S, F> | Failure<S, F>
|
|
14
|
+
|
|
15
|
+
const $_kind = "@terrygonguet/utils/functional/result"
|
|
16
|
+
const $_variant_Success = "@terrygonguet/utils/functional/result/Success"
|
|
17
|
+
const $_variant_Failure = "@terrygonguet/utils/functional/result/Failure"
|
|
18
|
+
|
|
19
|
+
export function Success<S, F>(value: S): Success<S, F> {
|
|
20
|
+
const SuccessPrototype: API<S, F> = {
|
|
21
|
+
isSuccess: () => true,
|
|
22
|
+
isFailure: () => false,
|
|
23
|
+
map<S2>(this: Success<S, F>, f: (value: S) => S2) {
|
|
24
|
+
return Success(f(this.value))
|
|
25
|
+
},
|
|
26
|
+
flatMap<S2, F2>(this: Success<S, F>, f: (value: S) => Result<S2, F2>) {
|
|
27
|
+
return f(this.value)
|
|
28
|
+
},
|
|
29
|
+
toJSON(this: Success<S, F>) {
|
|
30
|
+
return { $_kind, $_variant: $_variant_Success, value: this.value }
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
return Object.create(SuccessPrototype, {
|
|
34
|
+
$_kind: { value: $_kind, enumerable: false, writable: false },
|
|
35
|
+
$_variant: { value: $_variant_Success, enumerable: false, writable: false },
|
|
36
|
+
value: { value, writable: false },
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function Failure<S, F>(reason: F): Failure<S, F> {
|
|
41
|
+
const FailurePrototype: API<S, F> = {
|
|
42
|
+
isSuccess: () => false,
|
|
43
|
+
isFailure: () => true,
|
|
44
|
+
map<S2>(this: Failure<S, F>) {
|
|
45
|
+
return this as unknown as Result<S2, F>
|
|
46
|
+
},
|
|
47
|
+
flatMap<S2, F2>(this: Failure<S, F>) {
|
|
48
|
+
return this as unknown as Result<S2, F | F2>
|
|
49
|
+
},
|
|
50
|
+
toJSON(this: Failure<S, F>) {
|
|
51
|
+
return { $_kind, $_variant: $_variant_Failure, reason: this.reason }
|
|
52
|
+
},
|
|
53
|
+
}
|
|
54
|
+
return Object.create(FailurePrototype, {
|
|
55
|
+
$_kind: { value: $_kind, enumerable: false, writable: false },
|
|
56
|
+
$_variant: { value: $_variant_Success, enumerable: false, writable: false },
|
|
57
|
+
reason: { value: reason, writable: false },
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Result = {
|
|
62
|
+
try<S, F>(tryFn: () => S) {
|
|
63
|
+
return new TryCatch<S, F>(tryFn)
|
|
64
|
+
},
|
|
65
|
+
JSONReviver(_key: string, value: any) {
|
|
66
|
+
if (value?.$_kind == $_kind) {
|
|
67
|
+
const $_variant = value?.$_variant
|
|
68
|
+
if ($_variant == $_variant_Success) return Success<unknown, unknown>(value?.value)
|
|
69
|
+
else if ($_variant == $_variant_Failure) return Failure<unknown, unknown>(value?.reason)
|
|
70
|
+
else return value
|
|
71
|
+
} else return value
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
class TryCatch<S, F> {
|
|
76
|
+
tryFn: () => S
|
|
77
|
+
catchFn: (err: unknown) => F
|
|
78
|
+
|
|
79
|
+
constructor(tryFn: () => S) {
|
|
80
|
+
this.tryFn = tryFn
|
|
81
|
+
this.catchFn = identity as any
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
catch(catchFn: (err: unknown) => F) {
|
|
85
|
+
this.catchFn = catchFn
|
|
86
|
+
return this
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
exec(): Result<S, F> {
|
|
90
|
+
try {
|
|
91
|
+
return Success<S, F>(this.tryFn())
|
|
92
|
+
} catch (error) {
|
|
93
|
+
return Failure<S, F>(this.catchFn(error))
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
package/src/main.test.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe } from "vitest"
|
|
2
|
+
import { clamp, safeParse } from "./main.ts"
|
|
3
|
+
|
|
4
|
+
describe.concurrent("clamp()", it => {
|
|
5
|
+
it("does nothing to values within range", ({ expect }) => {
|
|
6
|
+
expect(clamp(5, 0, 10)).to.equal(5)
|
|
7
|
+
expect(clamp(10, 10, 10)).to.equal(10)
|
|
8
|
+
expect(clamp(0, 0, 10)).to.equal(0)
|
|
9
|
+
expect(clamp(10, 0, 10)).to.equal(10)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it("clamps values outside of range", ({ expect }) => {
|
|
13
|
+
expect(clamp(-5, 0, 10)).to.equal(0)
|
|
14
|
+
expect(clamp(15, 0, 10)).to.equal(10)
|
|
15
|
+
expect(clamp(-5, 10, 10)).to.equal(10)
|
|
16
|
+
expect(clamp(15, 10, 10)).to.equal(10)
|
|
17
|
+
})
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
describe.concurrent("safeParse()", it => {
|
|
21
|
+
it("works the same as JSON.parse for valid objects", ({ expect }) => {
|
|
22
|
+
const json = `{"prop":"value"}`
|
|
23
|
+
expect(safeParse(json, {})).to.deep.equal(JSON.parse(json))
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it("uses default value for malformed JSON", ({ expect }) => {
|
|
27
|
+
const json = `{"prop":}`
|
|
28
|
+
expect(safeParse(json, { prop: "default" })).to.deep.equal({ prop: "default" })
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it("uses revivers", ({ expect }) => {
|
|
32
|
+
const json = `{"prop":"value","other":"ignored"}`
|
|
33
|
+
function reviver(key, value) {
|
|
34
|
+
if (key == "prop") return "replaced"
|
|
35
|
+
else return value
|
|
36
|
+
}
|
|
37
|
+
const defaultValue = { prop: "value", other: "ignored" }
|
|
38
|
+
expect(safeParse(json, defaultValue, reviver)).to.deep.equal({
|
|
39
|
+
prop: "replaced",
|
|
40
|
+
other: "ignored",
|
|
41
|
+
})
|
|
42
|
+
})
|
|
43
|
+
})
|
package/src/main.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour is undefined when max < min
|
|
3
|
+
*/
|
|
1
4
|
export function clamp(value: number, min: number, max: number) {
|
|
2
5
|
return Math.min(Math.max(value, min), max)
|
|
3
|
-
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function safeParse<T>(
|
|
9
|
+
str: string,
|
|
10
|
+
defaultValue: T,
|
|
11
|
+
reviver?: (key: string, value: any) => any,
|
|
12
|
+
) {
|
|
13
|
+
try {
|
|
14
|
+
return JSON.parse(str, reviver)
|
|
15
|
+
} catch (_) {
|
|
16
|
+
return defaultValue
|
|
17
|
+
}
|
|
18
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
"target": "ES2020",
|
|
4
4
|
"useDefineForClassFields": true,
|
|
5
5
|
"module": "ESNext",
|
|
6
|
-
"lib": ["ES2020"],
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
7
|
"skipLibCheck": true,
|
|
8
8
|
|
|
9
9
|
/* Bundler mode */
|
|
10
10
|
"moduleResolution": "node16",
|
|
11
11
|
"allowImportingTsExtensions": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
12
13
|
"resolveJsonModule": true,
|
|
13
14
|
"isolatedModules": true,
|
|
14
15
|
"declaration": true,
|
|
@@ -18,8 +19,7 @@
|
|
|
18
19
|
/* Linting */
|
|
19
20
|
"strict": true,
|
|
20
21
|
"noUnusedLocals": true,
|
|
21
|
-
"noUnusedParameters": true
|
|
22
|
-
"noFallthroughCasesInSwitch": true
|
|
22
|
+
"noUnusedParameters": true
|
|
23
23
|
},
|
|
24
24
|
"include": ["src"]
|
|
25
25
|
}
|
package/types/async.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function wait(ms: number): Promise<void>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface API<T> {
|
|
2
|
+
isSome(this: Maybe<T>): this is Some<T>;
|
|
3
|
+
isNone(this: Maybe<T>): this is None;
|
|
4
|
+
orDefault(this: Maybe<T>, defaultValue: T): T;
|
|
5
|
+
map<U>(this: Maybe<T>, f: (value: T) => U): Maybe<U>;
|
|
6
|
+
flatMap<U>(this: Maybe<T>, f: (value: T) => Maybe<U>): Maybe<U>;
|
|
7
|
+
toJSON(this: Maybe<T>): Object;
|
|
8
|
+
}
|
|
9
|
+
export type Some<T> = {
|
|
10
|
+
value: T;
|
|
11
|
+
} & API<T>;
|
|
12
|
+
export type None = {} & API<never>;
|
|
13
|
+
export type Maybe<T> = Some<T> | None;
|
|
14
|
+
export declare function Some<T>(value: NonNullable<T>): Some<T>;
|
|
15
|
+
export declare const None: None;
|
|
16
|
+
export declare const Maybe: {
|
|
17
|
+
from<T>(value: T | null | undefined): Maybe<T>;
|
|
18
|
+
JSONReviver(_key: string, value: any): any;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
interface API<S, F> {
|
|
2
|
+
isSuccess(this: Result<S, F>): this is Success<S, F>;
|
|
3
|
+
isFailure(this: Result<S, F>): this is Failure<S, F>;
|
|
4
|
+
map<S2>(this: Result<S, F>, f: (value: S) => S2): Result<S2, F>;
|
|
5
|
+
flatMap<S2, F2>(this: Result<S, F>, f: (value: S) => Result<S2, F | F2>): Result<S2, F | F2>;
|
|
6
|
+
toJSON(this: Result<S, F>): Object;
|
|
7
|
+
}
|
|
8
|
+
export type Success<S, F> = {
|
|
9
|
+
value: S;
|
|
10
|
+
} & API<S, F>;
|
|
11
|
+
export type Failure<S, F> = {
|
|
12
|
+
reason: F;
|
|
13
|
+
} & API<S, F>;
|
|
14
|
+
export type Result<S, F> = Success<S, F> | Failure<S, F>;
|
|
15
|
+
export declare function Success<S, F>(value: S): Success<S, F>;
|
|
16
|
+
export declare function Failure<S, F>(reason: F): Failure<S, F>;
|
|
17
|
+
export declare const Result: {
|
|
18
|
+
try<S, F>(tryFn: () => S): TryCatch<S, F>;
|
|
19
|
+
JSONReviver(_key: string, value: any): any;
|
|
20
|
+
};
|
|
21
|
+
declare class TryCatch<S, F> {
|
|
22
|
+
tryFn: () => S;
|
|
23
|
+
catchFn: (err: unknown) => F;
|
|
24
|
+
constructor(tryFn: () => S);
|
|
25
|
+
catch(catchFn: (err: unknown) => F): this;
|
|
26
|
+
exec(): Result<S, F>;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
package/types/main.d.ts
CHANGED
package/vite.config.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
import { resolve } from "path"
|
|
2
|
-
import { defineConfig } from "vite"
|
|
3
|
-
|
|
4
|
-
export default defineConfig({
|
|
5
|
-
build: {
|
|
6
|
-
lib: {
|
|
7
|
-
formats: ["es"],
|
|
8
|
-
entry: {
|
|
9
|
-
main: resolve(__dirname, "src/main.ts"),
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
1
|
+
import { resolve } from "path"
|
|
2
|
+
import { defineConfig } from "vite"
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
build: {
|
|
6
|
+
lib: {
|
|
7
|
+
formats: ["es"],
|
|
8
|
+
entry: {
|
|
9
|
+
main: resolve(__dirname, "src/main.ts"),
|
|
10
|
+
async: resolve(__dirname, "src/async.ts"),
|
|
11
|
+
functional: resolve(__dirname, "src/functional/index.ts"),
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
})
|