@planet-matrix/mobius-model 0.6.0 → 0.10.1
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/CHANGELOG.md +50 -0
- package/oxlint.config.ts +1 -2
- package/package.json +29 -17
- package/scripts/build.ts +2 -52
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/promise.ts +141 -71
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +198 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/event/class-event-proxy.ts +5 -6
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +5 -6
- package/src/event/internal.ts +4 -4
- package/src/exception/README.md +28 -19
- package/src/exception/error/error.ts +123 -0
- package/src/exception/error/index.ts +2 -0
- package/src/exception/error/match.ts +38 -0
- package/src/exception/error/must-fix.ts +17 -0
- package/src/exception/index.ts +2 -0
- package/src/file-system/find.ts +53 -0
- package/src/file-system/index.ts +2 -0
- package/src/file-system/path.ts +76 -0
- package/src/file-system/resolve.ts +22 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +861 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +179 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +34 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +294 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/index.ts +21 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +509 -0
- package/src/orchestration/README.md +9 -7
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +2 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +8 -7
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +280 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +280 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +246 -0
- package/src/request/request/general.ts +63 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +58 -0
- package/src/result/either.ts +363 -0
- package/src/result/generator.ts +168 -0
- package/src/result/index.ts +3 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1641 -0
- package/src/route/uri/hash.ts +307 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +412 -0
- package/src/service/README.md +1 -0
- package/src/service/index.ts +1 -0
- package/src/service/service.ts +110 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +658 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +448 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +137 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +157 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +132 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +131 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +146 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/promise.spec.ts +158 -50
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- package/tests/unit/credential/api-key.spec.ts +36 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +40 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- package/tests/unit/exception/error/error.spec.ts +83 -0
- package/tests/unit/exception/error/match.spec.ts +81 -0
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +187 -0
- package/tests/unit/identifier/uuid.spec.ts +0 -1
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +382 -0
- package/tests/unit/request/request/general.spec.ts +160 -0
- package/tests/unit/result/controller.spec.ts +82 -0
- package/tests/unit/result/either.spec.ts +377 -0
- package/tests/unit/result/generator.spec.ts +273 -0
- package/tests/unit/route/router/route.spec.ts +430 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +146 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +133 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/vite.config.ts +2 -1
- package/dist/index.js +0 -50
- package/dist/index.js.map +0 -209
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { Either, eitherToTuple, isEither, isLeft, isRight, left, right } from "#Source/result/index.ts"
|
|
4
|
+
|
|
5
|
+
const internalCreateLeft = (): Either<string, number> => Either.left<string, number>("stop")
|
|
6
|
+
|
|
7
|
+
const internalCreateRight = (): Either<string, number> => Either.right<string, number>(2)
|
|
8
|
+
|
|
9
|
+
test("Either.isEither identifies Either instances", () => {
|
|
10
|
+
expect(Either.isEither(internalCreateLeft())).toBe(true)
|
|
11
|
+
expect(Either.isEither(internalCreateRight())).toBe(true)
|
|
12
|
+
expect(Either.isEither({ value: "stop" })).toBe(false)
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test("Either.isLeft currently overflows through recursive static dispatch", () => {
|
|
16
|
+
expect(() => Either.isLeft(internalCreateLeft())).toThrow(RangeError)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("Either.isRight currently overflows through recursive static dispatch", () => {
|
|
20
|
+
expect(() => Either.isRight(internalCreateRight())).toThrow(RangeError)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("Either.left creates a Left instance", () => {
|
|
24
|
+
const either = Either.left<string, number>("left")
|
|
25
|
+
|
|
26
|
+
expect(either.isLeft()).toBe(true)
|
|
27
|
+
expect(either.getLeft()).toBe("left")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("Either.right creates a Right instance", () => {
|
|
31
|
+
const either = Either.right<string, number>(1)
|
|
32
|
+
|
|
33
|
+
expect(either.isRight()).toBe(true)
|
|
34
|
+
expect(either.getRight()).toBe(1)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("Either.pure creates a Right instance", () => {
|
|
38
|
+
const either = Either.pure<string, number>(3)
|
|
39
|
+
|
|
40
|
+
expect(either.isRight()).toBe(true)
|
|
41
|
+
expect(either.getRight()).toBe(3)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("Either.lift combines right values and returns the first left", () => {
|
|
45
|
+
const map = vi.fn((values: number[]) => values.reduce((sum, value) => sum + value, 0))
|
|
46
|
+
|
|
47
|
+
const liftedRight = Either.lift([
|
|
48
|
+
Either.right<string, number>(2),
|
|
49
|
+
Either.right<string, number>(3),
|
|
50
|
+
], map)
|
|
51
|
+
const liftedLeft = Either.lift([
|
|
52
|
+
Either.right<string, number>(2),
|
|
53
|
+
Either.left<string, number>("stop"),
|
|
54
|
+
Either.left<string, number>("ignored"),
|
|
55
|
+
], map)
|
|
56
|
+
|
|
57
|
+
expect(liftedRight.isRight()).toBe(true)
|
|
58
|
+
expect(liftedRight.getRight()).toBe(5)
|
|
59
|
+
expect(liftedLeft.isLeft()).toBe(true)
|
|
60
|
+
expect(liftedLeft.getLeft()).toBe("stop")
|
|
61
|
+
expect(map).toHaveBeenCalledTimes(1)
|
|
62
|
+
expect(map).toHaveBeenCalledWith([2, 3])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("Left.getValue returns the left value", () => {
|
|
66
|
+
expect(internalCreateLeft().getValue()).toBe("stop")
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("Left.getLeft returns the left value", () => {
|
|
70
|
+
expect(internalCreateLeft().getLeft()).toBe("stop")
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
test("Left.getRight throws", () => {
|
|
74
|
+
expect(() => internalCreateLeft().getRight()).toThrow("Cannot get right value from Left")
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("Left.getRightOr returns the fallback value", () => {
|
|
78
|
+
expect(internalCreateLeft().getRightOr(9)).toBe(9)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test("Left.isLeft returns true", () => {
|
|
82
|
+
expect(internalCreateLeft().isLeft()).toBe(true)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
test("Left.isRight returns false", () => {
|
|
86
|
+
expect(internalCreateLeft().isRight()).toBe(false)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test("Left.assertLeft returns itself", () => {
|
|
90
|
+
const either = internalCreateLeft()
|
|
91
|
+
|
|
92
|
+
expect(either.assertLeft()).toBe(either)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("Left.assertRight throws", () => {
|
|
96
|
+
expect(() => internalCreateLeft().assertRight()).toThrow("Assert failed")
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("Left.tap only invokes the left effect", () => {
|
|
100
|
+
const effects = {
|
|
101
|
+
left: vi.fn<(value: string) => void>(),
|
|
102
|
+
right: vi.fn<(value: number) => void>(),
|
|
103
|
+
}
|
|
104
|
+
const either = internalCreateLeft()
|
|
105
|
+
|
|
106
|
+
expect(either.tap(effects)).toBe(either)
|
|
107
|
+
expect(effects.left).toHaveBeenCalledWith("stop")
|
|
108
|
+
expect(effects.right).not.toHaveBeenCalled()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("Left.tapLeft invokes the left effect", () => {
|
|
112
|
+
const effect = vi.fn<(value: string) => void>()
|
|
113
|
+
const either = internalCreateLeft()
|
|
114
|
+
|
|
115
|
+
expect(either.tapLeft(effect)).toBe(either)
|
|
116
|
+
expect(effect).toHaveBeenCalledWith("stop")
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("Left.tapRight ignores the effect", () => {
|
|
120
|
+
const effect = vi.fn<(value: number) => void>()
|
|
121
|
+
const either = internalCreateLeft()
|
|
122
|
+
|
|
123
|
+
expect(either.tapRight(effect)).toBe(either)
|
|
124
|
+
expect(effect).not.toHaveBeenCalled()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("Left.mapLeft maps the left value", () => {
|
|
128
|
+
const either = internalCreateLeft().mapLeft((value) => `${value}!`)
|
|
129
|
+
|
|
130
|
+
expect(either.isLeft()).toBe(true)
|
|
131
|
+
expect(either.getLeft()).toBe("stop!")
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("Left.mapRight preserves the left value", () => {
|
|
135
|
+
const effect = vi.fn((value: number) => value + 1)
|
|
136
|
+
const either = internalCreateLeft().mapRight(effect)
|
|
137
|
+
|
|
138
|
+
expect(either.isLeft()).toBe(true)
|
|
139
|
+
expect(either.getLeft()).toBe("stop")
|
|
140
|
+
expect(effect).not.toHaveBeenCalled()
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test("Left.bind preserves the left value", () => {
|
|
144
|
+
const bind = vi.fn((value: number) => Either.right<string, number>(value + 1))
|
|
145
|
+
const either = internalCreateLeft().bind(bind)
|
|
146
|
+
|
|
147
|
+
expect(either.isLeft()).toBe(true)
|
|
148
|
+
expect(either.getLeft()).toBe("stop")
|
|
149
|
+
expect(bind).not.toHaveBeenCalled()
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test("Left.bindAsync preserves the left value", async () => {
|
|
153
|
+
const bind = vi.fn(async (value: number) => await Promise.resolve(Either.right<string, number>(value + 1)))
|
|
154
|
+
const either = await internalCreateLeft().bindAsync(bind)
|
|
155
|
+
|
|
156
|
+
expect(either.isLeft()).toBe(true)
|
|
157
|
+
expect(either.getLeft()).toBe("stop")
|
|
158
|
+
expect(bind).not.toHaveBeenCalled()
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test("Left.match uses the left branch", () => {
|
|
162
|
+
const result = internalCreateLeft().match(
|
|
163
|
+
(value) => `left:${value}`,
|
|
164
|
+
(value) => `right:${value}`,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
expect(result).toBe("left:stop")
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test("Left.matchAsync uses the left branch", async () => {
|
|
171
|
+
const result = await internalCreateLeft().matchAsync(
|
|
172
|
+
async (value) => await Promise.resolve(`left:${value}`),
|
|
173
|
+
async (value) => await Promise.resolve(`right:${value}`),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
expect(result).toBe("left:stop")
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test("Left.mplus returns the fallback either", () => {
|
|
180
|
+
const result = internalCreateLeft().mplus(Either.right<string, number>(9))
|
|
181
|
+
|
|
182
|
+
expect(result.isRight()).toBe(true)
|
|
183
|
+
expect(result.getRight()).toBe(9)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
test("Left iterator yields itself and then throws", () => {
|
|
187
|
+
const iterator = internalCreateLeft()[Symbol.iterator]()
|
|
188
|
+
const firstStep = iterator.next()
|
|
189
|
+
|
|
190
|
+
expect(firstStep.done).toBe(false)
|
|
191
|
+
expect(Either.isEither(firstStep.value)).toBe(true)
|
|
192
|
+
if (Either.isEither(firstStep.value) === false) {
|
|
193
|
+
throw new TypeError("Expected Either from Left iterator")
|
|
194
|
+
}
|
|
195
|
+
expect(firstStep.value.isLeft()).toBe(true)
|
|
196
|
+
expect(firstStep.value.getLeft()).toBe("stop")
|
|
197
|
+
expect(() => iterator.next(1)).toThrow("Cannot extract right value from Left")
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
test("Left async iterator yields itself and then throws", async () => {
|
|
201
|
+
const iterator = internalCreateLeft()[Symbol.asyncIterator]()
|
|
202
|
+
const firstStep = await iterator.next()
|
|
203
|
+
|
|
204
|
+
expect(firstStep.done).toBe(false)
|
|
205
|
+
expect(Either.isEither(firstStep.value)).toBe(true)
|
|
206
|
+
if (Either.isEither(firstStep.value) === false) {
|
|
207
|
+
throw new TypeError("Expected Either from Left async iterator")
|
|
208
|
+
}
|
|
209
|
+
expect(firstStep.value.isLeft()).toBe(true)
|
|
210
|
+
expect(firstStep.value.getLeft()).toBe("stop")
|
|
211
|
+
await expect(iterator.next(1)).rejects.toThrow("Cannot extract right value from Left")
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
test("Right.getValue returns the right value", () => {
|
|
215
|
+
expect(internalCreateRight().getValue()).toBe(2)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
test("Right.getLeft throws", () => {
|
|
219
|
+
expect(() => internalCreateRight().getLeft()).toThrow("Cannot get left value from Right")
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test("Right.getRight returns the right value", () => {
|
|
223
|
+
expect(internalCreateRight().getRight()).toBe(2)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
test("Right.getRightOr returns the right value", () => {
|
|
227
|
+
expect(internalCreateRight().getRightOr(9)).toBe(2)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
test("Right.isLeft returns false", () => {
|
|
231
|
+
expect(internalCreateRight().isLeft()).toBe(false)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
test("Right.isRight returns true", () => {
|
|
235
|
+
expect(internalCreateRight().isRight()).toBe(true)
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test("Right.assertLeft throws", () => {
|
|
239
|
+
expect(() => internalCreateRight().assertLeft()).toThrow("Assert failed")
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
test("Right.assertRight returns itself", () => {
|
|
243
|
+
const either = internalCreateRight()
|
|
244
|
+
|
|
245
|
+
expect(either.assertRight()).toBe(either)
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
test("Right.tap only invokes the right effect", () => {
|
|
249
|
+
const effects = {
|
|
250
|
+
left: vi.fn<(value: string) => void>(),
|
|
251
|
+
right: vi.fn<(value: number) => void>(),
|
|
252
|
+
}
|
|
253
|
+
const either = internalCreateRight()
|
|
254
|
+
|
|
255
|
+
expect(either.tap(effects)).toBe(either)
|
|
256
|
+
expect(effects.left).not.toHaveBeenCalled()
|
|
257
|
+
expect(effects.right).toHaveBeenCalledWith(2)
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test("Right.tapLeft ignores the effect", () => {
|
|
261
|
+
const effect = vi.fn<(value: string) => void>()
|
|
262
|
+
const either = internalCreateRight()
|
|
263
|
+
|
|
264
|
+
expect(either.tapLeft(effect)).toBe(either)
|
|
265
|
+
expect(effect).not.toHaveBeenCalled()
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
test("Right.tapRight invokes the effect", () => {
|
|
269
|
+
const effect = vi.fn<(value: number) => void>()
|
|
270
|
+
const either = internalCreateRight()
|
|
271
|
+
|
|
272
|
+
expect(either.tapRight(effect)).toBe(either)
|
|
273
|
+
expect(effect).toHaveBeenCalledWith(2)
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
test("Right.mapLeft preserves the right value", () => {
|
|
277
|
+
const effect = vi.fn((value: string) => value.length)
|
|
278
|
+
const either = internalCreateRight().mapLeft(effect)
|
|
279
|
+
|
|
280
|
+
expect(either.isRight()).toBe(true)
|
|
281
|
+
expect(either.getRight()).toBe(2)
|
|
282
|
+
expect(effect).not.toHaveBeenCalled()
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
test("Right.mapRight maps the right value", () => {
|
|
286
|
+
const either = internalCreateRight().mapRight((value) => value + 1)
|
|
287
|
+
|
|
288
|
+
expect(either.isRight()).toBe(true)
|
|
289
|
+
expect(either.getRight()).toBe(3)
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test("Right.bind uses the bind result", () => {
|
|
293
|
+
const bind = vi.fn((value: number) => Either.right<string, number>(value + 3))
|
|
294
|
+
const either = internalCreateRight().bind(bind)
|
|
295
|
+
|
|
296
|
+
expect(either.isRight()).toBe(true)
|
|
297
|
+
expect(either.getRight()).toBe(5)
|
|
298
|
+
expect(bind).toHaveBeenCalledWith(2)
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
test("Right.bindAsync uses the bind result", async () => {
|
|
302
|
+
const bind = vi.fn(async (value: number) => await Promise.resolve(Either.right<string, number>(value + 4)))
|
|
303
|
+
const either = await internalCreateRight().bindAsync(bind)
|
|
304
|
+
|
|
305
|
+
expect(either.isRight()).toBe(true)
|
|
306
|
+
expect(either.getRight()).toBe(6)
|
|
307
|
+
expect(bind).toHaveBeenCalledWith(2)
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
test("Right.match uses the right branch", () => {
|
|
311
|
+
const result = internalCreateRight().match(
|
|
312
|
+
(value) => `left:${value}`,
|
|
313
|
+
(value) => `right:${value}`,
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
expect(result).toBe("right:2")
|
|
317
|
+
})
|
|
318
|
+
|
|
319
|
+
test("Right.matchAsync uses the right branch", async () => {
|
|
320
|
+
const result = await internalCreateRight().matchAsync(
|
|
321
|
+
async (value) => await Promise.resolve(`left:${value}`),
|
|
322
|
+
async (value) => await Promise.resolve(`right:${value}`),
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
expect(result).toBe("right:2")
|
|
326
|
+
})
|
|
327
|
+
|
|
328
|
+
test("Right.mplus keeps the current either", () => {
|
|
329
|
+
const result = internalCreateRight().mplus(Either.left<string, number>("fallback"))
|
|
330
|
+
|
|
331
|
+
expect(result.isRight()).toBe(true)
|
|
332
|
+
expect(result.getRight()).toBe(2)
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
test("Right iterator returns the right value immediately", () => {
|
|
336
|
+
expect(internalCreateRight()[Symbol.iterator]().next()).toEqual({ done: true, value: 2 })
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
test("Right async iterator returns the right value immediately", async () => {
|
|
340
|
+
await expect(internalCreateRight()[Symbol.asyncIterator]().next()).resolves.toEqual({ done: true, value: 2 })
|
|
341
|
+
})
|
|
342
|
+
|
|
343
|
+
test("Right iterator returns the right value immediately", () => {
|
|
344
|
+
expect(internalCreateRight()[Symbol.iterator]().next()).toEqual({ done: true, value: 2 })
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
test("isEither delegates to Either.isEither", () => {
|
|
348
|
+
expect(isEither(internalCreateLeft())).toBe(true)
|
|
349
|
+
expect(isEither(null)).toBe(false)
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
test("isLeft currently overflows through recursive static dispatch", () => {
|
|
353
|
+
expect(() => isLeft(internalCreateLeft())).toThrow(RangeError)
|
|
354
|
+
})
|
|
355
|
+
|
|
356
|
+
test("isRight currently overflows through recursive static dispatch", () => {
|
|
357
|
+
expect(() => isRight(internalCreateRight())).toThrow(RangeError)
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
test("left delegates to Either.left", () => {
|
|
361
|
+
const either = left<string, number>("helper-left")
|
|
362
|
+
|
|
363
|
+
expect(either.isLeft()).toBe(true)
|
|
364
|
+
expect(either.getLeft()).toBe("helper-left")
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
test("right delegates to Either.right", () => {
|
|
368
|
+
const either = right<string, number>(7)
|
|
369
|
+
|
|
370
|
+
expect(either.isRight()).toBe(true)
|
|
371
|
+
expect(either.getRight()).toBe(7)
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
test("eitherToTuple converts Left and Right values into tuples", () => {
|
|
375
|
+
expect(eitherToTuple(Either.left<string, number>("left"))).toEqual(["left", undefined])
|
|
376
|
+
expect(eitherToTuple(Either.right<string, number>(7))).toEqual([undefined, 7])
|
|
377
|
+
})
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { Either, gen, genAsync, genAwait, genSync, runAsyncGenerator, runSyncGenerator } from "#Source/result/index.ts"
|
|
4
|
+
|
|
5
|
+
const throwCleanupError = (): never => {
|
|
6
|
+
throw new Error("cleanup")
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
test("runSyncGenerator returns the first yielded Left, the final Right, and wraps sync failures", () => {
|
|
10
|
+
let internalFinallyCalled = false
|
|
11
|
+
|
|
12
|
+
const success = runSyncGenerator((function* () {
|
|
13
|
+
const value = yield* Either.right<string, number>(1)
|
|
14
|
+
return Either.right<string, number>(value + 1)
|
|
15
|
+
})())
|
|
16
|
+
|
|
17
|
+
const failure = runSyncGenerator((function* () {
|
|
18
|
+
try {
|
|
19
|
+
yield* Either.right<string, number>(1)
|
|
20
|
+
yield* Either.left<string, number>("stop")
|
|
21
|
+
return Either.right<string, number>(999)
|
|
22
|
+
} finally {
|
|
23
|
+
internalFinallyCalled = true
|
|
24
|
+
}
|
|
25
|
+
})())
|
|
26
|
+
|
|
27
|
+
expect(success.isRight()).toBe(true)
|
|
28
|
+
expect(success.getRight()).toBe(2)
|
|
29
|
+
expect(failure.isLeft()).toBe(true)
|
|
30
|
+
expect(failure.getLeft()).toBe("stop")
|
|
31
|
+
expect(internalFinallyCalled).toBe(true)
|
|
32
|
+
expect(() => runSyncGenerator((function* () {
|
|
33
|
+
throw new Error("boom")
|
|
34
|
+
})())).toThrow("Generator threw an exception")
|
|
35
|
+
expect(() => runSyncGenerator((function* () {
|
|
36
|
+
yield "invalid" as unknown as Either<never, never>
|
|
37
|
+
return Either.right(1)
|
|
38
|
+
})())).toThrow("Generator yielded a non-Either value")
|
|
39
|
+
expect(() => runSyncGenerator((function* () {
|
|
40
|
+
try {
|
|
41
|
+
yield Either.left<string, number>("stop")
|
|
42
|
+
return Either.right(1)
|
|
43
|
+
} finally {
|
|
44
|
+
throwCleanupError()
|
|
45
|
+
}
|
|
46
|
+
})())).toThrow("Generator threw an exception during cleanup")
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("runAsyncGenerator returns the first yielded Left, the final Right, and wraps async failures", async () => {
|
|
50
|
+
let internalFinallyCalled = false
|
|
51
|
+
|
|
52
|
+
const success = await runAsyncGenerator((async function* () {
|
|
53
|
+
const value = yield* Either.right<string, number>(1)
|
|
54
|
+
return await Promise.resolve(Either.right<string, number>(value + 1))
|
|
55
|
+
})())
|
|
56
|
+
|
|
57
|
+
const failure = await runAsyncGenerator((async function* () {
|
|
58
|
+
try {
|
|
59
|
+
yield* Either.right<string, number>(1)
|
|
60
|
+
yield* Either.left<string, number>("stop")
|
|
61
|
+
return await Promise.resolve(Either.right<string, number>(999))
|
|
62
|
+
} finally {
|
|
63
|
+
internalFinallyCalled = true
|
|
64
|
+
}
|
|
65
|
+
})())
|
|
66
|
+
|
|
67
|
+
expect(success.isRight()).toBe(true)
|
|
68
|
+
expect(success.getRight()).toBe(2)
|
|
69
|
+
expect(failure.isLeft()).toBe(true)
|
|
70
|
+
expect(failure.getLeft()).toBe("stop")
|
|
71
|
+
expect(internalFinallyCalled).toBe(true)
|
|
72
|
+
await expect(runAsyncGenerator((async function* () {
|
|
73
|
+
await Promise.resolve()
|
|
74
|
+
throw new Error("boom")
|
|
75
|
+
})())).rejects.toThrow("Generator threw an exception")
|
|
76
|
+
await expect(runAsyncGenerator((async function* () {
|
|
77
|
+
await Promise.resolve()
|
|
78
|
+
yield "invalid" as unknown as Either<never, never>
|
|
79
|
+
return Either.right(1)
|
|
80
|
+
})())).rejects.toThrow("Generator yielded a non-Either value")
|
|
81
|
+
await expect(runAsyncGenerator((async function* () {
|
|
82
|
+
try {
|
|
83
|
+
await Promise.resolve()
|
|
84
|
+
yield Either.left<string, number>("stop")
|
|
85
|
+
return Either.right(1)
|
|
86
|
+
} finally {
|
|
87
|
+
throwCleanupError()
|
|
88
|
+
}
|
|
89
|
+
})())).rejects.toThrow("Generator threw an exception during cleanup")
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("genSync composes Either values with sync generators and keeps yielded Left unions", () => {
|
|
93
|
+
const getA = (): Either<"a", number> => Either.right(1)
|
|
94
|
+
const getB = (value: number): Either<"b", number> => Either.right(value + 1)
|
|
95
|
+
const getC = (value: number): Either<"c", number> => Either.right(value + 1)
|
|
96
|
+
const context = {
|
|
97
|
+
offset: 4,
|
|
98
|
+
*build(this: { offset: number }) {
|
|
99
|
+
const value = yield* Either.right<"ctx", number>(this.offset)
|
|
100
|
+
return Either.right(value + 1)
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const success = genSync(function* () {
|
|
105
|
+
const a = yield* getA()
|
|
106
|
+
const b = yield* getB(a)
|
|
107
|
+
const c = yield* getC(b)
|
|
108
|
+
return Either.right(c)
|
|
109
|
+
})
|
|
110
|
+
const thisBound = genSync(context.build, context)
|
|
111
|
+
|
|
112
|
+
const failure = genSync(function* () {
|
|
113
|
+
yield* getA()
|
|
114
|
+
yield* Either.left<"stop", number>("stop")
|
|
115
|
+
return Either.right(999)
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const expected: Either<"a" | "b" | "c", number> = success
|
|
119
|
+
const actual: typeof success = expected
|
|
120
|
+
const thisExpected: Either<"ctx", number> = thisBound
|
|
121
|
+
const thisActual: typeof thisBound = thisExpected
|
|
122
|
+
|
|
123
|
+
expect(actual).toBe(success)
|
|
124
|
+
expect(thisActual).toBe(thisBound)
|
|
125
|
+
expect(success.isRight()).toBe(true)
|
|
126
|
+
expect(success.getRight()).toBe(3)
|
|
127
|
+
expect(thisBound.isRight()).toBe(true)
|
|
128
|
+
expect(thisBound.getRight()).toBe(5)
|
|
129
|
+
expect(failure.isLeft()).toBe(true)
|
|
130
|
+
expect(failure.getLeft()).toBe("stop")
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("genAsync composes Either values with async generators and keeps yielded Left unions", async () => {
|
|
134
|
+
const getA = (): Either<"a", number> => Either.right(1)
|
|
135
|
+
const getB = (value: number): Either<"b", number> => Either.right(value + 1)
|
|
136
|
+
const getC = (value: number): Either<"c", number> => Either.right(value + 1)
|
|
137
|
+
const context = {
|
|
138
|
+
offset: 5,
|
|
139
|
+
async *build(this: { offset: number }) {
|
|
140
|
+
const value = yield* Either.right<"ctx", number>(this.offset)
|
|
141
|
+
return await Promise.resolve(Either.right(value + 1))
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const success = genAsync(async function* () {
|
|
146
|
+
const a = yield* getA()
|
|
147
|
+
const b = yield* getB(a)
|
|
148
|
+
const c = yield* getC(b)
|
|
149
|
+
return await Promise.resolve(Either.right(c))
|
|
150
|
+
})
|
|
151
|
+
const thisBound = genAsync(context.build, context)
|
|
152
|
+
|
|
153
|
+
const failure = genAsync(async function* () {
|
|
154
|
+
yield* getA()
|
|
155
|
+
yield* Either.left<"stop", number>("stop")
|
|
156
|
+
return await Promise.resolve(Either.right(999))
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
const expected: Promise<Either<"a" | "b" | "c", number>> = success
|
|
160
|
+
const actual: typeof success = expected
|
|
161
|
+
const thisExpected: Promise<Either<"ctx", number>> = thisBound
|
|
162
|
+
const thisActual: typeof thisBound = thisExpected
|
|
163
|
+
const resolvedSuccess = await success
|
|
164
|
+
const resolvedThisBound = await thisBound
|
|
165
|
+
const resolvedFailure = await failure
|
|
166
|
+
|
|
167
|
+
expect(actual).toBe(success)
|
|
168
|
+
expect(thisActual).toBe(thisBound)
|
|
169
|
+
expect(resolvedSuccess.isRight()).toBe(true)
|
|
170
|
+
expect(resolvedSuccess.getRight()).toBe(3)
|
|
171
|
+
expect(resolvedThisBound.isRight()).toBe(true)
|
|
172
|
+
expect(resolvedThisBound.getRight()).toBe(6)
|
|
173
|
+
expect(resolvedFailure.isLeft()).toBe(true)
|
|
174
|
+
expect(resolvedFailure.getLeft()).toBe("stop")
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test("gen dispatches sync and async generators while preserving inferred Left unions", async () => {
|
|
178
|
+
const getA = (): Either<"a", number> => Either.right(1)
|
|
179
|
+
const getB = (value: number): Either<"b", number> => Either.right(value + 1)
|
|
180
|
+
const syncContext = {
|
|
181
|
+
offset: 7,
|
|
182
|
+
*build(this: { offset: number }) {
|
|
183
|
+
const value = yield* Either.right<"ctx-sync", number>(this.offset)
|
|
184
|
+
return Either.right(value + 1)
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
const asyncContext = {
|
|
188
|
+
offset: 8,
|
|
189
|
+
async *build(this: { offset: number }) {
|
|
190
|
+
const value = yield* Either.right<"ctx-async", number>(this.offset)
|
|
191
|
+
return await Promise.resolve(Either.right(value + 1))
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const sync = gen(function* () {
|
|
196
|
+
const a = yield* getA()
|
|
197
|
+
const b = yield* getB(a)
|
|
198
|
+
return Either.right(b)
|
|
199
|
+
})
|
|
200
|
+
const syncWithThis = gen(syncContext.build, syncContext)
|
|
201
|
+
|
|
202
|
+
const asyncResult = gen(async function* () {
|
|
203
|
+
const a = yield* getA()
|
|
204
|
+
const b = yield* getB(a)
|
|
205
|
+
return await Promise.resolve(Either.right(b))
|
|
206
|
+
})
|
|
207
|
+
const asyncWithThis = gen(asyncContext.build, asyncContext)
|
|
208
|
+
|
|
209
|
+
const syncExpected: Either<"a" | "b", number> = sync
|
|
210
|
+
const syncActual: typeof sync = syncExpected
|
|
211
|
+
const syncWithThisExpected: Either<"ctx-sync", number> = syncWithThis
|
|
212
|
+
const syncWithThisActual: typeof syncWithThis = syncWithThisExpected
|
|
213
|
+
const asyncExpected: Promise<Either<"a" | "b", number>> = asyncResult
|
|
214
|
+
const asyncActual: typeof asyncResult = asyncExpected
|
|
215
|
+
const asyncWithThisExpected: Promise<Either<"ctx-async", number>> = asyncWithThis
|
|
216
|
+
const asyncWithThisActual: typeof asyncWithThis = asyncWithThisExpected
|
|
217
|
+
const resolvedAsyncResult = await asyncResult
|
|
218
|
+
const resolvedAsyncWithThis = await asyncWithThis
|
|
219
|
+
|
|
220
|
+
expect(syncActual).toBe(sync)
|
|
221
|
+
expect(syncWithThisActual).toBe(syncWithThis)
|
|
222
|
+
expect(asyncActual).toBe(asyncResult)
|
|
223
|
+
expect(asyncWithThisActual).toBe(asyncWithThis)
|
|
224
|
+
expect(sync.isRight()).toBe(true)
|
|
225
|
+
expect(sync.getRight()).toBe(2)
|
|
226
|
+
expect(syncWithThis.isRight()).toBe(true)
|
|
227
|
+
expect(syncWithThis.getRight()).toBe(8)
|
|
228
|
+
expect(asyncResult).toBeInstanceOf(Promise)
|
|
229
|
+
expect(asyncWithThis).toBeInstanceOf(Promise)
|
|
230
|
+
expect(resolvedAsyncResult.isRight()).toBe(true)
|
|
231
|
+
expect(resolvedAsyncResult.getRight()).toBe(2)
|
|
232
|
+
expect(resolvedAsyncWithThis.isRight()).toBe(true)
|
|
233
|
+
expect(resolvedAsyncWithThis.getRight()).toBe(9)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
test("genAwait composes promised Either values inside async generators", async () => {
|
|
237
|
+
const fetchUser = async (id: string): Promise<Either<"not-found", { id: string }>> => {
|
|
238
|
+
return await Promise.resolve(Either.right({ id }))
|
|
239
|
+
}
|
|
240
|
+
const fetchMissingProfile = async (_id: string): Promise<Either<"profile-missing", { id: string; role: string }>> => {
|
|
241
|
+
return await Promise.resolve(Either.left("profile-missing"))
|
|
242
|
+
}
|
|
243
|
+
const enrichUserData = (user: { id: string }): Either<"enrich-failed", { id: string; kind: "enriched" }> => {
|
|
244
|
+
return Either.right({ id: user.id, kind: "enriched" })
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const success = gen(async function* () {
|
|
248
|
+
const validated = yield* Either.right<"invalid", { id: string }>({ id: "123" })
|
|
249
|
+
const user = yield* genAwait(fetchUser(validated.id))
|
|
250
|
+
const enriched = yield* enrichUserData(user)
|
|
251
|
+
return Either.right(enriched)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
const failure = gen(async function* () {
|
|
255
|
+
const validated = yield* Either.right<"invalid", { id: string }>({ id: "123" })
|
|
256
|
+
yield* genAwait(fetchMissingProfile(validated.id))
|
|
257
|
+
return Either.right("unreachable")
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const successExpected: Promise<Either<"invalid" | "not-found" | "enrich-failed", { id: string; kind: "enriched" }>> = success
|
|
261
|
+
const successActual: typeof success = successExpected
|
|
262
|
+
const failureExpected: Promise<Either<"invalid" | "profile-missing", string>> = failure
|
|
263
|
+
const failureActual: typeof failure = failureExpected
|
|
264
|
+
const resolvedSuccess = await success
|
|
265
|
+
const resolvedFailure = await failure
|
|
266
|
+
|
|
267
|
+
expect(successActual).toBe(success)
|
|
268
|
+
expect(failureActual).toBe(failure)
|
|
269
|
+
expect(resolvedSuccess.isRight()).toBe(true)
|
|
270
|
+
expect(resolvedSuccess.getRight()).toEqual({ id: "123", kind: "enriched" })
|
|
271
|
+
expect(resolvedFailure.isLeft()).toBe(true)
|
|
272
|
+
expect(resolvedFailure.getLeft()).toBe("profile-missing")
|
|
273
|
+
})
|