@planet-matrix/mobius-model 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (233) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/index.js +706 -36
  3. package/dist/index.js.map +855 -59
  4. package/package.json +28 -16
  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/schedule.ts +111 -0
  29. package/src/basic/stream.ts +135 -25
  30. package/src/credential/README.md +107 -0
  31. package/src/credential/api-key.ts +158 -0
  32. package/src/credential/bearer.ts +73 -0
  33. package/src/credential/index.ts +4 -0
  34. package/src/credential/json-web-token.ts +96 -0
  35. package/src/credential/password.ts +170 -0
  36. package/src/cron/README.md +86 -0
  37. package/src/cron/cron.ts +87 -0
  38. package/src/cron/index.ts +1 -0
  39. package/src/drizzle/README.md +1 -0
  40. package/src/drizzle/drizzle.ts +1 -0
  41. package/src/drizzle/helper.ts +47 -0
  42. package/src/drizzle/index.ts +5 -0
  43. package/src/drizzle/infer.ts +52 -0
  44. package/src/drizzle/kysely.ts +8 -0
  45. package/src/drizzle/pagination.ts +200 -0
  46. package/src/email/README.md +1 -0
  47. package/src/email/index.ts +1 -0
  48. package/src/email/resend.ts +25 -0
  49. package/src/event/class-event-proxy.ts +6 -5
  50. package/src/event/common.ts +13 -3
  51. package/src/event/event-manager.ts +3 -3
  52. package/src/event/instance-event-proxy.ts +6 -5
  53. package/src/event/internal.ts +4 -4
  54. package/src/form/README.md +25 -0
  55. package/src/form/index.ts +1 -0
  56. package/src/form/inputor-controller/base.ts +874 -0
  57. package/src/form/inputor-controller/boolean.ts +39 -0
  58. package/src/form/inputor-controller/file.ts +39 -0
  59. package/src/form/inputor-controller/form.ts +181 -0
  60. package/src/form/inputor-controller/helper.ts +117 -0
  61. package/src/form/inputor-controller/index.ts +17 -0
  62. package/src/form/inputor-controller/multi-select.ts +99 -0
  63. package/src/form/inputor-controller/number.ts +116 -0
  64. package/src/form/inputor-controller/select.ts +109 -0
  65. package/src/form/inputor-controller/text.ts +82 -0
  66. package/src/http/READMD.md +1 -0
  67. package/src/http/api/api-core.ts +84 -0
  68. package/src/http/api/api-handler.ts +79 -0
  69. package/src/http/api/api-host.ts +47 -0
  70. package/src/http/api/api-result.ts +56 -0
  71. package/src/http/api/api-schema.ts +154 -0
  72. package/src/http/api/api-server.ts +130 -0
  73. package/src/http/api/api-test.ts +142 -0
  74. package/src/http/api/api-type.ts +37 -0
  75. package/src/http/api/api.ts +81 -0
  76. package/src/http/api/index.ts +11 -0
  77. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  78. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  79. package/src/http/api-adapter/api-result-arktype.ts +297 -0
  80. package/src/http/api-adapter/api-result-zod.ts +286 -0
  81. package/src/http/api-adapter/index.ts +5 -0
  82. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  83. package/src/http/bin/gen-api-list/index.ts +1 -0
  84. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  85. package/src/http/bin/gen-api-test/index.ts +1 -0
  86. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  87. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  88. package/src/http/bin/gen-api-type/index.ts +2 -0
  89. package/src/http/bin/index.ts +2 -0
  90. package/src/http/index.ts +3 -0
  91. package/src/huawei/README.md +1 -0
  92. package/src/huawei/index.ts +2 -0
  93. package/src/huawei/moderation/index.ts +1 -0
  94. package/src/huawei/moderation/moderation.ts +355 -0
  95. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  96. package/src/huawei/obs/index.ts +1 -0
  97. package/src/huawei/obs/obs.ts +42 -0
  98. package/src/index.ts +19 -2
  99. package/src/json/README.md +92 -0
  100. package/src/json/index.ts +1 -0
  101. package/src/json/repair.ts +18 -0
  102. package/src/log/logger.ts +15 -4
  103. package/src/openai/README.md +1 -0
  104. package/src/openai/index.ts +1 -0
  105. package/src/openai/openai.ts +510 -0
  106. package/src/orchestration/README.md +9 -7
  107. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  108. package/src/orchestration/dispatching/index.ts +2 -0
  109. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  110. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  111. package/src/orchestration/dispatching/selector/index.ts +2 -0
  112. package/src/orchestration/index.ts +2 -0
  113. package/src/orchestration/scheduling/index.ts +2 -0
  114. package/src/orchestration/scheduling/scheduler.ts +103 -0
  115. package/src/orchestration/scheduling/task.ts +32 -0
  116. package/src/random/README.md +8 -7
  117. package/src/random/base.ts +66 -0
  118. package/src/random/index.ts +5 -1
  119. package/src/random/random-boolean.ts +40 -0
  120. package/src/random/random-integer.ts +60 -0
  121. package/src/random/random-number.ts +72 -0
  122. package/src/random/random-string.ts +66 -0
  123. package/src/request/README.md +108 -0
  124. package/src/request/fetch/base.ts +108 -0
  125. package/src/request/fetch/browser.ts +285 -0
  126. package/src/request/fetch/general.ts +20 -0
  127. package/src/request/fetch/index.ts +4 -0
  128. package/src/request/fetch/nodejs.ts +285 -0
  129. package/src/request/index.ts +2 -0
  130. package/src/request/request/base.ts +250 -0
  131. package/src/request/request/general.ts +64 -0
  132. package/src/request/request/index.ts +3 -0
  133. package/src/request/request/resource.ts +68 -0
  134. package/src/result/README.md +4 -0
  135. package/src/result/controller.ts +54 -0
  136. package/src/result/either.ts +193 -0
  137. package/src/result/index.ts +2 -0
  138. package/src/route/README.md +105 -0
  139. package/src/route/adapter/browser.ts +122 -0
  140. package/src/route/adapter/driver.ts +56 -0
  141. package/src/route/adapter/index.ts +2 -0
  142. package/src/route/index.ts +3 -0
  143. package/src/route/router/index.ts +2 -0
  144. package/src/route/router/route.ts +630 -0
  145. package/src/route/router/router.ts +1642 -0
  146. package/src/route/uri/hash.ts +308 -0
  147. package/src/route/uri/index.ts +7 -0
  148. package/src/route/uri/pathname.ts +376 -0
  149. package/src/route/uri/search.ts +413 -0
  150. package/src/socket/README.md +105 -0
  151. package/src/socket/client/index.ts +2 -0
  152. package/src/socket/client/socket-unit.ts +660 -0
  153. package/src/socket/client/socket.ts +203 -0
  154. package/src/socket/common/index.ts +2 -0
  155. package/src/socket/common/socket-unit-common.ts +23 -0
  156. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  157. package/src/socket/index.ts +3 -0
  158. package/src/socket/server/index.ts +3 -0
  159. package/src/socket/server/server.ts +183 -0
  160. package/src/socket/server/socket-unit.ts +449 -0
  161. package/src/socket/server/socket.ts +264 -0
  162. package/src/storage/table.ts +3 -3
  163. package/src/timer/expiration/expiration-manager.ts +3 -3
  164. package/src/timer/expiration/remaining-manager.ts +3 -3
  165. package/src/tube/README.md +99 -0
  166. package/src/tube/helper.ts +138 -0
  167. package/src/tube/index.ts +2 -0
  168. package/src/tube/tube.ts +880 -0
  169. package/src/weixin/README.md +1 -0
  170. package/src/weixin/index.ts +2 -0
  171. package/src/weixin/official-account/authorization.ts +159 -0
  172. package/src/weixin/official-account/index.ts +2 -0
  173. package/src/weixin/official-account/js-api.ts +134 -0
  174. package/src/weixin/open/index.ts +1 -0
  175. package/src/weixin/open/oauth2.ts +133 -0
  176. package/tests/unit/ai/ai.spec.ts +85 -0
  177. package/tests/unit/aio/content.spec.ts +105 -0
  178. package/tests/unit/aio/json.spec.ts +147 -0
  179. package/tests/unit/aio/prompt.spec.ts +111 -0
  180. package/tests/unit/basic/error.spec.ts +16 -4
  181. package/tests/unit/basic/schedule.spec.ts +74 -0
  182. package/tests/unit/basic/stream.spec.ts +90 -37
  183. package/tests/unit/credential/api-key.spec.ts +37 -0
  184. package/tests/unit/credential/bearer.spec.ts +23 -0
  185. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  186. package/tests/unit/credential/password.spec.ts +41 -0
  187. package/tests/unit/cron/cron.spec.ts +84 -0
  188. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  189. package/tests/unit/event/event-manager.spec.ts +3 -3
  190. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  191. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  192. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  193. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  194. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  195. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  196. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  197. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  198. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  199. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  200. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  201. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  202. package/tests/unit/http/api/api-server.spec.ts +363 -0
  203. package/tests/unit/http/api/api-test.spec.ts +117 -0
  204. package/tests/unit/http/api/api.spec.ts +121 -0
  205. package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
  206. package/tests/unit/json/repair.spec.ts +11 -0
  207. package/tests/unit/log/logger.spec.ts +19 -4
  208. package/tests/unit/openai/openai.spec.ts +64 -0
  209. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  210. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  211. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  212. package/tests/unit/random/base.spec.ts +58 -0
  213. package/tests/unit/random/random-boolean.spec.ts +25 -0
  214. package/tests/unit/random/random-integer.spec.ts +32 -0
  215. package/tests/unit/random/random-number.spec.ts +33 -0
  216. package/tests/unit/random/random-string.spec.ts +22 -0
  217. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  218. package/tests/unit/request/fetch/general.spec.ts +43 -0
  219. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  220. package/tests/unit/request/request/base.spec.ts +385 -0
  221. package/tests/unit/request/request/general.spec.ts +161 -0
  222. package/tests/unit/route/router/route.spec.ts +431 -0
  223. package/tests/unit/route/router/router.spec.ts +407 -0
  224. package/tests/unit/route/uri/hash.spec.ts +72 -0
  225. package/tests/unit/route/uri/pathname.spec.ts +147 -0
  226. package/tests/unit/route/uri/search.spec.ts +107 -0
  227. package/tests/unit/socket/client.spec.ts +208 -0
  228. package/tests/unit/socket/server.spec.ts +135 -0
  229. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  230. package/tests/unit/tube/helper.spec.ts +139 -0
  231. package/tests/unit/tube/tube.spec.ts +501 -0
  232. package/src/random/string.ts +0 -35
  233. package/tests/unit/random/string.spec.ts +0 -11
