@planet-matrix/mobius-model 0.5.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.
- package/CHANGELOG.md +61 -0
- package/README.md +123 -36
- package/dist/index.js +715 -4
- package/dist/index.js.map +981 -13
- package/oxlint.config.ts +6 -0
- package/package.json +36 -18
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +72 -116
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +83 -64
- package/src/basic/index.ts +1 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/encoding/README.md +66 -79
- package/src/encoding/base64.ts +13 -4
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +229 -0
- package/src/event/common.ts +29 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +187 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +33 -1
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +554 -0
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +91 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +3 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +56 -86
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +91 -38
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/credential/api-key.spec.ts +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +237 -0
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/.oxlintrc.json +0 -5
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { generateUuidV4 } from "#Source/identifier/uuid.ts"
|
|
2
|
+
import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/logger.ts"
|
|
3
|
+
import { Logger } from "#Source/log/logger.ts"
|
|
4
|
+
import { toQueryObject } from "#Source/route/index.ts"
|
|
5
|
+
|
|
6
|
+
import type { AnyApiHost, ApiHostStartResult } from "./api-host.ts"
|
|
7
|
+
import type { AnyApi } from "./api.ts"
|
|
8
|
+
|
|
9
|
+
export interface ApiServerOptions extends LoggerFriendlyOptions {
|
|
10
|
+
apiList: AnyApi[]
|
|
11
|
+
apiHost: AnyApiHost
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* {@link ApiServer} 包含若干个 {@link Api}(接口),并且需要指定一个监听端口(Port)。Server 启动
|
|
15
|
+
* 后会监听对应的端口,将端口接收到的请求分发给不同的接口。
|
|
16
|
+
*
|
|
17
|
+
* 请求 -> Server -> Api
|
|
18
|
+
*
|
|
19
|
+
* 具体来说,当服务器收到请求时:
|
|
20
|
+
* - 通过请求的路径(path)和方法(method)与所有的 {@link Api} 进行匹配。
|
|
21
|
+
* - 如果能匹配到,将请求分给第一个匹配到的接口。
|
|
22
|
+
* - 如果匹配不到任何接口,将返回 404。
|
|
23
|
+
* - 按照 {@link Api} 的处理逻辑进行处理,最终将结果返回给请求方。
|
|
24
|
+
*/
|
|
25
|
+
export class ApiServer implements LoggerFriendly {
|
|
26
|
+
readonly options: ApiServerOptions
|
|
27
|
+
|
|
28
|
+
readonly logger: Logger
|
|
29
|
+
|
|
30
|
+
constructor(options: ApiServerOptions) {
|
|
31
|
+
this.options = options
|
|
32
|
+
|
|
33
|
+
this.logger = Logger.fromOptions(options).setDefaultName("ApiServer")
|
|
34
|
+
|
|
35
|
+
this.initialize()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected initialize(): void {
|
|
39
|
+
const { apiList, apiHost } = this.options
|
|
40
|
+
|
|
41
|
+
apiHost.attachApiCoreHandler(async (apiCore) => {
|
|
42
|
+
const path = await apiCore.getRequestPath()
|
|
43
|
+
const method = (await apiCore.getRequestMethod()).toUpperCase()
|
|
44
|
+
|
|
45
|
+
// tag every request with a unique identifier for better logging
|
|
46
|
+
const logger = Logger
|
|
47
|
+
.derive(this.logger)
|
|
48
|
+
.setDefaultName(`${method} ${path} ${generateUuidV4()}`)
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
logger.log(`start handling request`)
|
|
52
|
+
|
|
53
|
+
logger.log(`start finding target api`)
|
|
54
|
+
// 根据请求的路径和方法找到对应的接口
|
|
55
|
+
const targetApi = apiList.find((api) => {
|
|
56
|
+
const apiSchema = api.getApiSchema()
|
|
57
|
+
const apiSchemaPath = apiSchema.getPath()
|
|
58
|
+
const apiSchemaMethod = apiSchema.getMethod().toUpperCase()
|
|
59
|
+
return apiSchemaPath === path && apiSchemaMethod === method
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// 如果找不到对应的接口,抛出错误
|
|
63
|
+
if (targetApi === undefined) {
|
|
64
|
+
logger.error(`finding target api failed`)
|
|
65
|
+
throw new Error(`finding target api failed`)
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
logger.log(`finding target api success`)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 如果找到对应的接口,获取接口定义,按照接口的定义进行处理
|
|
72
|
+
const apiSchema = targetApi.getApiSchema()
|
|
73
|
+
|
|
74
|
+
// 1. 准备 ApiHandlerContext
|
|
75
|
+
logger.log(`start preparing api handler context`)
|
|
76
|
+
const inputSearch = await apiCore.getRequestSearch()
|
|
77
|
+
const inputQueryObject = toQueryObject(inputSearch)
|
|
78
|
+
const inputQuerySchema = apiSchema.getInputQuerySchema()
|
|
79
|
+
const validateResultQuery = await inputQuerySchema["~standard"].validate(inputQueryObject)
|
|
80
|
+
if (validateResultQuery.issues !== undefined) {
|
|
81
|
+
logger.error(`validating api input query failed, issues: ${JSON.stringify(validateResultQuery.issues)}`)
|
|
82
|
+
throw new Error(`validating api input query failed, issues: ${JSON.stringify(validateResultQuery.issues)}`)
|
|
83
|
+
}
|
|
84
|
+
const inputQuery = validateResultQuery.value
|
|
85
|
+
|
|
86
|
+
const inputBodySchema = apiSchema.getInputBodySchema()
|
|
87
|
+
const inputBodyJson = await apiCore.getRequestBodyJson()
|
|
88
|
+
const validateResult = await inputBodySchema["~standard"].validate(inputBodyJson)
|
|
89
|
+
if (validateResult.issues !== undefined) {
|
|
90
|
+
logger.error(`validating api input body failed, issues: ${JSON.stringify(validateResult.issues)}`)
|
|
91
|
+
throw new Error(`validating api input body failed, issues: ${JSON.stringify(validateResult.issues)}`)
|
|
92
|
+
}
|
|
93
|
+
const inputBody = validateResult.value
|
|
94
|
+
logger.log(`preparing api handler context success`)
|
|
95
|
+
|
|
96
|
+
// 2. 执行 ApiHandler
|
|
97
|
+
logger.log(`start executing api handler`)
|
|
98
|
+
const apiHandler = targetApi.getApiHandler()
|
|
99
|
+
const apiResult = await apiHandler({
|
|
100
|
+
...apiSchema.getApiSchemaOptions(),
|
|
101
|
+
inputQuery,
|
|
102
|
+
inputBody,
|
|
103
|
+
apiCore,
|
|
104
|
+
logger,
|
|
105
|
+
})
|
|
106
|
+
logger.log(`executing api handler success`)
|
|
107
|
+
|
|
108
|
+
// 3. 处理 ApiHandler 的执行结果,这一步会将数据返回给请求方
|
|
109
|
+
logger.log(`start handling api result`)
|
|
110
|
+
await apiResult.handle(apiCore)
|
|
111
|
+
logger.log(`handling api result success`)
|
|
112
|
+
|
|
113
|
+
logger.log(`handling request success`)
|
|
114
|
+
} catch (exception) {
|
|
115
|
+
logger.error(`handling request failed, exception: ${String(exception)}`)
|
|
116
|
+
await apiCore.error()
|
|
117
|
+
}
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async start(): Promise<ApiHostStartResult> {
|
|
122
|
+
const { apiHost } = this.options
|
|
123
|
+
const result = await apiHost.start()
|
|
124
|
+
return result
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export const createApiServer = (options: ApiServerOptions): ApiServer => {
|
|
129
|
+
return new ApiServer(options)
|
|
130
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
|
|
2
|
+
import { Logger } from "#Source/log/index.ts"
|
|
3
|
+
|
|
4
|
+
import type { AnyApiSchema } from "./api-schema.ts"
|
|
5
|
+
|
|
6
|
+
export interface BeforeApiTestHandlerContext<
|
|
7
|
+
ApiSchema extends AnyApiSchema,
|
|
8
|
+
> {
|
|
9
|
+
apiSchema: ApiSchema
|
|
10
|
+
}
|
|
11
|
+
export type BeforeApiTestHandler<
|
|
12
|
+
ApiSchema extends AnyApiSchema,
|
|
13
|
+
BeforeApiTestResult,
|
|
14
|
+
> = (context: BeforeApiTestHandlerContext<ApiSchema>) => Promise<BeforeApiTestResult>
|
|
15
|
+
export interface ApiTestHandlerContext<
|
|
16
|
+
ApiSchema extends AnyApiSchema,
|
|
17
|
+
BeforeApiTestHandlerResult,
|
|
18
|
+
> {
|
|
19
|
+
apiSchema: ApiSchema
|
|
20
|
+
beforeApiTestResult: BeforeApiTestHandlerResult
|
|
21
|
+
}
|
|
22
|
+
export type ApiTestHandler<
|
|
23
|
+
ApiSchema extends AnyApiSchema,
|
|
24
|
+
BeforeApiTestResult,
|
|
25
|
+
ApiTestResult,
|
|
26
|
+
> = (context: ApiTestHandlerContext<ApiSchema, BeforeApiTestResult>) => Promise<ApiTestResult>
|
|
27
|
+
export interface AfterApiTestContext<
|
|
28
|
+
ApiSchema extends AnyApiSchema,
|
|
29
|
+
BeforeApiTestResult,
|
|
30
|
+
ApiTestResult,
|
|
31
|
+
> {
|
|
32
|
+
apiSchema: ApiSchema
|
|
33
|
+
beforeApiTestResult: BeforeApiTestResult
|
|
34
|
+
apiTestResult: ApiTestResult
|
|
35
|
+
}
|
|
36
|
+
export type AfterApiTestHandler<
|
|
37
|
+
ApiSchema extends AnyApiSchema,
|
|
38
|
+
BeforeApiTestResult,
|
|
39
|
+
ApiTestResult,
|
|
40
|
+
AfterApiTestResult,
|
|
41
|
+
> = (context: AfterApiTestContext<ApiSchema, BeforeApiTestResult, ApiTestResult>) => Promise<AfterApiTestResult>
|
|
42
|
+
|
|
43
|
+
export interface ApiTestOptions<
|
|
44
|
+
ApiSchema extends AnyApiSchema,
|
|
45
|
+
BeforeApiTestResult,
|
|
46
|
+
ApiTestResult,
|
|
47
|
+
AfterApiTestResult,
|
|
48
|
+
> extends LoggerFriendlyOptions {
|
|
49
|
+
apiSchema: ApiSchema
|
|
50
|
+
beforeApiTestHandler: BeforeApiTestHandler<ApiSchema, BeforeApiTestResult>
|
|
51
|
+
apiTestHandler: ApiTestHandler<ApiSchema, BeforeApiTestResult, ApiTestResult>
|
|
52
|
+
afterApiTestHandler: AfterApiTestHandler<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult>
|
|
53
|
+
}
|
|
54
|
+
export class ApiTest<
|
|
55
|
+
ApiSchema extends AnyApiSchema,
|
|
56
|
+
BeforeApiTestResult,
|
|
57
|
+
ApiTestResult,
|
|
58
|
+
AfterApiTestResult,
|
|
59
|
+
> implements LoggerFriendly {
|
|
60
|
+
readonly logger: Logger
|
|
61
|
+
|
|
62
|
+
protected __ApiSchemaPhantomType__?: ApiSchema
|
|
63
|
+
protected __BeforeApiTestResultPhantomType__?: BeforeApiTestResult
|
|
64
|
+
protected __ApiTestResultPhantomType__?: ApiTestResult
|
|
65
|
+
protected __AfterApiTestResultPhantomType__?: AfterApiTestResult
|
|
66
|
+
|
|
67
|
+
private apiSchema: ApiSchema
|
|
68
|
+
private beforeApiTestHandler: BeforeApiTestHandler<ApiSchema, BeforeApiTestResult>
|
|
69
|
+
private apiTestHandler: ApiTestHandler<ApiSchema, BeforeApiTestResult, ApiTestResult>
|
|
70
|
+
private afterApiTestHandler: AfterApiTestHandler<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult>
|
|
71
|
+
|
|
72
|
+
constructor(options: ApiTestOptions<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult>) {
|
|
73
|
+
this.logger = Logger.fromOptions(options).setDefaultName("ApiTest")
|
|
74
|
+
|
|
75
|
+
this.apiSchema = options.apiSchema
|
|
76
|
+
this.beforeApiTestHandler = options.beforeApiTestHandler
|
|
77
|
+
this.apiTestHandler = options.apiTestHandler
|
|
78
|
+
this.afterApiTestHandler = options.afterApiTestHandler
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getApiSchema(): ApiSchema {
|
|
82
|
+
return this.apiSchema
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
getBeforeApiTestHandler(): BeforeApiTestHandler<ApiSchema, BeforeApiTestResult> {
|
|
86
|
+
return this.beforeApiTestHandler
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getApiTestHandler(): ApiTestHandler<ApiSchema, BeforeApiTestResult, ApiTestResult> {
|
|
90
|
+
return this.apiTestHandler
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getAfterApiTestHandler(): AfterApiTestHandler<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult> {
|
|
94
|
+
return this.afterApiTestHandler
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const createApiTest = <
|
|
99
|
+
ApiSchema extends AnyApiSchema,
|
|
100
|
+
BeforeApiTestResult,
|
|
101
|
+
ApiTestResult,
|
|
102
|
+
AfterApiTestResult,
|
|
103
|
+
>(
|
|
104
|
+
options: ApiTestOptions<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult>,
|
|
105
|
+
): ApiTest<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult> => {
|
|
106
|
+
return new ApiTest(options)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
110
|
+
export type AnyApiTest = ApiTest<any, any, any, any>
|
|
111
|
+
|
|
112
|
+
export interface TestApiOptions<
|
|
113
|
+
ApiSchema extends AnyApiSchema,
|
|
114
|
+
BeforeApiTestResult,
|
|
115
|
+
ApiTestResult,
|
|
116
|
+
AfterApiTestResult,
|
|
117
|
+
> {
|
|
118
|
+
apiTest: ApiTest<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult>
|
|
119
|
+
}
|
|
120
|
+
export const testApi = async <
|
|
121
|
+
ApiSchema extends AnyApiSchema,
|
|
122
|
+
BeforeApiTestResult,
|
|
123
|
+
ApiTestResult,
|
|
124
|
+
AfterApiTestResult,
|
|
125
|
+
>(
|
|
126
|
+
options: TestApiOptions<ApiSchema, BeforeApiTestResult, ApiTestResult, AfterApiTestResult>,
|
|
127
|
+
): Promise<AfterApiTestResult> => {
|
|
128
|
+
const { apiTest } = options
|
|
129
|
+
|
|
130
|
+
const apiSchema = apiTest.getApiSchema()
|
|
131
|
+
|
|
132
|
+
const beforeApiTestHandler = apiTest.getBeforeApiTestHandler()
|
|
133
|
+
const beforeApiTestResult = await beforeApiTestHandler({ apiSchema })
|
|
134
|
+
|
|
135
|
+
const apiTestHandler = apiTest.getApiTestHandler()
|
|
136
|
+
const apiTestResult = await apiTestHandler({ apiSchema, beforeApiTestResult })
|
|
137
|
+
|
|
138
|
+
const afterApiTestHandler = apiTest.getAfterApiTestHandler()
|
|
139
|
+
const afterApiTestResult = await afterApiTestHandler({ apiSchema, beforeApiTestResult, apiTestResult })
|
|
140
|
+
|
|
141
|
+
return afterApiTestResult
|
|
142
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
2
|
+
|
|
3
|
+
import type { AnyApiSchema, ApiSchema } from './api-schema.ts';
|
|
4
|
+
|
|
5
|
+
export type ApiType<Target extends AnyApiSchema> = Target extends ApiSchema<
|
|
6
|
+
infer Path,
|
|
7
|
+
infer Method,
|
|
8
|
+
infer InputQuerySchema,
|
|
9
|
+
infer InputBodySchema,
|
|
10
|
+
infer OutputSuccessSchema,
|
|
11
|
+
infer OutputErrorSchema
|
|
12
|
+
> ? {
|
|
13
|
+
path: Path
|
|
14
|
+
method: Method
|
|
15
|
+
inputQuery: StandardSchemaV1.InferOutput<InputQuerySchema>
|
|
16
|
+
inputBody: StandardSchemaV1.InferOutput<InputBodySchema>
|
|
17
|
+
outputSuccess: StandardSchemaV1.InferOutput<OutputSuccessSchema>
|
|
18
|
+
outputError: StandardSchemaV1.InferOutput<OutputErrorSchema>
|
|
19
|
+
} : never
|
|
20
|
+
export const apiType = <const Target extends AnyApiSchema>(apiSchema: Target): ApiType<Target> => {
|
|
21
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
22
|
+
return apiSchema as unknown as ApiType<Target>
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ApiTypesInTuple<Target extends readonly AnyApiSchema[]> = {
|
|
26
|
+
[Index in keyof Target]: ApiType<Target[Index]>
|
|
27
|
+
}
|
|
28
|
+
export const apiTypesInTuple = <const Target extends readonly AnyApiSchema[]>(apiSchemas: Target): ApiTypesInTuple<Target> => {
|
|
29
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
30
|
+
return apiSchemas.map(apiSchema => apiType(apiSchema)) as unknown as ApiTypesInTuple<Target>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type ApiTypesInUnion<Target extends readonly AnyApiSchema[]> = ApiType<Target[number]>
|
|
34
|
+
export const apiTypesInUnion = <const Target extends readonly AnyApiSchema[]>(apiSchemas: Target): ApiTypesInUnion<Target> => {
|
|
35
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
36
|
+
return apiSchemas.map(apiSchema => apiType(apiSchema)) as unknown as ApiTypesInUnion<Target>
|
|
37
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec"
|
|
2
|
+
|
|
3
|
+
import type { LoggerFriendly, LoggerFriendlyOptions } from "#Source/log/index.ts"
|
|
4
|
+
import { Logger } from "#Source/log/index.ts"
|
|
5
|
+
|
|
6
|
+
import type { ApiSchema } from "./api-schema.ts"
|
|
7
|
+
import type { ApiHandler } from "./api-handler.ts"
|
|
8
|
+
|
|
9
|
+
export interface ApiOptions<
|
|
10
|
+
Path extends string,
|
|
11
|
+
Method extends "get" | "post",
|
|
12
|
+
InputQuerySchema extends StandardSchemaV1,
|
|
13
|
+
InputBodySchema extends StandardSchemaV1,
|
|
14
|
+
OutputSuccessSchema extends StandardSchemaV1,
|
|
15
|
+
OutputErrorSchema extends StandardSchemaV1,
|
|
16
|
+
> extends LoggerFriendlyOptions {
|
|
17
|
+
apiSchema: ApiSchema<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>
|
|
18
|
+
apiHandler: ApiHandler<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Api 应该严格遵循 {@link ApiSchema} 的描述进行实现。
|
|
22
|
+
*
|
|
23
|
+
* 当一个请求被 {@link ApiServer} 按照路径(Path)和方法(Method)匹配到一个接口后,
|
|
24
|
+
* 便会开始按照 {@link Api} 的逻辑对请求进行处理。
|
|
25
|
+
*
|
|
26
|
+
* Api 的整个处理流程如下:
|
|
27
|
+
*
|
|
28
|
+
* 请求 -> 构造上下文 -> 处理器(Api Runner) -> 返回结果(Result)
|
|
29
|
+
*
|
|
30
|
+
* 其中:
|
|
31
|
+
* - 构造上下文包括:
|
|
32
|
+
* - 从处理原始请求并从中提取输入数据,主要来自 query 和 body。
|
|
33
|
+
* - 校验输入数据,确保输入数据符合 {@link ApiSchema} 的描述。
|
|
34
|
+
* - 处理器是 Api 的核心逻辑,负责对请求进行处理,可以包含副作用,比如查询数据库、调用其他服务等。
|
|
35
|
+
* - 一般情况下,处理器不应该直接返回数据给前端,而应该通过 {@link SuccessResult} 或 {@link ErrorResult}
|
|
36
|
+
* 定义自己想要返回什么,然后 {@link ApiServer} 会负责执行返回逻辑,将结果返回给前端,这样做既可以使 Api 的
|
|
37
|
+
* 核心逻辑更纯粹,也可以保证所有的接口始终给前端返回一致的数据结构。
|
|
38
|
+
*/
|
|
39
|
+
export class Api<
|
|
40
|
+
Path extends string,
|
|
41
|
+
Method extends "get" | "post",
|
|
42
|
+
InputQuerySchema extends StandardSchemaV1,
|
|
43
|
+
InputBodySchema extends StandardSchemaV1,
|
|
44
|
+
OutputSuccessSchema extends StandardSchemaV1,
|
|
45
|
+
OutputErrorSchema extends StandardSchemaV1,
|
|
46
|
+
> implements LoggerFriendly {
|
|
47
|
+
readonly logger: Logger
|
|
48
|
+
|
|
49
|
+
private readonly apiSchema: ApiSchema<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>
|
|
50
|
+
private readonly apiHandler: ApiHandler<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>
|
|
51
|
+
|
|
52
|
+
constructor(options: ApiOptions<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>) {
|
|
53
|
+
this.logger = Logger.fromOptions(options).setDefaultName("Api")
|
|
54
|
+
|
|
55
|
+
this.apiSchema = options.apiSchema
|
|
56
|
+
this.apiHandler = options.apiHandler
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
getApiSchema(): ApiSchema<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema> {
|
|
60
|
+
return this.apiSchema
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getApiHandler(): ApiHandler<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema> {
|
|
64
|
+
return this.apiHandler
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const createApi = <
|
|
69
|
+
Path extends string,
|
|
70
|
+
Method extends "get" | "post",
|
|
71
|
+
InputQuerySchema extends StandardSchemaV1,
|
|
72
|
+
InputBodySchema extends StandardSchemaV1,
|
|
73
|
+
OutputSuccessSchema extends StandardSchemaV1,
|
|
74
|
+
OutputErrorSchema extends StandardSchemaV1,
|
|
75
|
+
>(
|
|
76
|
+
options: ApiOptions<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema>,
|
|
77
|
+
): Api<Path, Method, InputQuerySchema, InputBodySchema, OutputSuccessSchema, OutputErrorSchema> => {
|
|
78
|
+
return new Api(options)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type AnyApi = Api<string, "get" | "post", StandardSchemaV1, StandardSchemaV1, StandardSchemaV1, StandardSchemaV1>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from "./api-core.ts"
|
|
2
|
+
export * from "./api-host.ts"
|
|
3
|
+
|
|
4
|
+
export * from "./api-result.ts"
|
|
5
|
+
export * from "./api-schema.ts"
|
|
6
|
+
export * from "./api-type.ts"
|
|
7
|
+
export type * from "./api-handler.ts"
|
|
8
|
+
export * from "./api.ts"
|
|
9
|
+
|
|
10
|
+
export * from "./api-test.ts"
|
|
11
|
+
export * from "./api-server.ts"
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http"
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
ApiCoreHeaders,
|
|
5
|
+
ApiCoreRequestFormData,
|
|
6
|
+
ApiCoreStream,
|
|
7
|
+
} from "../api/api-core.ts"
|
|
8
|
+
import { ApiCore } from "../api/api-core.ts"
|
|
9
|
+
|
|
10
|
+
export class NodeHttpApiCoreStream implements ApiCoreStream {
|
|
11
|
+
protected readonly response: ServerResponse<IncomingMessage>
|
|
12
|
+
|
|
13
|
+
constructor(response: ServerResponse<IncomingMessage>) {
|
|
14
|
+
this.response = response
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async write(chunk: Buffer | string, encoding: BufferEncoding): Promise<void> {
|
|
18
|
+
await new Promise<void>((resolve, reject) => {
|
|
19
|
+
this.response.write(chunk, encoding, (error) => {
|
|
20
|
+
if (error !== undefined) {
|
|
21
|
+
reject(error instanceof Error ? error : new Error(String(error)))
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
resolve()
|
|
26
|
+
})
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async end(): Promise<void> {
|
|
31
|
+
await new Promise<void>((resolve) => {
|
|
32
|
+
this.response.end(() => {
|
|
33
|
+
resolve()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ApiCoreNodeHttpOptions {
|
|
40
|
+
request: IncomingMessage
|
|
41
|
+
response: ServerResponse<IncomingMessage>
|
|
42
|
+
}
|
|
43
|
+
export class ApiCoreNodeHttp extends ApiCore {
|
|
44
|
+
protected readonly request: IncomingMessage
|
|
45
|
+
protected readonly response: ServerResponse<IncomingMessage>
|
|
46
|
+
|
|
47
|
+
protected requestUrl: URL | undefined
|
|
48
|
+
protected bodyBufferTask: Promise<Buffer> | undefined
|
|
49
|
+
protected bodyTextTask: Promise<string> | undefined
|
|
50
|
+
protected bodyFormDataTask: Promise<ApiCoreRequestFormData> | undefined
|
|
51
|
+
|
|
52
|
+
constructor(options: ApiCoreNodeHttpOptions) {
|
|
53
|
+
super()
|
|
54
|
+
|
|
55
|
+
this.request = options.request
|
|
56
|
+
this.response = options.response
|
|
57
|
+
|
|
58
|
+
this.bodyBufferTask = undefined
|
|
59
|
+
this.bodyTextTask = undefined
|
|
60
|
+
this.bodyFormDataTask = undefined
|
|
61
|
+
this.requestUrl = undefined
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async getRequestHeaders(): Promise<ApiCoreHeaders> {
|
|
65
|
+
const headers: ApiCoreHeaders = {}
|
|
66
|
+
|
|
67
|
+
for (const [name, rawValue] of Object.entries(this.request.headers)) {
|
|
68
|
+
if (Array.isArray(rawValue)) {
|
|
69
|
+
const [firstValue, ...restValues] = rawValue
|
|
70
|
+
|
|
71
|
+
if (firstValue === undefined) {
|
|
72
|
+
headers[name] = undefined
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
headers[name] = [firstValue, ...restValues]
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
headers[name] = rawValue === undefined ? undefined : [rawValue]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return await Promise.resolve(headers)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
getRequestUrl(): string {
|
|
87
|
+
let requestUrl = this.requestUrl
|
|
88
|
+
|
|
89
|
+
if (requestUrl === undefined) {
|
|
90
|
+
const partialUrl = this.request.url ?? "/"
|
|
91
|
+
const host = this.request.headers.host
|
|
92
|
+
|
|
93
|
+
if (host === undefined || host.length === 0) {
|
|
94
|
+
requestUrl = new URL(partialUrl, "http://127.0.0.1")
|
|
95
|
+
} else {
|
|
96
|
+
requestUrl = new URL(partialUrl, `http://${host}`)
|
|
97
|
+
}
|
|
98
|
+
this.requestUrl = requestUrl
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return requestUrl.href
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async getRequestMethod(): Promise<string> {
|
|
105
|
+
const method = this.request.method ?? "GET"
|
|
106
|
+
return await Promise.resolve(method)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
protected async getRequestBodyBuffer(): Promise<Buffer> {
|
|
110
|
+
if (this.bodyBufferTask !== undefined) {
|
|
111
|
+
return await this.bodyBufferTask
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.bodyBufferTask = new Promise<Buffer>((resolve, reject) => {
|
|
115
|
+
const chunks: Buffer[] = []
|
|
116
|
+
|
|
117
|
+
this.request.on("data", (chunk: Buffer | string) => {
|
|
118
|
+
if (typeof chunk === "string") {
|
|
119
|
+
chunks.push(Buffer.from(chunk))
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
chunks.push(chunk)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
this.request.once("aborted", () => {
|
|
127
|
+
reject(new Error("Request body was aborted."))
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
this.request.once("error", (error) => {
|
|
131
|
+
reject(error)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
this.request.once("end", () => {
|
|
135
|
+
resolve(Buffer.concat(chunks))
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
return await this.bodyBufferTask
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async getRequestBodyText(): Promise<string> {
|
|
143
|
+
if (this.bodyTextTask !== undefined) {
|
|
144
|
+
return await this.bodyTextTask
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
this.bodyTextTask = this.getRequestBodyBuffer().then((bodyBuffer) => {
|
|
148
|
+
return bodyBuffer.toString("utf8")
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
return await this.bodyTextTask
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async getRequestBodyFormData(): Promise<ApiCoreRequestFormData> {
|
|
155
|
+
if (this.bodyFormDataTask !== undefined) {
|
|
156
|
+
return await this.bodyFormDataTask
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
this.bodyFormDataTask = (async () => {
|
|
160
|
+
const contentType = this.request.headers["content-type"]
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
contentType === undefined
|
|
164
|
+
|| /^(application\/x-www-form-urlencoded|multipart\/form-data)\b/i.test(contentType) === false
|
|
165
|
+
) {
|
|
166
|
+
return []
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const bodyBuffer = await this.getRequestBodyBuffer()
|
|
170
|
+
const requestHeaders = Object.entries(await this.getRequestHeaders()).flatMap(([name, value]) => {
|
|
171
|
+
if (value === undefined) {
|
|
172
|
+
return []
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return value.map((item) => {
|
|
176
|
+
return [name, item] as [string, string]
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
const request = new Request(this.getRequestUrl(), {
|
|
180
|
+
method: await this.getRequestMethod(),
|
|
181
|
+
headers: requestHeaders,
|
|
182
|
+
body: new Uint8Array(bodyBuffer),
|
|
183
|
+
})
|
|
184
|
+
const formData = await request.formData()
|
|
185
|
+
|
|
186
|
+
return Array.from(formData.entries()).map(([name, value]) => {
|
|
187
|
+
return {
|
|
188
|
+
name,
|
|
189
|
+
value,
|
|
190
|
+
}
|
|
191
|
+
})
|
|
192
|
+
})()
|
|
193
|
+
|
|
194
|
+
return await this.bodyFormDataTask
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
protected prepareResponse(contentType: string, statusCode: number = 200): void {
|
|
198
|
+
if (this.response.writableEnded === true) {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (this.response.headersSent === false) {
|
|
203
|
+
this.response.statusCode = statusCode
|
|
204
|
+
this.response.setHeader("Content-Type", contentType)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async text(data: unknown): Promise<void> {
|
|
209
|
+
this.prepareResponse("text/plain; charset=utf-8")
|
|
210
|
+
|
|
211
|
+
await new Promise<void>((resolve) => {
|
|
212
|
+
this.response.end(String(data), () => {
|
|
213
|
+
resolve()
|
|
214
|
+
})
|
|
215
|
+
})
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async json(data: unknown): Promise<void> {
|
|
219
|
+
this.prepareResponse("application/json; charset=utf-8")
|
|
220
|
+
|
|
221
|
+
await new Promise<void>((resolve) => {
|
|
222
|
+
this.response.end(JSON.stringify(data), () => {
|
|
223
|
+
resolve()
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async stream(): Promise<ApiCoreStream> {
|
|
229
|
+
this.prepareResponse("text/event-stream; charset=utf-8")
|
|
230
|
+
this.response.setHeader("Cache-Control", "no-cache")
|
|
231
|
+
this.response.setHeader("Connection", "keep-alive")
|
|
232
|
+
this.response.flushHeaders()
|
|
233
|
+
|
|
234
|
+
const stream = new NodeHttpApiCoreStream(this.response)
|
|
235
|
+
return await Promise.resolve(stream)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async error(): Promise<void> {
|
|
239
|
+
if (this.response.writableEnded === true) {
|
|
240
|
+
return await Promise.resolve()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
this.prepareResponse("application/json; charset=utf-8", 500)
|
|
244
|
+
|
|
245
|
+
await new Promise<void>((resolve) => {
|
|
246
|
+
this.response.end(JSON.stringify({
|
|
247
|
+
status: "error",
|
|
248
|
+
data: {
|
|
249
|
+
reason: "internal_server_error",
|
|
250
|
+
},
|
|
251
|
+
}), () => {
|
|
252
|
+
resolve()
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export const createApiCoreNodeHttp = (options: ApiCoreNodeHttpOptions): ApiCoreNodeHttp => {
|
|
259
|
+
return new ApiCoreNodeHttp(options)
|
|
260
|
+
}
|