@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.
Files changed (258) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/oxlint.config.ts +1 -2
  3. package/package.json +29 -17
  4. package/scripts/build.ts +2 -52
  5. package/src/ai/README.md +1 -0
  6. package/src/ai/ai.ts +107 -0
  7. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  8. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  9. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  10. package/src/ai/chat-completion-ai/index.ts +7 -0
  11. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  12. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  13. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  14. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  15. package/src/ai/embedding-ai/embedding.ts +50 -0
  16. package/src/ai/embedding-ai/index.ts +4 -0
  17. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  18. package/src/ai/index.ts +4 -0
  19. package/src/aio/README.md +100 -0
  20. package/src/aio/content.ts +141 -0
  21. package/src/aio/index.ts +3 -0
  22. package/src/aio/json.ts +127 -0
  23. package/src/aio/prompt.ts +246 -0
  24. package/src/basic/README.md +20 -15
  25. package/src/basic/error.ts +19 -5
  26. package/src/basic/function.ts +2 -2
  27. package/src/basic/index.ts +1 -0
  28. package/src/basic/promise.ts +141 -71
  29. package/src/basic/schedule.ts +111 -0
  30. package/src/basic/stream.ts +135 -25
  31. package/src/credential/README.md +107 -0
  32. package/src/credential/api-key.ts +158 -0
  33. package/src/credential/bearer.ts +73 -0
  34. package/src/credential/index.ts +4 -0
  35. package/src/credential/json-web-token.ts +96 -0
  36. package/src/credential/password.ts +170 -0
  37. package/src/cron/README.md +86 -0
  38. package/src/cron/cron.ts +87 -0
  39. package/src/cron/index.ts +1 -0
  40. package/src/drizzle/README.md +1 -0
  41. package/src/drizzle/drizzle.ts +1 -0
  42. package/src/drizzle/helper.ts +47 -0
  43. package/src/drizzle/index.ts +5 -0
  44. package/src/drizzle/infer.ts +52 -0
  45. package/src/drizzle/kysely.ts +8 -0
  46. package/src/drizzle/pagination.ts +198 -0
  47. package/src/email/README.md +1 -0
  48. package/src/email/index.ts +1 -0
  49. package/src/email/resend.ts +25 -0
  50. package/src/event/class-event-proxy.ts +5 -6
  51. package/src/event/common.ts +13 -3
  52. package/src/event/event-manager.ts +3 -3
  53. package/src/event/instance-event-proxy.ts +5 -6
  54. package/src/event/internal.ts +4 -4
  55. package/src/exception/README.md +28 -19
  56. package/src/exception/error/error.ts +123 -0
  57. package/src/exception/error/index.ts +2 -0
  58. package/src/exception/error/match.ts +38 -0
  59. package/src/exception/error/must-fix.ts +17 -0
  60. package/src/exception/index.ts +2 -0
  61. package/src/file-system/find.ts +53 -0
  62. package/src/file-system/index.ts +2 -0
  63. package/src/file-system/path.ts +76 -0
  64. package/src/file-system/resolve.ts +22 -0
  65. package/src/form/README.md +25 -0
  66. package/src/form/index.ts +1 -0
  67. package/src/form/inputor-controller/base.ts +861 -0
  68. package/src/form/inputor-controller/boolean.ts +39 -0
  69. package/src/form/inputor-controller/file.ts +39 -0
  70. package/src/form/inputor-controller/form.ts +179 -0
  71. package/src/form/inputor-controller/helper.ts +117 -0
  72. package/src/form/inputor-controller/index.ts +17 -0
  73. package/src/form/inputor-controller/multi-select.ts +99 -0
  74. package/src/form/inputor-controller/number.ts +116 -0
  75. package/src/form/inputor-controller/select.ts +109 -0
  76. package/src/form/inputor-controller/text.ts +82 -0
  77. package/src/http/READMD.md +1 -0
  78. package/src/http/api/api-core.ts +84 -0
  79. package/src/http/api/api-handler.ts +79 -0
  80. package/src/http/api/api-host.ts +47 -0
  81. package/src/http/api/api-result.ts +56 -0
  82. package/src/http/api/api-schema.ts +154 -0
  83. package/src/http/api/api-server.ts +130 -0
  84. package/src/http/api/api-test.ts +142 -0
  85. package/src/http/api/api-type.ts +34 -0
  86. package/src/http/api/api.ts +81 -0
  87. package/src/http/api/index.ts +11 -0
  88. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  89. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  90. package/src/http/api-adapter/api-result-arktype.ts +294 -0
  91. package/src/http/api-adapter/api-result-zod.ts +286 -0
  92. package/src/http/api-adapter/index.ts +5 -0
  93. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  94. package/src/http/bin/gen-api-list/index.ts +1 -0
  95. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  96. package/src/http/bin/gen-api-test/index.ts +1 -0
  97. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  98. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  99. package/src/http/bin/gen-api-type/index.ts +2 -0
  100. package/src/http/bin/index.ts +2 -0
  101. package/src/http/index.ts +3 -0
  102. package/src/huawei/README.md +1 -0
  103. package/src/huawei/index.ts +2 -0
  104. package/src/huawei/moderation/index.ts +1 -0
  105. package/src/huawei/moderation/moderation.ts +355 -0
  106. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  107. package/src/huawei/obs/index.ts +1 -0
  108. package/src/huawei/obs/obs.ts +42 -0
  109. package/src/index.ts +21 -2
  110. package/src/json/README.md +92 -0
  111. package/src/json/index.ts +1 -0
  112. package/src/json/repair.ts +18 -0
  113. package/src/log/logger.ts +15 -4
  114. package/src/openai/README.md +1 -0
  115. package/src/openai/index.ts +1 -0
  116. package/src/openai/openai.ts +509 -0
  117. package/src/orchestration/README.md +9 -7
  118. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  119. package/src/orchestration/dispatching/index.ts +2 -0
  120. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  121. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  122. package/src/orchestration/dispatching/selector/index.ts +2 -0
  123. package/src/orchestration/index.ts +2 -0
  124. package/src/orchestration/scheduling/index.ts +2 -0
  125. package/src/orchestration/scheduling/scheduler.ts +103 -0
  126. package/src/orchestration/scheduling/task.ts +32 -0
  127. package/src/random/README.md +8 -7
  128. package/src/random/base.ts +66 -0
  129. package/src/random/index.ts +5 -1
  130. package/src/random/random-boolean.ts +40 -0
  131. package/src/random/random-integer.ts +60 -0
  132. package/src/random/random-number.ts +72 -0
  133. package/src/random/random-string.ts +66 -0
  134. package/src/request/README.md +108 -0
  135. package/src/request/fetch/base.ts +108 -0
  136. package/src/request/fetch/browser.ts +280 -0
  137. package/src/request/fetch/general.ts +20 -0
  138. package/src/request/fetch/index.ts +4 -0
  139. package/src/request/fetch/nodejs.ts +280 -0
  140. package/src/request/index.ts +2 -0
  141. package/src/request/request/base.ts +246 -0
  142. package/src/request/request/general.ts +63 -0
  143. package/src/request/request/index.ts +3 -0
  144. package/src/request/request/resource.ts +68 -0
  145. package/src/result/README.md +4 -0
  146. package/src/result/controller.ts +58 -0
  147. package/src/result/either.ts +363 -0
  148. package/src/result/generator.ts +168 -0
  149. package/src/result/index.ts +3 -0
  150. package/src/route/README.md +105 -0
  151. package/src/route/adapter/browser.ts +122 -0
  152. package/src/route/adapter/driver.ts +56 -0
  153. package/src/route/adapter/index.ts +2 -0
  154. package/src/route/index.ts +3 -0
  155. package/src/route/router/index.ts +2 -0
  156. package/src/route/router/route.ts +630 -0
  157. package/src/route/router/router.ts +1641 -0
  158. package/src/route/uri/hash.ts +307 -0
  159. package/src/route/uri/index.ts +7 -0
  160. package/src/route/uri/pathname.ts +376 -0
  161. package/src/route/uri/search.ts +412 -0
  162. package/src/service/README.md +1 -0
  163. package/src/service/index.ts +1 -0
  164. package/src/service/service.ts +110 -0
  165. package/src/socket/README.md +105 -0
  166. package/src/socket/client/index.ts +2 -0
  167. package/src/socket/client/socket-unit.ts +658 -0
  168. package/src/socket/client/socket.ts +203 -0
  169. package/src/socket/common/index.ts +2 -0
  170. package/src/socket/common/socket-unit-common.ts +23 -0
  171. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  172. package/src/socket/index.ts +3 -0
  173. package/src/socket/server/index.ts +3 -0
  174. package/src/socket/server/server.ts +183 -0
  175. package/src/socket/server/socket-unit.ts +448 -0
  176. package/src/socket/server/socket.ts +264 -0
  177. package/src/storage/table.ts +3 -3
  178. package/src/timer/expiration/expiration-manager.ts +3 -3
  179. package/src/timer/expiration/remaining-manager.ts +3 -3
  180. package/src/tube/README.md +99 -0
  181. package/src/tube/helper.ts +137 -0
  182. package/src/tube/index.ts +2 -0
  183. package/src/tube/tube.ts +880 -0
  184. package/src/weixin/README.md +1 -0
  185. package/src/weixin/index.ts +2 -0
  186. package/src/weixin/official-account/authorization.ts +157 -0
  187. package/src/weixin/official-account/index.ts +2 -0
  188. package/src/weixin/official-account/js-api.ts +132 -0
  189. package/src/weixin/open/index.ts +1 -0
  190. package/src/weixin/open/oauth2.ts +131 -0
  191. package/tests/unit/ai/ai.spec.ts +85 -0
  192. package/tests/unit/aio/content.spec.ts +105 -0
  193. package/tests/unit/aio/json.spec.ts +146 -0
  194. package/tests/unit/aio/prompt.spec.ts +111 -0
  195. package/tests/unit/basic/error.spec.ts +16 -4
  196. package/tests/unit/basic/promise.spec.ts +158 -50
  197. package/tests/unit/basic/schedule.spec.ts +74 -0
  198. package/tests/unit/basic/stream.spec.ts +90 -37
  199. package/tests/unit/credential/api-key.spec.ts +36 -0
  200. package/tests/unit/credential/bearer.spec.ts +23 -0
  201. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  202. package/tests/unit/credential/password.spec.ts +40 -0
  203. package/tests/unit/cron/cron.spec.ts +84 -0
  204. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  205. package/tests/unit/event/event-manager.spec.ts +3 -3
  206. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  207. package/tests/unit/exception/error/error.spec.ts +83 -0
  208. package/tests/unit/exception/error/match.spec.ts +81 -0
  209. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  210. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  211. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  212. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  213. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  214. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  215. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  216. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  217. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  218. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  219. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  220. package/tests/unit/http/api/api-server.spec.ts +363 -0
  221. package/tests/unit/http/api/api-test.spec.ts +117 -0
  222. package/tests/unit/http/api/api.spec.ts +121 -0
  223. package/tests/unit/http/api-adapter/node-http.spec.ts +187 -0
  224. package/tests/unit/identifier/uuid.spec.ts +0 -1
  225. package/tests/unit/json/repair.spec.ts +11 -0
  226. package/tests/unit/log/logger.spec.ts +19 -4
  227. package/tests/unit/openai/openai.spec.ts +64 -0
  228. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  229. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  230. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  231. package/tests/unit/random/base.spec.ts +58 -0
  232. package/tests/unit/random/random-boolean.spec.ts +25 -0
  233. package/tests/unit/random/random-integer.spec.ts +32 -0
  234. package/tests/unit/random/random-number.spec.ts +33 -0
  235. package/tests/unit/random/random-string.spec.ts +22 -0
  236. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  237. package/tests/unit/request/fetch/general.spec.ts +43 -0
  238. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  239. package/tests/unit/request/request/base.spec.ts +382 -0
  240. package/tests/unit/request/request/general.spec.ts +160 -0
  241. package/tests/unit/result/controller.spec.ts +82 -0
  242. package/tests/unit/result/either.spec.ts +377 -0
  243. package/tests/unit/result/generator.spec.ts +273 -0
  244. package/tests/unit/route/router/route.spec.ts +430 -0
  245. package/tests/unit/route/router/router.spec.ts +407 -0
  246. package/tests/unit/route/uri/hash.spec.ts +72 -0
  247. package/tests/unit/route/uri/pathname.spec.ts +146 -0
  248. package/tests/unit/route/uri/search.spec.ts +107 -0
  249. package/tests/unit/socket/client.spec.ts +208 -0
  250. package/tests/unit/socket/server.spec.ts +133 -0
  251. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  252. package/tests/unit/tube/helper.spec.ts +139 -0
  253. package/tests/unit/tube/tube.spec.ts +501 -0
  254. package/vite.config.ts +2 -1
  255. package/dist/index.js +0 -50
  256. package/dist/index.js.map +0 -209
  257. package/src/random/string.ts +0 -35
  258. 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
+ })