@@ -0,0 +1,250 @@
1
+ import type { BuildEvents } from "#Source/event/index.ts"
2
+ import { EventManager } from "#Source/event/index.ts"
3
+ import { isError, errorIsNetworkError, errorStringify } from "#Source/basic/index.ts"
4
+
5
+ import type { BaseFetch, FetchResponseType, FetchFactory } from "../fetch/index.ts"
6
+ import { generalFetch } from "../fetch/index.ts"
7
+ import type {
8
+ Resource,
9
+ ResourceOutput,
10
+ ResourceSuccessOutput,
11
+ ResourceErrorOutput,
12
+ ResourceSuccessData,
13
+ ResourceErrorData,
14
+ ResourcePatchOutput,
15
+ } from "./resource.ts"
16
+
17
+ /**
18
+ * 描述请求执行过程中会派发的标准事件集合。
19
+ */
20
+ export type BaseRequestEvents<R extends Resource> = BuildEvents<{
21
+ output: (output: ResourceOutput<R>) => void
22
+ successOutput: (successOutput: ResourceSuccessOutput<R>) => void
23
+ successData: (successData: ResourceSuccessData<R>) => void
24
+ errorOutput: (errorOutput: ResourceErrorOutput<R>) => void
25
+ errorData: (errorData: ResourceErrorData<R>) => void
26
+ patchOutput: (patchOutput: ResourcePatchOutput<R>) => void
27
+ }>
28
+
29
+ /**
30
+ * 实际执行请求动作的选项。
31
+ *
32
+ * 该类型会根据资源声明中 `input.query` 与 `input.body` 是否可选,
33
+ * 自动收紧当前路径对应请求选项里的必填性。
34
+ */
35
+ export type RequestOptions<Path, R extends Resource>
36
+ = {
37
+ baseUrl: Extract<R, { path: Path }>["baseUrl"]
38
+ path: Extract<R, { path: Path }>["path"]
39
+ method: Extract<R, { path: Path }>["method"]
40
+ headers?: Record<string, string> | undefined
41
+ responseType?: FetchResponseType | undefined
42
+ timeout?: number | undefined
43
+ abortSignal?: AbortSignal | undefined
44
+ } & (
45
+ undefined extends Extract<R, { path: Path }>["input"]["query"]
46
+ ? { query?: Extract<R, { path: Path }>["input"]["query"] | undefined }
47
+ : { query: Extract<R, { path: Path }>["input"]["query"] }
48
+ ) & (
49
+ undefined extends Extract<R, { path: Path }>["input"]["body"]
50
+ ? { body?: Extract<R, { path: Path }>["input"]["body"] | undefined }
51
+ : { body: Extract<R, { path: Path }>["input"]["body"] }
52
+ ) & {
53
+ onOutput?: ((output: ResourceOutput<Extract<R, { path: Path }>>) => void) | undefined
54
+ onSuccessOutput?: ((successOutput: ResourceSuccessOutput<Extract<R, { path: Path }>>) => void) | undefined
55
+ onSuccessData?: ((successData: ResourceSuccessData<Extract<R, { path: Path }>>) => void) | undefined
56
+ onErrorOutput?: ((errorOutput: ResourceErrorOutput<Extract<R, { path: Path }>>) => void) | undefined
57
+ onErrorData?: ((errorData: ResourceErrorData<Extract<R, { path: Path }>>) => void) | undefined
58
+ onPatchOutput?: ((patchOutput: ResourcePatchOutput<Extract<R, { path: Path }>>) => void) | undefined
59
+ }
60
+
61
+ /**
62
+ * 实际执行请求动作的结果。
63
+ */
64
+ export type RequestResult<Path, R extends Resource>
65
+ = ResourceOutput<Extract<R, { path: Path }>>
66
+ | ResourcePatchOutput<Extract<R, { path: Path }>>
67
+
68
+ /**
69
+ * 表示请求失败时的错误归类。
70
+ */
71
+ export type ErrorKind = "network" | "unknown"
72
+
73
+ /**
74
+ * 生成补丁输出时可使用的上下文。
75
+ */
76
+ export type GeneratePatchOutputContext = {
77
+ type: "error"
78
+ errorKind: ErrorKind
79
+ error: Error
80
+ }
81
+
82
+ /**
83
+ * 构造专用请求类的选项。
84
+ */
85
+ export interface BaseRequestOptions<R extends Resource> {
86
+ /**
87
+ * 自定义请求执行器工厂;未提供时使用默认的通用运行时分派实现。
88
+ */
89
+ fetchFactory?: FetchFactory | undefined
90
+
91
+ /**
92
+ * 在真正发起请求前,对标准化请求选项做最后一次统一调整。
93
+ */
94
+ modifyRequestOptions?: ((options: RequestOptions<R["path"], R>) => RequestOptions<R["path"], R>) | undefined
95
+
96
+ /**
97
+ * 在请求失败时生成仍可被上层继续消费的补丁输出。
98
+ */
99
+ generatePatchOutput: (context: GeneratePatchOutputContext) => ResourcePatchOutput<R>
100
+ }
101
+
102
+ /**
103
+ * 基于资源声明组织请求、输出分类与失败补丁的抽象请求类。
104
+ *
105
+ * `Resource` 类型参数应传入资源声明的联合类型或元组成员联合。
106
+ * 默认实现假定 JSON 输出可通过 `status` 字段区分为 `success` 与 `error`,
107
+ * 并通过 `data` 字段提取真正的成功数据或错误数据;若资源输出不满足这组约定,
108
+ * 则应由子类覆写对应的分类与提取方法。
109
+ */
110
+ export abstract class BaseRequest<R extends Resource = Resource> {
111
+ protected options: BaseRequestOptions<R>
112
+
113
+ /**
114
+ * 对外暴露请求生命周期事件的事件管理器。
115
+ */
116
+ event: EventManager<BaseRequestEvents<R>>
117
+
118
+ constructor(options: BaseRequestOptions<R>) {
119
+ this.options = options
120
+ this.event = new EventManager()
121
+ }
122
+
123
+ /**
124
+ * 判断当前输出是否应被视为成功输出。
125
+ */
126
+ protected isSuccessDataResponse(response: ResourceOutput<R>): boolean {
127
+ return response['status'] === "success"
128
+ }
129
+
130
+ /**
131
+ * 从成功输出中提取成功数据。
132
+ */
133
+ protected extractSuccessData(response: ResourceSuccessOutput<R>): ResourceSuccessData<R> {
134
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
135
+ return response['data'] as ResourceSuccessData<R>
136
+ }
137
+
138
+ /**
139
+ * 判断当前输出是否应被视为错误输出。
140
+ */
141
+ protected isErrorDataResponse(response: ResourceOutput<R>): boolean {
142
+ return response['status'] === "error"
143
+ }
144
+
145
+ /**
146
+ * 从错误输出中提取错误数据。
147
+ */
148
+ protected extractErrorData(response: ResourceErrorOutput<R>): ResourceErrorData<R> {
149
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
150
+ return response['data'] as ResourceErrorData<R>
151
+ }
152
+
153
+ /**
154
+ * 按资源路径约束执行一次请求,并在成功或失败时派发对应事件。
155
+ */
156
+ async request<Path extends R["path"]>(
157
+ options: RequestOptions<Path, R>,
158
+ ): Promise<RequestResult<Path, R>> {
159
+ const { fetchFactory, modifyRequestOptions } = this.options
160
+
161
+ const preparedOptions = modifyRequestOptions !== undefined
162
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
163
+ ? modifyRequestOptions(options as RequestOptions<R["path"], R>)
164
+ : options
165
+
166
+ const {
167
+ responseType = "json",
168
+ onOutput,
169
+ onSuccessOutput,
170
+ onSuccessData,
171
+ onErrorOutput,
172
+ onErrorData,
173
+ onPatchOutput,
174
+ } = preparedOptions
175
+
176
+ try {
177
+ type FetchInput = {
178
+ query: Extract<R, { path: Path }>["input"]["query"]
179
+ body: Extract<R, { path: Path }>["input"]["body"]
180
+ }
181
+ type FetchOutput = {
182
+ [K in typeof responseType]: {
183
+ type: K
184
+ data: ResourceOutput<Extract<R, { path: Path }>>
185
+ }
186
+ }[typeof responseType]
187
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
188
+ const preparedFetch = (fetchFactory ?? generalFetch)(preparedOptions) as BaseFetch<FetchInput, FetchOutput>
189
+
190
+ if (responseType === "json") {
191
+ const json = await preparedFetch.getJson()
192
+ onOutput?.(json)
193
+ this.event.emit("output", json)
194
+
195
+ // do we have a better way to consume the success data?
196
+ if (this.isSuccessDataResponse(json)) {
197
+ onSuccessOutput?.(json)
198
+ this.event.emit("successOutput", json)
199
+ const successData = this.extractSuccessData(json)
200
+ onSuccessData?.(successData)
201
+ this.event.emit("successData", successData)
202
+ }
203
+
204
+ if (this.isErrorDataResponse(json)) {
205
+ onErrorOutput?.(json)
206
+ this.event.emit("errorOutput", json)
207
+ const errorData = this.extractErrorData(json)
208
+ onErrorData?.(errorData)
209
+ this.event.emit("errorData", errorData)
210
+ }
211
+
212
+ return json
213
+ }
214
+
215
+ throw new Error("Unsupported response type, please use \"json\" or improve implementation of BaseRequest.")
216
+ }
217
+ catch (exception) {
218
+ if (errorIsNetworkError(exception)) {
219
+ const patchOutput = this.options.generatePatchOutput({
220
+ type: "error",
221
+ errorKind: "network",
222
+ error: exception
223
+ })
224
+ onPatchOutput?.(patchOutput)
225
+ this.event.emit("patchOutput", patchOutput)
226
+ return patchOutput
227
+ }
228
+ else if (isError(exception)) {
229
+ const patchOutput = this.options.generatePatchOutput({
230
+ type: "error",
231
+ errorKind: "unknown",
232
+ error: exception
233
+ })
234
+ onPatchOutput?.(patchOutput)
235
+ this.event.emit("patchOutput", patchOutput)
236
+ return patchOutput
237
+ }
238
+ else {
239
+ const patchOutput = this.options.generatePatchOutput({
240
+ type: "error",
241
+ errorKind: "unknown",
242
+ error: new Error(errorStringify(exception))
243
+ })
244
+ onPatchOutput?.(patchOutput)
245
+ this.event.emit("patchOutput", patchOutput)
246
+ return patchOutput
247
+ }
248
+ }
249
+ }
250
+ }
@@ -0,0 +1,64 @@
1
+ import type { ResourcePatchOutput } from "./resource.ts"
2
+ import type { BaseRequestOptions, GeneratePatchOutputContext } from "./base.ts"
3
+ import { BaseRequest } from "./base.ts"
4
+
5
+ /**
6
+ * 描述适用于通用 `{ status, data }` 输出结构的资源声明。
7
+ */
8
+ export interface GeneralResource {
9
+ baseUrl: string
10
+ path: string
11
+ method: "get" | "post"
12
+ input: {
13
+ query?: Record<string, unknown> | undefined
14
+ body?: Record<string, unknown> | undefined
15
+ }
16
+ successData: Record<string, unknown>
17
+ errorData: Record<string, unknown>
18
+ }
19
+
20
+ /**
21
+ * 把通用资源声明补齐为 `BaseRequest` 可直接消费的完整资源契约。
22
+ */
23
+ export type GeneralResourceToResource<R extends GeneralResource> = R & {
24
+ output: { status: "success", data: R["successData"] } | { status: "error", data: R["errorData"] }
25
+ successOutput: { status: "success", data: R["successData"] }
26
+ errorOutput: { status: "error", data: R["errorData"] }
27
+ patchOutput: { status: "unknown", data: GeneratePatchOutputContext }
28
+ }
29
+
30
+ /**
31
+ * 构造通用请求类的选项。
32
+ *
33
+ * 该类型会沿用 `BaseRequestOptions` 的配置能力,只是由 `GeneralRequest`
34
+ * 自动补齐失败时的补丁输出生成逻辑。
35
+ */
36
+ export interface GeneralRequestOptions<R extends GeneralResource>
37
+ extends Omit<BaseRequestOptions<GeneralResourceToResource<R>>, "generatePatchOutput"> {
38
+ }
39
+
40
+ /**
41
+ * 面向通用 `{ status, data }` 输出结构的请求实现。
42
+ *
43
+ * `GeneralRequest` 仅适用于返回结构严格符合特定情况的目标资源,特定情况指:
44
+ *
45
+ * 1. 业务结果仅包含成功(success)和错误(error)两种状态。
46
+ * 2. 业务结果通过一个名为 `status` 的字段区分状态,并通过一个名为 `data` 的字段承载数据:
47
+ * `{ status: "success" | "error", data: Record<string, unknown> }`。
48
+ */
49
+ export class GeneralRequest<R extends GeneralResource = GeneralResource>
50
+ extends BaseRequest<GeneralResourceToResource<R>> {
51
+ constructor(options: GeneralRequestOptions<R>) {
52
+ const generatePatchOutput = (context: GeneratePatchOutputContext): ResourcePatchOutput<GeneralResourceToResource<R>> => {
53
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
54
+ return {
55
+ status: "unknown",
56
+ data: context
57
+ } as ResourcePatchOutput<GeneralResourceToResource<R>>
58
+ }
59
+ super({
60
+ ...options,
61
+ generatePatchOutput
62
+ })
63
+ }
64
+ }
@@ -0,0 +1,3 @@
1
+ export type * from "./resource.ts"
2
+ export * from "./base.ts"
3
+ export * from "./general.ts"
@@ -0,0 +1,68 @@
1
+ /**
2
+ * 描述一个远程资源契约。
3
+ *
4
+ * 这个接口用于把某一路径、方法、输入、输出、成功数据、错误数据与失败补丁输出
5
+ * 收束为一份可长期维护的类型层声明,供 `BaseRequest` 及其派生实现消费。
6
+ */
7
+ export interface Resource {
8
+ baseUrl: string
9
+ path: string
10
+ method: "get" | "post"
11
+ input: {
12
+ query?: Record<string, unknown> | undefined
13
+ body?: Record<string, unknown> | undefined
14
+ }
15
+ output: Record<string, unknown>
16
+
17
+ successOutput: Record<string, unknown>
18
+ successData: Record<string, unknown>
19
+ errorOutput: Record<string, unknown>
20
+ errorData: Record<string, unknown>
21
+
22
+ patchOutput: Record<string, unknown>
23
+ }
24
+
25
+ /**
26
+ * 提取资源声明中的基础 URL 类型。
27
+ */
28
+ export type ResourceBaseUrl<R extends Resource> = R["baseUrl"]
29
+
30
+ /**
31
+ * 提取资源声明中的路径类型。
32
+ */
33
+ export type ResourcePath<R extends Resource> = R["path"]
34
+
35
+ /**
36
+ * 提取资源声明中的请求方法类型。
37
+ */
38
+ export type ResourceMethod<R extends Resource> = R["method"]
39
+
40
+ /**
41
+ * 提取资源声明中的完整输出类型。
42
+ */
43
+ export type ResourceOutput<R extends Resource> = R["output"]
44
+
45
+ /**
46
+ * 提取资源声明中的成功输出类型。
47
+ */
48
+ export type ResourceSuccessOutput<R extends Resource> = R["successOutput"]
49
+
50
+ /**
51
+ * 提取资源声明中的错误输出类型。
52
+ */
53
+ export type ResourceErrorOutput<R extends Resource> = R["errorOutput"]
54
+
55
+ /**
56
+ * 提取资源声明中的成功数据类型。
57
+ */
58
+ export type ResourceSuccessData<R extends Resource> = R["successData"]
59
+
60
+ /**
61
+ * 提取资源声明中的错误数据类型。
62
+ */
63
+ export type ResourceErrorData<R extends Resource> = R["errorData"]
64
+
65
+ /**
66
+ * 提取资源声明中的补丁输出类型。
67
+ */
68
+ export type ResourcePatchOutput<R extends Resource> = R["patchOutput"]
@@ -0,0 +1,4 @@
1
+ # Result
2
+
3
+ @see https://github.com/supermacro/neverthrow
4
+ @see https://better-result.dev/introduction
@@ -0,0 +1,54 @@
1
+ import type { Either } from "./either.ts"
2
+ import { Left, Right } from "./either.ts"
3
+
4
+ export class Controller<L, R> {
5
+ static isLeft<L, R>(target: unknown): target is Left<L, R> {
6
+ return Left.isLeft(target)
7
+ }
8
+
9
+ static isRight<L, R>(target: unknown): target is Right<L, R> {
10
+ return Right.isRight(target)
11
+ }
12
+
13
+ private promise: Promise<Either<L, R>>
14
+ private resolve: (value: Either<L, R>) => void
15
+
16
+ constructor() {
17
+ let _resolve: (value: Either<L, R>) => void
18
+ this.promise = new Promise((resolve) => {
19
+ _resolve = resolve
20
+ })
21
+ this.resolve = _resolve!
22
+ }
23
+
24
+ get value(): Promise<Either<L, R>> {
25
+ return this.promise
26
+ }
27
+
28
+ async returnRight(value: R): Promise<Right<L, R>> {
29
+ const right = new Right<L, R>(value)
30
+ this.resolve(right)
31
+ return await Promise.resolve(right)
32
+ }
33
+
34
+ async returnLeft(value: L): Promise<Left<L, R>> {
35
+ const left = new Left<L, R>(value)
36
+ this.resolve(left)
37
+ return await Promise.resolve(left)
38
+ }
39
+
40
+ // oxlint-disable-next-line require-await
41
+ async throw(value: L): Promise<void> {
42
+ const left = new Left<L, R>(value)
43
+ this.resolve(left)
44
+ // oxlint-disable-next-line typescript/only-throw-error
45
+ throw left
46
+ }
47
+ }
48
+
49
+ type ExtractLeft<E> = E extends Either<infer L, infer _> ? L : never
50
+ type ExtractRight<E> = E extends Either<infer _, infer R> ? R : never
51
+ export const controllerFromEitherType = <E extends Either<unknown, unknown>>(
52
+ ): Controller<ExtractLeft<E>, ExtractRight<E>> => {
53
+ return new Controller<ExtractLeft<E>, ExtractRight<E>>()
54
+ }
@@ -0,0 +1,193 @@
1
+
2
+ // oxlint-disable-next-line typescript/no-explicit-any
3
+ type AnyEither = Either<any, any>
4
+ type GetRight<Eithers> = Eithers extends []
5
+ ? []
6
+ : (
7
+ Eithers extends [infer AssumeEither, ...infer xs]
8
+ ? (
9
+ AssumeEither extends Either<unknown, infer Right>
10
+ ? [Right, ...GetRight<xs>]
11
+ : never
12
+ )
13
+ : never
14
+ )
15
+ type GetLeft<Eithers> = Eithers extends []
16
+ ? []
17
+ : (
18
+ Eithers extends [infer AssumeEither, ...infer xs]
19
+ ? (
20
+ AssumeEither extends Either<infer Left, unknown>
21
+ ? [Left, ...GetLeft<xs>]
22
+ : never
23
+ )
24
+ : never
25
+ )
26
+ type TupleToUnion<Tuple> = Tuple extends unknown[] ? Tuple[number] : never
27
+
28
+ export abstract class Either<L, R> {
29
+ static left<L, R>(value: L): Either<L, R> {
30
+ return new Left(value)
31
+ }
32
+
33
+ static right<L, R>(value: R): Either<L, R> {
34
+ return new Right(value)
35
+ }
36
+
37
+ static pure<L, R>(value: R): Either<L, R> {
38
+ return new Right(value)
39
+ }
40
+
41
+ static lift<NewR, Eithers extends AnyEither[]>(
42
+ eithers: [...Eithers],
43
+ map: (eitherRights: GetRight<Eithers>) => NewR,
44
+ ): Either<TupleToUnion<GetLeft<Eithers>>, NewR> {
45
+ const firstLeft = eithers.find(either => either.isLeft())
46
+ if (firstLeft !== undefined) {
47
+ return firstLeft as Either<TupleToUnion<GetLeft<Eithers>>, NewR>
48
+ }
49
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
50
+ const rights = eithers.map(either => {
51
+ // oxlint-disable-next-line typescript/no-unsafe-return
52
+ return either.assertRight().getRight()
53
+ }) as GetRight<Eithers>
54
+ const newRightValue = map(rights)
55
+ return Either.pure(newRightValue)
56
+ }
57
+
58
+ abstract map<NewR>(map: (value: R) => NewR): Either<L, NewR>
59
+ abstract mapLeft<NewL>(map: (value: L) => NewL): Either<NewL, R>
60
+ abstract bind<NewR>(bind: (value: R) => Either<L, NewR>): Either<L, NewR>
61
+
62
+ abstract isLeft(): this is Left<L, R>
63
+ abstract isRight(): this is Right<L, R>
64
+ abstract getValue(): L | R
65
+
66
+ abstract assertLeft(): Left<L, R>
67
+ abstract assertRight(): Right<L, R>
68
+
69
+ abstract match<X>(onLeft: (value: L) => X, onRight: (value: R) => X): X
70
+
71
+ abstract mplus(either: Either<L, R>): Either<L, R>
72
+ }
73
+ export class Left<L, R> extends Either<L, R> {
74
+ static isLeft<L, R>(target: unknown): target is Left<L, R> {
75
+ return target instanceof Left
76
+ }
77
+
78
+ private value: L
79
+
80
+ constructor(value: L) {
81
+ super()
82
+ this.value = value
83
+ }
84
+
85
+ map<NewR>(_map: (value: R) => NewR): Either<L, NewR> {
86
+ return new Left(this.value)
87
+ }
88
+
89
+ mapLeft<NewL>(map: (value: L) => NewL): Either<NewL, R> {
90
+ return new Left(map(this.value))
91
+ }
92
+
93
+ bind<NewR>(_map: (value: R) => Either<L, NewR>): Either<L, NewR> {
94
+ return new Left(this.value)
95
+ }
96
+
97
+ isLeft(): this is Left<L, R> {
98
+ return true
99
+ }
100
+
101
+ isRight(): this is Right<L, R> {
102
+ return false
103
+ }
104
+
105
+ getLeft(): L {
106
+ return this.value
107
+ }
108
+
109
+ getValue(): L | R {
110
+ return this.value
111
+ }
112
+
113
+ assertLeft(): this {
114
+ return this
115
+ }
116
+
117
+ assertRight(): Right<L, R> {
118
+ throw new Error("Assert failed")
119
+ }
120
+
121
+ match<X>(onLeft: (value: L) => X, _onRight: (value: R) => X): X {
122
+ return onLeft(this.value)
123
+ }
124
+
125
+ mplus(either: Either<L, R>): Either<L, R> {
126
+ return either
127
+ }
128
+ }
129
+
130
+ export class Right<L, R> extends Either<L, R> {
131
+ static isRight<L, R>(target: unknown): target is Right<L, R> {
132
+ return target instanceof Right
133
+ }
134
+
135
+ private value: R
136
+
137
+ constructor(value: R) {
138
+ super()
139
+ this.value = value
140
+ }
141
+
142
+ map<NewR>(map: (value: R) => NewR): Either<L, NewR> {
143
+ return new Right(map(this.value))
144
+ }
145
+
146
+ mapLeft<NewL>(_map: (value: L) => NewL): Either<NewL, R> {
147
+ return new Right(this.value)
148
+ }
149
+
150
+ bind<NewR>(map: (value: R) => Either<L, NewR>): Either<L, NewR> {
151
+ return map(this.value)
152
+ }
153
+
154
+ isLeft(): this is Left<L, R> {
155
+ return false
156
+ }
157
+
158
+ isRight(): this is Right<L, R> {
159
+ return true
160
+ }
161
+
162
+ getRight(): R {
163
+ return this.value
164
+ }
165
+
166
+ getValue(): L | R {
167
+ return this.value
168
+ }
169
+
170
+ assertLeft(): Left<L, R> {
171
+ throw new Error("Assert failed")
172
+ }
173
+
174
+ assertRight(): this {
175
+ return this
176
+ }
177
+
178
+ match<X>(_onLeft: (value: L) => X, onRight: (value: R) => X): X {
179
+ return onRight(this.value)
180
+ }
181
+
182
+ mplus(_either: Either<L, R>): Either<L, R> {
183
+ return this
184
+ }
185
+ }
186
+
187
+ export type TupleOfEither<Left, Right> = [undefined, Right] | [Left, undefined]
188
+ export const eitherToTuple = <L, R>(either: Either<L, R>): TupleOfEither<L, R> => {
189
+ const left = either.isLeft() ? either.getLeft() : undefined
190
+ const right = either.isRight() ? either.getRight() : undefined
191
+ // oxlint-disable-next-line typescript/no-unsafe-type-assertion
192
+ return [left, right] as TupleOfEither<L, R>
193
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./either.ts"
2
+ export * from "./controller.ts"