@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,286 @@
1
+ import { z } from "zod"
2
+
3
+ import { generateUuidV4 } from "#Source/identifier/uuid.ts"
4
+
5
+ import type { ApiCore, ApiCoreStream } from "../api/api-core.ts"
6
+ import type { SuccessResultOptions, ErrorResultOptions } from "../api/api-result.ts"
7
+ import type { ApiHandlerContext } from "../api/api-handler.ts"
8
+
9
+ import { SuccessResult, ErrorResult } from "../api/api-result.ts"
10
+
11
+ export interface JsonSuccessResultOptions<Data> extends SuccessResultOptions<Data> {
12
+ data: Data
13
+ }
14
+ export class JsonSuccessResult<Data> extends SuccessResult<Data> {
15
+ protected declare options: JsonSuccessResultOptions<Data>
16
+
17
+ constructor(options: JsonSuccessResultOptions<Data>) {
18
+ super(options)
19
+ this.logger.setDefaultName("JsonSuccessResult")
20
+ }
21
+
22
+ async handle(apiCore: ApiCore): Promise<void> {
23
+ const { data } = this.options
24
+
25
+ this.logger.log(`send, ${JSON.stringify(data, null, 2)}`)
26
+ await apiCore.json(data)
27
+ this.logger.log(`end`)
28
+ }
29
+ }
30
+
31
+ export interface CommonSuccessJson<Data> {
32
+ status: "success"
33
+ data: Data
34
+ }
35
+ export interface CommonJsonSuccessResultOptions<Data>
36
+ extends Omit<JsonSuccessResultOptions<CommonSuccessJson<Data>>, "data"> {
37
+ data: Data
38
+ }
39
+ export class CommonJsonSuccessResult<Data>
40
+ extends JsonSuccessResult<CommonSuccessJson<Data>> {
41
+ protected declare options: JsonSuccessResultOptions<CommonSuccessJson<Data>>
42
+
43
+ constructor(options: CommonJsonSuccessResultOptions<Data>) {
44
+ super({
45
+ ...options,
46
+ data: {
47
+ status: "success",
48
+ data: options.data,
49
+ },
50
+ })
51
+ this.logger.setDefaultName("CommonJsonSuccessResult")
52
+ }
53
+ }
54
+ export const commonJsonSuccessResult = <Data>(
55
+ options: CommonJsonSuccessResultOptions<Data>
56
+ ): CommonJsonSuccessResult<Data> => {
57
+ return new CommonJsonSuccessResult<Data>(options)
58
+ }
59
+
60
+ export type CommonJsonSuccessResultSchema<DataSchema extends z.ZodObject> = z.ZodObject<{
61
+ status: z.ZodLiteral<"success">
62
+ data: DataSchema
63
+ }>
64
+ export type GetDataSchemaFromCommonJsonSuccessResultSchema<Schema extends z.ZodType>
65
+ = Schema extends CommonJsonSuccessResultSchema<infer DataSchema> ? DataSchema : never
66
+ export type GetDataFromCommonJsonSuccessResultSchema<Schema extends z.ZodType>
67
+ = Schema extends CommonJsonSuccessResultSchema<infer DataSchema> ? z.infer<DataSchema> : never
68
+ export const commonJsonSuccessResultSchema = <DataSchema extends z.ZodObject>(
69
+ dataSchema: DataSchema,
70
+ ): CommonJsonSuccessResultSchema<DataSchema> => {
71
+ return z.object({
72
+ status: z.literal("success"),
73
+ data: dataSchema,
74
+ })
75
+ }
76
+
77
+ export type StreamWrite = (chunk: Buffer | string, encoding: BufferEncoding) => void | Promise<void>
78
+ export type StreamEnd = () => void | Promise<void>
79
+ export interface StreamSuccessResultOptions extends SuccessResultOptions<unknown> {
80
+ writeHandler: (write: StreamWrite) => Promise<void>
81
+ }
82
+ export class StreamSuccessResult extends SuccessResult<unknown> {
83
+ protected declare options: StreamSuccessResultOptions
84
+
85
+ protected streamId: string
86
+
87
+ constructor(options: StreamSuccessResultOptions) {
88
+ super(options)
89
+ this.logger.setDefaultName("StreamSuccessResult")
90
+ this.streamId = generateUuidV4()
91
+ }
92
+
93
+ async handle(apiCore: ApiCore): Promise<void> {
94
+ const apiCoreStream = await apiCore.stream()
95
+
96
+ const write = this.wrapWrite(apiCoreStream)
97
+ await this.options.writeHandler(write)
98
+ const end = this.wrapEnd(apiCoreStream)
99
+ await end()
100
+ }
101
+
102
+ /**
103
+ * 把 write 方法包装一下,以达到注入日志等目的。
104
+ */
105
+ protected wrapWrite(apiCoreStream: ApiCoreStream): StreamWrite {
106
+ return async (chunk, encoding): Promise<void> => {
107
+ this.logger.log(`send, id: ${this.streamId}, data: ${String(chunk)}`)
108
+ await apiCoreStream.write(chunk, encoding)
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 把 end 方法包装一下,以达到注入日志等目的。
114
+ */
115
+ protected wrapEnd(apiCoreStream: ApiCoreStream): StreamEnd {
116
+ const end = async (): Promise<void> => {
117
+ this.logger.log(`end, id: ${this.streamId}`)
118
+ await apiCoreStream.end()
119
+ }
120
+ return end
121
+ }
122
+ }
123
+
124
+ export type CustomHandler = (apiCore: ApiCore) => Promise<void>
125
+ export interface CustomSuccessResultOptions extends SuccessResultOptions<unknown> {
126
+ customHandler: CustomHandler
127
+ }
128
+ export class CustomSuccessResult extends SuccessResult<unknown> {
129
+ protected declare options: CustomSuccessResultOptions
130
+
131
+ constructor(options: CustomSuccessResultOptions) {
132
+ super(options)
133
+ this.logger.setDefaultName("CustomSuccessResult")
134
+ }
135
+
136
+ async handle(apiCore: ApiCore): Promise<void> {
137
+ this.logger.log(`send`)
138
+ await this.options.customHandler(apiCore)
139
+ this.logger.log(`end`)
140
+ }
141
+ }
142
+
143
+ // ======================
144
+
145
+ export interface JsonErrorResultOptions<Data> extends ErrorResultOptions<Data> {
146
+ data: Data
147
+ }
148
+ export class JsonErrorResult<Data> extends ErrorResult<Data> {
149
+ protected declare options: JsonErrorResultOptions<Data>
150
+
151
+ constructor(options: JsonErrorResultOptions<Data>) {
152
+ super(options)
153
+ this.logger.setDefaultName("JsonErrorResult")
154
+ }
155
+
156
+ async handle(apiCore: ApiCore): Promise<void> {
157
+ const { data } = this.options
158
+
159
+ this.logger.log(`send, ${JSON.stringify(data, null, 2)}`)
160
+ await apiCore.json(data)
161
+ this.logger.log(`end`)
162
+ }
163
+ }
164
+
165
+ export interface CommonErrorJsonData<Reason> {
166
+ reason: Reason
167
+ reasonDetail?: string | undefined
168
+ }
169
+ export interface CommonErrorJson<Reason> {
170
+ status: "error"
171
+ data: CommonErrorJsonData<Reason>
172
+ }
173
+ export interface CommonJsonErrorResultOptions<Reason>
174
+ extends Omit<JsonErrorResultOptions<CommonErrorJson<Reason>>, "data">,
175
+ CommonErrorJsonData<Reason> {
176
+ }
177
+ export class CommonJsonErrorResult<const Reason>
178
+ extends JsonErrorResult<CommonErrorJson<Reason>> {
179
+ protected declare options: JsonErrorResultOptions<CommonErrorJson<Reason>>
180
+
181
+ constructor(options: CommonJsonErrorResultOptions<Reason>) {
182
+ super({
183
+ ...options,
184
+ data: {
185
+ status: "error",
186
+ data: {
187
+ reason: options.reason,
188
+ reasonDetail: options.reasonDetail,
189
+ },
190
+ },
191
+ })
192
+ this.logger.setDefaultName("CommonJsonErrorResult")
193
+ }
194
+ }
195
+ export const commonJsonErrorResult = <const Reason>(
196
+ options: CommonJsonErrorResultOptions<Reason>
197
+ ): CommonJsonErrorResult<Reason> => {
198
+ return new CommonJsonErrorResult<Reason>(options)
199
+ }
200
+
201
+ export type CommonJsonErrorResultSchema<
202
+ DataSchema extends z.ZodObject<{
203
+ reason: z.ZodEnum<Readonly<Record<string, string>>>
204
+ reasonDetail?: z.ZodString | z.ZodNull | z.ZodUndefined
205
+ }>
206
+ > = z.ZodObject<{
207
+ status: z.ZodLiteral<"error">
208
+ data: z.ZodObject<DataSchema["shape"] & {
209
+ reasonDetail: z.ZodString | z.ZodNull | z.ZodUndefined
210
+ }>
211
+ }>
212
+ export type GetDataSchemaFromCommonJsonErrorResultSchema<Schema extends z.ZodType>
213
+ = Schema extends CommonJsonErrorResultSchema<infer DataSchema> ? DataSchema : never
214
+ export type GetDataFromCommonJsonErrorResultSchema<Schema extends z.ZodType>
215
+ = Schema extends CommonJsonErrorResultSchema<infer DataSchema> ? z.infer<DataSchema> : never
216
+ export type GetReasonFromCommonJsonErrorResultSchema<Schema extends z.ZodType>
217
+ = Schema extends CommonJsonErrorResultSchema<infer DataSchema>
218
+ ? z.infer<DataSchema> extends { reason: infer Reason } ? Reason : never
219
+ : never
220
+ export const commonJsonErrorResultSchema = <
221
+ DataSchema extends z.ZodObject<{
222
+ reason: z.ZodEnum<Readonly<Record<string, string>>>
223
+ reasonDetail?: z.ZodString | z.ZodNull | z.ZodUndefined
224
+ }>
225
+ >(
226
+ dataSchema: DataSchema
227
+ ): CommonJsonErrorResultSchema<DataSchema> => {
228
+ const newOutputErrorSchema = z.object({
229
+ status: z.literal("error"),
230
+ data: dataSchema.extend({
231
+ reasonDetail: z.string(),
232
+ }),
233
+ })
234
+ return newOutputErrorSchema
235
+ }
236
+
237
+ // ======================
238
+
239
+ export type ContextWithResultHandler<
240
+ Path extends string,
241
+ Method extends "get" | "post",
242
+ InputQuerySchema extends z.ZodType,
243
+ InputBodySchema extends z.ZodType,
244
+ OutputSuccessSchema extends z.ZodType,
245
+ OutputErrorSchema extends z.ZodType,
246
+ >
247
+ = ApiHandlerContext<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>
248
+ & {
249
+ commonJsonSuccessResult: (
250
+ options: CommonJsonSuccessResultOptions<GetDataFromCommonJsonSuccessResultSchema<OutputSuccessSchema>>
251
+ ) => CommonJsonSuccessResult<GetDataFromCommonJsonSuccessResultSchema<OutputSuccessSchema>>
252
+ commonJsonErrorResult: (
253
+ options: CommonJsonErrorResultOptions<GetReasonFromCommonJsonErrorResultSchema<OutputErrorSchema>>
254
+ ) => CommonJsonErrorResult<GetReasonFromCommonJsonErrorResultSchema<OutputErrorSchema>>
255
+ }
256
+
257
+ export const withResultHandler = <
258
+ Path extends string,
259
+ Method extends "get" | "post",
260
+ InputQuerySchema extends z.ZodType,
261
+ InputBodySchema extends z.ZodType,
262
+ OutputSuccessSchema extends z.ZodType,
263
+ OutputErrorSchema extends z.ZodType,
264
+ >(
265
+ context: ApiHandlerContext<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>,
266
+ ): ContextWithResultHandler<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema> => {
267
+ return {
268
+ ...context,
269
+ commonJsonSuccessResult: (
270
+ options: CommonJsonSuccessResultOptions<GetDataFromCommonJsonSuccessResultSchema<OutputSuccessSchema>>,
271
+ ) => {
272
+ return commonJsonSuccessResult({
273
+ logger: context.logger,
274
+ ...options,
275
+ })
276
+ },
277
+ commonJsonErrorResult: (
278
+ options: CommonJsonErrorResultOptions<GetReasonFromCommonJsonErrorResultSchema<OutputErrorSchema>>,
279
+ ) => {
280
+ return commonJsonErrorResult<GetReasonFromCommonJsonErrorResultSchema<OutputErrorSchema>>({
281
+ logger: context.logger,
282
+ ...options,
283
+ })
284
+ },
285
+ }
286
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./api-result-arktype.ts"
2
+ export * as Zod from "./api-result-zod.ts"
3
+
4
+ export * from "./api-core-node-http.ts"
5
+ export * from "./api-host-node-http.ts"
@@ -0,0 +1,126 @@
1
+ import fs, { existsSync, mkdirSync, writeFileSync } from "node:fs"
2
+ import path from "node:path"
3
+ import ts from "typescript"
4
+
5
+ import { getGlobalLogger } from "#Source/log/index.ts"
6
+
7
+ export interface GenerateApiListOptions {
8
+ importFrom: string
9
+ /**
10
+ * Path to tsconfig.json file.
11
+ */
12
+ tsconfigFilePath: string
13
+ /**
14
+ * Path to Api folder.
15
+ */
16
+ apiFolderPath: string
17
+ /**
18
+ * Path to output file.
19
+ */
20
+ outputFilePath: string
21
+ }
22
+ const toAbsolutePath = (options: GenerateApiListOptions): GenerateApiListOptions => {
23
+ const { tsconfigFilePath, apiFolderPath, outputFilePath } = options
24
+ const absoluteTsconfigFilePath = path.resolve(tsconfigFilePath)
25
+ const absoluteApiFolderPath = path.resolve(apiFolderPath)
26
+ const absoluteOutputFilePath = path.resolve(outputFilePath)
27
+ console.log("tsconfigFilePath:", absoluteTsconfigFilePath)
28
+ console.log("apiFolderPath:", absoluteApiFolderPath)
29
+ console.log("outputFilePath:", absoluteOutputFilePath)
30
+ return {
31
+ ...options,
32
+ tsconfigFilePath: absoluteTsconfigFilePath,
33
+ apiFolderPath: absoluteApiFolderPath,
34
+ outputFilePath: absoluteOutputFilePath,
35
+ }
36
+ }
37
+ export const generateApiList = (options: GenerateApiListOptions): void => {
38
+ const {
39
+ importFrom,
40
+ tsconfigFilePath,
41
+ apiFolderPath,
42
+ outputFilePath,
43
+ } = toAbsolutePath(options)
44
+ const logger = getGlobalLogger()
45
+
46
+ // const _projectRootPath = path.dirname(tsconfigFilePath)
47
+
48
+ const tsconfigJson = ts.parseConfigFileTextToJson(tsconfigFilePath, fs.readFileSync(tsconfigFilePath, "utf8"))
49
+ if (tsconfigJson.error) {
50
+ throw new Error("无法解析 tsconfig.json")
51
+ }
52
+ const parsedTsconfig = ts.parseJsonConfigFileContent(tsconfigJson.config, ts.sys, path.resolve(tsconfigFilePath, ".."))
53
+ logger.debug("成功解析 tsconfig 文件...")
54
+
55
+ const projectHost = ts.createCompilerHost(parsedTsconfig.options)
56
+ const project = ts.createProgram(parsedTsconfig.fileNames, parsedTsconfig.options, projectHost)
57
+ logger.debug("成功读取项目...")
58
+
59
+ // 不可以删除, 否则会变得不幸
60
+ // NOTE: Gets a type checker that can be used to semantically analyze source files in the program.
61
+ // const _check = project.getTypeChecker()
62
+
63
+ const allSourceFiles = project.getSourceFiles()
64
+ const apiSourceFiles = allSourceFiles.filter((sourceFile) => {
65
+ // 我们约定接口实现必须名为 index.ts
66
+ // oxlint-disable-next-line no-useless-escape unicorn/prefer-string-raw
67
+ return new RegExp(`${apiFolderPath.replaceAll("\\", "\\\\")}.*index\.ts`).test(path.resolve(sourceFile.fileName))
68
+ })
69
+
70
+ logger.debug(`找到 ${apiSourceFiles.length} 个接口`)
71
+
72
+ const importArea: string[] = []
73
+ const codeArea: string[] = []
74
+ apiSourceFiles.forEach((apiSourceFile, index) => {
75
+ // For example:
76
+ // - apiFolderPath: C:\Users\xxx\Desktop\Codespace\web-core\test\test-project\src\interface
77
+ // - apiSourceFile.fileName: C:/Users/xxx/Desktop/Codespace/web-core/test/test-project/src/interface/add/index.ts
78
+ // - filenameRelativeToApiFolder: add/index.ts
79
+ // - filenameRelativeToApiFolder: ./add/index.ts --- sometimes
80
+ const filenameRelativeToApiFolder = path.relative(apiFolderPath, apiSourceFile.fileName).replaceAll("\\", "/")
81
+ // For example:
82
+ // - importName: add -- from add/index.ts
83
+ // - importName: user_get_user_info -- from user/get-user-info.ts
84
+ const importName = filenameRelativeToApiFolder.replaceAll("/", "_").replaceAll(".ts", "").replaceAll("./", "").replaceAll("-", "_")
85
+ // For example:
86
+ // - outputFilePath: C:\Users\xxx\Desktop\Codespace\web-core\test\test-project\interface\api-list.ts
87
+ // - outputFolderRelativeToApiFolder: ''
88
+ const outputFolderRelativeToApiFolder = path.relative(path.dirname(outputFilePath), apiFolderPath).replaceAll("\\", "/")
89
+ // For example:
90
+ // - importPath: ./add/index
91
+ const importPath = outputFolderRelativeToApiFolder === ""
92
+ ? `./${filenameRelativeToApiFolder}`.replaceAll("\\", "/").replaceAll(".ts", "")
93
+ : path.join(outputFolderRelativeToApiFolder, filenameRelativeToApiFolder).replaceAll("\\", "/").replaceAll(".ts", "")
94
+
95
+ logger.info(`处理(${index + 1} / ${apiSourceFiles.length}):${filenameRelativeToApiFolder}`)
96
+
97
+ for (const node of apiSourceFile.statements) {
98
+ if (ts.isExportAssignment(node) && node.isExportEquals === undefined) {
99
+ const expression = node.expression
100
+ if (ts.isNewExpression(expression) && expression.expression.getText() === "Api") {
101
+ break
102
+ }
103
+ throw new Error(`${apiSourceFile.fileName}:不是 Api`)
104
+ }
105
+ }
106
+
107
+ importArea.push(`import ${importName} from '${importPath}'`)
108
+ codeArea.push(`${String(importName)}`)
109
+ })
110
+
111
+ const finalTestFile = [
112
+ `import type { AnyApi } from '${importFrom}'`,
113
+ "",
114
+ ...importArea,
115
+ "",
116
+ `export const apiList: AnyApi[] = [\n${codeArea.map(a => ` ${String(a)}`).join(",\n")}\n]`,
117
+ "",
118
+ ].join("\n")
119
+
120
+ const outDir = path.dirname(outputFilePath)
121
+ if (existsSync(outDir) === false) {
122
+ mkdirSync(outDir, { recursive: true })
123
+ }
124
+
125
+ writeFileSync(outputFilePath, finalTestFile)
126
+ }
@@ -0,0 +1 @@
1
+ export * from "./gen-api-list.ts"
@@ -0,0 +1,136 @@
1
+ import fs, { existsSync, mkdirSync, writeFileSync } from "node:fs"
2
+ import path from "node:path"
3
+ import ts from "typescript"
4
+
5
+ import { getGlobalLogger } from "#Source/log/index.ts"
6
+
7
+ export interface GenerateApiTestOptions {
8
+ importFrom: string
9
+ tsconfigFilePath: string
10
+ apiFolderPath: string
11
+ outputFilePath: string
12
+ apiFileFilter: string
13
+ }
14
+ const toAbsolutePath = (options: GenerateApiTestOptions): GenerateApiTestOptions => {
15
+ const { tsconfigFilePath, apiFolderPath, outputFilePath } = options
16
+ const absoluteTsconfigFilePath = path.resolve(tsconfigFilePath)
17
+ const absoluteApiFolderPath = path.resolve(apiFolderPath)
18
+ const absoluteOutputFilePath = path.resolve(outputFilePath)
19
+ console.log("tsconfigFilePath:", absoluteTsconfigFilePath)
20
+ console.log("apiFolderPath:", absoluteApiFolderPath)
21
+ console.log("outputFilePath:", absoluteOutputFilePath)
22
+ return {
23
+ ...options,
24
+ tsconfigFilePath: absoluteTsconfigFilePath,
25
+ apiFolderPath: absoluteApiFolderPath,
26
+ outputFilePath: absoluteOutputFilePath,
27
+ }
28
+ }
29
+ export const generateApiText = (options: GenerateApiTestOptions): void => {
30
+ const {
31
+ importFrom,
32
+ tsconfigFilePath,
33
+ apiFolderPath,
34
+ outputFilePath,
35
+ apiFileFilter,
36
+ } = toAbsolutePath(options)
37
+ const logger = getGlobalLogger()
38
+
39
+ const projectRootPath = path.dirname(tsconfigFilePath)
40
+
41
+ const tsconfigJson = ts.parseConfigFileTextToJson(tsconfigFilePath, fs.readFileSync(tsconfigFilePath, "utf8"))
42
+ if (tsconfigJson.error) {
43
+ throw new Error("无法解析 tsconfig.json")
44
+ }
45
+ const parsedTsconfig = ts.parseJsonConfigFileContent(tsconfigJson.config, ts.sys, path.resolve(tsconfigFilePath, ".."))
46
+ logger.debug("成功解析 tsconfig 文件...")
47
+
48
+ const projectHost = ts.createCompilerHost(parsedTsconfig.options)
49
+ const project = ts.createProgram(parsedTsconfig.fileNames, parsedTsconfig.options, projectHost)
50
+ logger.debug("成功读取项目...")
51
+
52
+ // 不可以删除, 否则会变得不幸
53
+ // NOTE: Gets a type checker that can be used to semantically analyze source files in the program.
54
+ // const _check = project.getTypeChecker()
55
+
56
+ const allSourceFiles = project.getSourceFiles()
57
+ const typeSourceFiles = allSourceFiles.filter((sourceFile) => {
58
+ // 我们约定接口蓝图必须名为 type.ts
59
+ // oxlint-disable-next-line no-useless-escape unicorn/prefer-string-raw
60
+ return new RegExp(`${apiFolderPath.replaceAll("\\", "\\\\")}.*type\.ts`).test(path.resolve(sourceFile.fileName))
61
+ })
62
+ const testSourceFiles = allSourceFiles.filter((sourceFile) => {
63
+ // oxlint-disable-next-line no-useless-escape unicorn/prefer-string-raw
64
+ return new RegExp(`${apiFolderPath.replaceAll("\\", "\\\\")}.*\.test\.ts`).test(path.resolve(sourceFile.fileName))
65
+ })
66
+
67
+ const testSourceFilesFilter = testSourceFiles.filter((sourceFile) => {
68
+ return new RegExp(apiFileFilter).test(path.resolve(sourceFile.fileName))
69
+ })
70
+ // TODO: 这里可以明确打印出来谁有测试,谁没有测试。
71
+ logger.debug(`找到 ${typeSourceFiles.length} 个接口,其中有 ${testSourceFiles.length} 个测试,筛选后还剩 ${testSourceFilesFilter.length} 个...`)
72
+
73
+ const importCode: string[] = []
74
+ const testCode: string[] = []
75
+ testSourceFilesFilter.forEach((testSourceFile, index) => {
76
+ // For example:
77
+ // - apiFolderPath: C:\Users\xxx\Desktop\Codespace\web-core\test\test-project\src\interface
78
+ // - testSourceFile.fileName: C:/Users/xxx/Desktop/Codespace/web-core/test/test-project/src/interface/add/t01.test.ts
79
+ // - filenameRelativeToApiFolder: add/t01.test.ts
80
+ // - filenameRelativeToApiFolder: ./add/t01.test.ts --- sometimes
81
+ const filenameRelativeToApiFolder = path.relative(apiFolderPath, testSourceFile.fileName).replaceAll("\\", "/")
82
+ // For example:
83
+ // - importName: add_t01 -- from add/t01.test.ts
84
+ // - importName: get_user_info -- from get-user-info.test.ts
85
+ const importName = filenameRelativeToApiFolder.replaceAll("/", "_").replaceAll(".test.ts", "").replaceAll("./", "").replaceAll("-", "_")
86
+ // For example:
87
+ // - projectRootPath: C:\Users\xxx\Desktop\Codespace\web-core\test\test-project
88
+ // - filenameRelativeToProjectRoot: src/interface/add/t01.test
89
+ const filenameRelativeToProjectRoot = path.relative(projectRootPath, testSourceFile.fileName).replaceAll("\\", "/").replaceAll(".ts", "")
90
+ // For example:
91
+ // - outputPath: C:\Users\xxx\Desktop\Codespace\web-core\test\test-project\test\test.ts
92
+ // - outputFolderRelativeToProjectRoot: ../
93
+ const outputFolderRelativeToProjectRoot = path.relative(path.dirname(outputFilePath), projectRootPath).replaceAll("\\", "/")
94
+ // For example:
95
+ // - importPath: ../src/interface/add/t01.test
96
+ const importPath = path.join(outputFolderRelativeToProjectRoot, filenameRelativeToProjectRoot).replaceAll("\\", "/")
97
+
98
+ logger.info(`处理(${index + 1} / ${testSourceFilesFilter.length}):${filenameRelativeToApiFolder}`)
99
+
100
+ for (const node of testSourceFile.statements) {
101
+ if (ts.isExportAssignment(node) && node.isExportEquals === undefined) {
102
+ const expression = node.expression
103
+ if (ts.isNewExpression(expression) && expression.expression.getText() === "ApiTestSchema") {
104
+ break
105
+ }
106
+ throw new Error(`${testSourceFile.fileName}:不是 ApiTestSchema`)
107
+ }
108
+ }
109
+
110
+ importCode.push(`import ${importName} from '${importPath}'`)
111
+ testCode.push(generateTestCode(importName, importName))
112
+ })
113
+
114
+ const finalTestFile = [
115
+ "import { test } from 'vitest'",
116
+ `import { testApi } from '${importFrom}'`,
117
+ "",
118
+ "import './unit-test-prefix'",
119
+ "",
120
+ ...importCode,
121
+ "",
122
+ ...testCode,
123
+ "",
124
+ ].join("\n")
125
+
126
+ const outDir = path.dirname(outputFilePath)
127
+ if (existsSync(outDir) === false) {
128
+ mkdirSync(outDir, { recursive: true })
129
+ }
130
+
131
+ writeFileSync(outputFilePath, finalTestFile)
132
+ }
133
+
134
+ const generateTestCode = (testCaseName: string, importName: string): string => {
135
+ return `test('${testCaseName}', async () => await testApi({ apiTest: ${importName} }))`
136
+ }
@@ -0,0 +1 @@
1
+ export * from "./gen-api-test.ts"
@@ -0,0 +1,25 @@
1
+ export interface GetCalcCodeOptions {
2
+ importFrom: string
3
+ }
4
+ export const getCalcCode = (options: GetCalcCodeOptions): string => {
5
+ const { importFrom } = options
6
+ return `
7
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
8
+ import type { ApiSchema } from "${importFrom}"
9
+
10
+ import exportedApiSchema from "./type"
11
+
12
+ type Api = (typeof exportedApiSchema) extends ApiSchema<infer Path, infer Method, infer InputQuerySchema, infer InputBodySchema, infer OutputSuccessSchema, infer OutputErrorSchema>
13
+ ? {
14
+ path: Path,
15
+ method: Method,
16
+ inputQuery: StandardSchemaV1.InferOutput<InputQuerySchema>,
17
+ inputBody: StandardSchemaV1.InferOutput<InputBodySchema>,
18
+ outputSuccess: StandardSchemaV1.InferOutput<OutputSuccessSchema>,
19
+ outputError: StandardSchemaV1.InferOutput<OutputErrorSchema>,
20
+ }
21
+ : never
22
+
23
+ export default Api
24
+ `
25
+ }