@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.
- package/CHANGELOG.md +37 -0
- package/dist/index.js +706 -36
- package/dist/index.js.map +855 -59
- package/package.json +28 -16
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +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/event/class-event-proxy.ts +6 -5
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +6 -5
- package/src/event/internal.ts +4 -4
- 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/index.ts +19 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +9 -7
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +2 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +8 -7
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +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/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/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +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/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/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- package/tests/unit/credential/api-key.spec.ts +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/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- package/tests/unit/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/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +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/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/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
SearchUnion,
|
|
3
|
+
PathnameUnion,
|
|
4
|
+
HashUnion,
|
|
5
|
+
} from "../uri/index.ts"
|
|
6
|
+
import {
|
|
7
|
+
isHashIncludes,
|
|
8
|
+
isHashLooseEqual,
|
|
9
|
+
isHashLooseIncludes,
|
|
10
|
+
isHashStrictEqual,
|
|
11
|
+
isHashStrictIncludes,
|
|
12
|
+
isSearchIncludes,
|
|
13
|
+
isSearchLooseEqual,
|
|
14
|
+
isSearchLooseIncludes,
|
|
15
|
+
isSearchStrictEqual,
|
|
16
|
+
isSearchStrictIncludes,
|
|
17
|
+
toPathnameArray,
|
|
18
|
+
toPathnameString,
|
|
19
|
+
toHashObject,
|
|
20
|
+
toHashString,
|
|
21
|
+
toQueryObject,
|
|
22
|
+
toQueryString,
|
|
23
|
+
toSearch,
|
|
24
|
+
isPathnameLooseIncludes,
|
|
25
|
+
isPathnameStrictIncludes,
|
|
26
|
+
isPathnameLooseEqual,
|
|
27
|
+
isPathnameStrictEqual
|
|
28
|
+
} from "../uri/index.ts"
|
|
29
|
+
|
|
30
|
+
const isString = (target: unknown): target is string => {
|
|
31
|
+
return typeof target === "string"
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const isObject = (target: unknown): target is object => {
|
|
35
|
+
if (typeof target !== "object" && typeof target !== "function") {
|
|
36
|
+
return false
|
|
37
|
+
}
|
|
38
|
+
if (target === null) {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
return true
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isPlainObject = (target: unknown): target is Record<PropertyKey, unknown> => {
|
|
45
|
+
if (isObject(target) === false) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (Object.prototype.toString.call(target) !== "[object Object]") {
|
|
50
|
+
return false
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// oxlint-disable-next-line typescript/no-unsafe-assignment
|
|
54
|
+
const prototype = Object.getPrototypeOf(target)
|
|
55
|
+
if (prototype !== null && prototype !== Object.prototype) {
|
|
56
|
+
return false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return true
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* URL 中 pathname 及其后续部分的组合值。
|
|
64
|
+
*/
|
|
65
|
+
export type PartialUrl = string
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 判断给定值是否可以视为 PartialUrl。
|
|
69
|
+
*/
|
|
70
|
+
export const isPartialUrl = (target: unknown): target is PartialUrl => {
|
|
71
|
+
return isString(target)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Route 的标准化记录结构。
|
|
76
|
+
*/
|
|
77
|
+
export interface RouteRecord {
|
|
78
|
+
partialUrl: PartialUrl
|
|
79
|
+
pathname: string
|
|
80
|
+
search: string
|
|
81
|
+
hash: string
|
|
82
|
+
|
|
83
|
+
pathnameString: string
|
|
84
|
+
pathnameArray: string[]
|
|
85
|
+
query: string
|
|
86
|
+
queryString: string
|
|
87
|
+
queryObject: Record<string, string>
|
|
88
|
+
hashString: string
|
|
89
|
+
hashObject: Record<string, string>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const DEFAULT_ROUTE_RECORD: RouteRecord = {
|
|
93
|
+
partialUrl: "",
|
|
94
|
+
pathname: "",
|
|
95
|
+
search: "",
|
|
96
|
+
hash: "",
|
|
97
|
+
|
|
98
|
+
pathnameString: "",
|
|
99
|
+
pathnameArray: [],
|
|
100
|
+
query: "",
|
|
101
|
+
queryString: "",
|
|
102
|
+
queryObject: {},
|
|
103
|
+
hashString: "",
|
|
104
|
+
hashObject: {},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 判断给定值是否满足 RouteRecord 的最小公共结构。
|
|
109
|
+
*/
|
|
110
|
+
export const isRouteRecord = (target: unknown): target is RouteRecord => {
|
|
111
|
+
if (isPlainObject(target) === false) {
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
if ("partialUrl" in target === false) {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
if (isPartialUrl(target["partialUrl"]) === false) {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
if ("pathname" in target === false) {
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
if (isString(target["pathname"]) === false) {
|
|
124
|
+
return false
|
|
125
|
+
}
|
|
126
|
+
if ("search" in target === false) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
if (isString(target["search"]) === false) {
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
if ("hash" in target === false) {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
if (isString(target["hash"]) === false) {
|
|
136
|
+
return false
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return true
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* 将 PartialUrl 解析为 URL 时使用的选项。
|
|
144
|
+
*/
|
|
145
|
+
export interface PartialUrlToUrlOptions {
|
|
146
|
+
partialUrl: PartialUrl
|
|
147
|
+
baseUrl?: string | undefined
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* 将 PartialUrl 解析为完整的 URL 对象。
|
|
152
|
+
*/
|
|
153
|
+
export const partialUrlToUrl = (options: PartialUrlToUrlOptions): URL => {
|
|
154
|
+
const { partialUrl, baseUrl } = options
|
|
155
|
+
|
|
156
|
+
// JavaScript 的 URL 构造函数在处理相对路径时,必须提供一个完整的基准 URL。
|
|
157
|
+
const PLACEHOLDER_URL_BASE = "https://url.example.com/"
|
|
158
|
+
const intermediateUrl = new URL(baseUrl ?? "/", PLACEHOLDER_URL_BASE)
|
|
159
|
+
const resultUrl = new URL(partialUrl, intermediateUrl)
|
|
160
|
+
|
|
161
|
+
return resultUrl
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const constrainSearch = (search: string): string => {
|
|
165
|
+
return search === "?" ? "" : search
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const constrainHash = (hash: string): string => {
|
|
169
|
+
return hash === "#" ? "" : hash
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* 将 URL 拆解为标准化的 RouteRecord。
|
|
174
|
+
*/
|
|
175
|
+
export const urlToRouteRecord = (url: URL): RouteRecord => {
|
|
176
|
+
const pathname = toPathnameString(url.pathname)
|
|
177
|
+
const search = constrainSearch(toSearch(url.search))
|
|
178
|
+
const hash = constrainHash(toHashString(url.hash))
|
|
179
|
+
|
|
180
|
+
const pathnameString = pathname
|
|
181
|
+
const pathnameArray = toPathnameArray(pathname)
|
|
182
|
+
const query = toQueryString(search)
|
|
183
|
+
const queryString = toQueryString(search)
|
|
184
|
+
const queryObject = toQueryObject(search)
|
|
185
|
+
const hashString = hash
|
|
186
|
+
const hashObject = toHashObject(hash)
|
|
187
|
+
|
|
188
|
+
const partialUrl = `${pathnameString}${search}${hashString}`
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...DEFAULT_ROUTE_RECORD,
|
|
192
|
+
|
|
193
|
+
partialUrl,
|
|
194
|
+
pathname,
|
|
195
|
+
search,
|
|
196
|
+
hash,
|
|
197
|
+
|
|
198
|
+
pathnameString,
|
|
199
|
+
pathnameArray,
|
|
200
|
+
query,
|
|
201
|
+
queryString,
|
|
202
|
+
queryObject,
|
|
203
|
+
hashString,
|
|
204
|
+
hashObject,
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 将 PartialUrl 直接转换为标准化的 RouteRecord。
|
|
210
|
+
*/
|
|
211
|
+
export const partialUrlToRouteRecord = (partialUrl: PartialUrl): RouteRecord => {
|
|
212
|
+
return urlToRouteRecord(partialUrlToUrl({ partialUrl }))
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Route 构造时需要的输入选项。
|
|
217
|
+
*/
|
|
218
|
+
export interface RouteOptions {
|
|
219
|
+
partialUrl: PartialUrl
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* 判断给定值是否满足 RouteOptions 结构。
|
|
224
|
+
*/
|
|
225
|
+
export const isRouteOptions = (target: unknown): target is RouteOptions => {
|
|
226
|
+
if (isPlainObject(target) === false) {
|
|
227
|
+
return false
|
|
228
|
+
}
|
|
229
|
+
if ("partialUrl" in target === false) {
|
|
230
|
+
return false
|
|
231
|
+
}
|
|
232
|
+
if (isPartialUrl(target["partialUrl"]) === false) {
|
|
233
|
+
return false
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return true
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* 维护 pathname、search 与 hash 一致性的路由模型。
|
|
240
|
+
*
|
|
241
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/URL}
|
|
242
|
+
*/
|
|
243
|
+
export class Route<Payload = unknown> {
|
|
244
|
+
protected options: RouteOptions
|
|
245
|
+
protected record: RouteRecord
|
|
246
|
+
protected payload: Payload | undefined
|
|
247
|
+
|
|
248
|
+
constructor(options: RouteOptions) {
|
|
249
|
+
this.options = options
|
|
250
|
+
this.record = partialUrlToRouteRecord(options.partialUrl)
|
|
251
|
+
this.payload = undefined
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* 创建当前 Route 的深拷贝。
|
|
256
|
+
*
|
|
257
|
+
* 克隆结果会复制当前的标准化地址记录与 payload,避免后续修改影响原对象。
|
|
258
|
+
*/
|
|
259
|
+
clone(): Route<Payload> {
|
|
260
|
+
const newRoute = new Route<Payload>({ partialUrl: this.record.partialUrl })
|
|
261
|
+
newRoute.setPayload(this.payload)
|
|
262
|
+
return newRoute
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 返回当前挂载在 Route 上的业务负载。
|
|
267
|
+
*/
|
|
268
|
+
getPayload(): Payload | undefined {
|
|
269
|
+
return this.payload
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* 设置当前 Route 对应的业务负载。
|
|
274
|
+
*/
|
|
275
|
+
setPayload(payload: Payload | undefined): this {
|
|
276
|
+
this.payload = payload
|
|
277
|
+
return this
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 返回当前 RouteRecord 的深拷贝快照。
|
|
282
|
+
*/
|
|
283
|
+
getRecord(): RouteRecord {
|
|
284
|
+
return structuredClone(this.record)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
protected correctPartialUrl(): this {
|
|
288
|
+
const { pathname, search, hash } = this.record
|
|
289
|
+
this.record.partialUrl = `${pathname}${search}${hash}`
|
|
290
|
+
return this
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* 清空当前 search 及其派生的 query 表达。
|
|
295
|
+
*/
|
|
296
|
+
resetSearch(): this {
|
|
297
|
+
this.record.search = DEFAULT_ROUTE_RECORD.search
|
|
298
|
+
this.record.query = DEFAULT_ROUTE_RECORD.query
|
|
299
|
+
this.record.queryString = DEFAULT_ROUTE_RECORD.queryString
|
|
300
|
+
this.record.queryObject = DEFAULT_ROUTE_RECORD.queryObject
|
|
301
|
+
this.correctPartialUrl()
|
|
302
|
+
return this
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 清空当前 query 表达。
|
|
307
|
+
*/
|
|
308
|
+
resetQuery(): this {
|
|
309
|
+
this.resetSearch()
|
|
310
|
+
return this
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* 清空当前 hash 及其对象化表达。
|
|
315
|
+
*/
|
|
316
|
+
resetHash(): this {
|
|
317
|
+
this.record.hash = DEFAULT_ROUTE_RECORD.hash
|
|
318
|
+
this.record.hashString = DEFAULT_ROUTE_RECORD.hashString
|
|
319
|
+
this.record.hashObject = DEFAULT_ROUTE_RECORD.hashObject
|
|
320
|
+
this.correctPartialUrl()
|
|
321
|
+
return this
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 用完整 URL 替换当前 RouteRecord。
|
|
326
|
+
*/
|
|
327
|
+
url(url: URL): this {
|
|
328
|
+
this.record = urlToRouteRecord(url)
|
|
329
|
+
return this
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* 用 PartialUrl 替换当前 RouteRecord。
|
|
334
|
+
*/
|
|
335
|
+
partialUrl(partialUrl: PartialUrl): this {
|
|
336
|
+
this.record = partialUrlToRouteRecord(partialUrl)
|
|
337
|
+
return this
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 更新 pathname,并在路径变化时重置 search 与 hash。
|
|
342
|
+
*/
|
|
343
|
+
pathname(options: { pathname: PathnameUnion }): this {
|
|
344
|
+
const { pathname } = options
|
|
345
|
+
|
|
346
|
+
const oldPathnameString = this.record.pathnameString
|
|
347
|
+
|
|
348
|
+
const newPathnameString = toPathnameString(pathname)
|
|
349
|
+
this.record.pathname = newPathnameString
|
|
350
|
+
this.record.pathnameString = newPathnameString
|
|
351
|
+
this.record.pathnameArray = toPathnameArray(newPathnameString)
|
|
352
|
+
|
|
353
|
+
// when path is changed, reset query and hash
|
|
354
|
+
if (oldPathnameString !== newPathnameString) {
|
|
355
|
+
this.resetSearch()
|
|
356
|
+
this.resetQuery()
|
|
357
|
+
this.resetHash()
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.correctPartialUrl()
|
|
361
|
+
return this
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* 更新 search,并同步刷新 query 的字符串与对象表示。
|
|
366
|
+
*/
|
|
367
|
+
search(options: { search: SearchUnion }): this {
|
|
368
|
+
const { search } = options
|
|
369
|
+
|
|
370
|
+
this.record.search = constrainSearch(toSearch(search))
|
|
371
|
+
this.record.query = toQueryString(search)
|
|
372
|
+
this.record.queryString = toQueryString(search)
|
|
373
|
+
this.record.queryObject = toQueryObject(search)
|
|
374
|
+
|
|
375
|
+
this.correctPartialUrl()
|
|
376
|
+
return this
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* 以合并方式写入 search,不存在的键会新增,已存在的键会覆盖。
|
|
381
|
+
*/
|
|
382
|
+
upsertSearch(options: { search: SearchUnion }): this {
|
|
383
|
+
const { search } = options
|
|
384
|
+
|
|
385
|
+
const patchQueryObject = isString(search) ? toQueryObject(search) : search
|
|
386
|
+
const newQueryObject = { ...this.record.queryObject, ...patchQueryObject }
|
|
387
|
+
|
|
388
|
+
this.search({ search: toQueryString(newQueryObject) })
|
|
389
|
+
|
|
390
|
+
return this
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* 更新 hash,并同步刷新 hash 的对象表示。
|
|
395
|
+
*/
|
|
396
|
+
hash(options: { hash: HashUnion }): this {
|
|
397
|
+
const { hash } = options
|
|
398
|
+
|
|
399
|
+
if (hash === "") {
|
|
400
|
+
this.resetHash()
|
|
401
|
+
return this
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const hashString = constrainHash(toHashString(hash))
|
|
405
|
+
|
|
406
|
+
this.record.hash = hashString
|
|
407
|
+
this.record.hashString = hashString
|
|
408
|
+
this.record.hashObject = toHashObject(hashString)
|
|
409
|
+
|
|
410
|
+
this.correctPartialUrl()
|
|
411
|
+
return this
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* 比较两个 Route 或 RouteRecord 的 partialUrl 是否不同。
|
|
416
|
+
*/
|
|
417
|
+
static distinctByPartialUrl(routeA: Route | RouteRecord, routeB: Route | RouteRecord): boolean {
|
|
418
|
+
const routeRecordA = isRoute(routeA) ? routeA.getRecord() : routeA
|
|
419
|
+
const routeRecordB = isRoute(routeB) ? routeB.getRecord() : routeB
|
|
420
|
+
return routeRecordA.partialUrl !== routeRecordB.partialUrl
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* 比较两个 Route 或 RouteRecord 的 pathname 是否不同。
|
|
425
|
+
*/
|
|
426
|
+
static distinctByPathname(routeA: Route | RouteRecord, routeB: Route | RouteRecord): boolean {
|
|
427
|
+
const routeRecordA = isRoute(routeA) ? routeA.getRecord() : routeA
|
|
428
|
+
const routeRecordB = isRoute(routeB) ? routeB.getRecord() : routeB
|
|
429
|
+
return routeRecordA.pathnameString !== routeRecordB.pathnameString
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* 比较两个 Route 或 RouteRecord 的 search 是否不同。
|
|
434
|
+
*/
|
|
435
|
+
static distinctBySearch(routeA: Route | RouteRecord, routeB: Route | RouteRecord): boolean {
|
|
436
|
+
const routeRecordA = isRoute(routeA) ? routeA.getRecord() : routeA
|
|
437
|
+
const routeRecordB = isRoute(routeB) ? routeB.getRecord() : routeB
|
|
438
|
+
return routeRecordA.search !== routeRecordB.search
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* 比较两个 Route 或 RouteRecord 的 hash 是否不同。
|
|
443
|
+
*/
|
|
444
|
+
static distinctByHash(routeA: Route | RouteRecord, routeB: Route | RouteRecord): boolean {
|
|
445
|
+
const routeRecordA = isRoute(routeA) ? routeA.getRecord() : routeA
|
|
446
|
+
const routeRecordB = isRoute(routeB) ? routeB.getRecord() : routeB
|
|
447
|
+
return routeRecordA.hashString !== routeRecordB.hashString
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* 判断当前 Route 与目标 Route 或 RouteRecord 的 partialUrl 是否不同。
|
|
452
|
+
*/
|
|
453
|
+
isDistinctByPartialUrl(route: Route | RouteRecord): boolean {
|
|
454
|
+
const targetRouteRecord = isRoute(route) ? route.getRecord() : route
|
|
455
|
+
return this.record.partialUrl !== targetRouteRecord.partialUrl
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* 判断当前 Route 与目标 Route 或 RouteRecord 的 pathname 是否不同。
|
|
460
|
+
*/
|
|
461
|
+
isDistinctByPathname(route: Route | RouteRecord): boolean {
|
|
462
|
+
const targetRouteRecord = isRoute(route) ? route.getRecord() : route
|
|
463
|
+
return this.record.pathnameString !== targetRouteRecord.pathnameString
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* 判断当前 Route 与目标 Route 或 RouteRecord 的 search 是否不同。
|
|
468
|
+
*/
|
|
469
|
+
isDistinctBySearch(route: Route | RouteRecord): boolean {
|
|
470
|
+
const targetRouteRecord = isRoute(route) ? route.getRecord() : route
|
|
471
|
+
return this.record.search !== targetRouteRecord.search
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* 判断当前 Route 与目标 Route 或 RouteRecord 的 hash 是否不同。
|
|
476
|
+
*/
|
|
477
|
+
isDistinctByHash(route: Route | RouteRecord): boolean {
|
|
478
|
+
const targetRouteRecord = isRoute(route) ? route.getRecord() : route
|
|
479
|
+
return this.record.hashString !== targetRouteRecord.hashString
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* 以宽松模式判断当前 pathname 是否包含指定路径段。
|
|
484
|
+
*/
|
|
485
|
+
isPathnameLooseIncludes(pieces: string | string[]): boolean {
|
|
486
|
+
return isPathnameLooseIncludes(this.record.pathname, pieces)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* 以严格模式判断当前 pathname 是否包含指定路径片段。
|
|
491
|
+
*/
|
|
492
|
+
isPathnameStrictIncludes(pieces: string | string[]): boolean {
|
|
493
|
+
return isPathnameStrictIncludes(this.record.pathname, pieces)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 以宽松模式判断当前 pathname 是否与目标值相等。
|
|
498
|
+
*/
|
|
499
|
+
isPathnameLooseEqual(pathname: PathnameUnion): boolean {
|
|
500
|
+
return isPathnameLooseEqual(this.record.pathname, pathname)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* 以严格模式判断当前 pathname 是否与目标值相等。
|
|
505
|
+
*/
|
|
506
|
+
isPathnameStrictEqual(pathname: PathnameUnion): boolean {
|
|
507
|
+
return isPathnameStrictEqual(this.record.pathname, pathname)
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* 宽松包含关系:查询参数的键集合是目标查询参数键集合的子集。
|
|
512
|
+
* 例如:`?a=1&b=2` 包含 `?a=1`,但不包含 `?a=1&c=3`。
|
|
513
|
+
*/
|
|
514
|
+
isSearchLooseIncludes(queryKeys: string | string[]): boolean {
|
|
515
|
+
return isSearchLooseIncludes(this.record.search, queryKeys)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* 严格包含关系:查询参数的键集合与目标查询参数键集合完全相同。
|
|
520
|
+
* 例如:`?a=1&b=2` 严格包含 `?a=1&b=2`,但不严格包含 `?a=1` 或 `?a=1&c=3`。
|
|
521
|
+
*/
|
|
522
|
+
isSearchStrictIncludes(queryKeys: string | string[]): boolean {
|
|
523
|
+
return isSearchStrictIncludes(this.record.search, queryKeys)
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* 包含关系:查询参数的键集合必须包含目标查询参数键集合,并且不能包含除目标查询参数键集合之外的其他键。
|
|
528
|
+
* 例如:`?a=1&b=2` 包含 `?a=1`,但不包含 `?a=1&c=3` 或 `?a=1&b=2&c=3`。
|
|
529
|
+
*/
|
|
530
|
+
isSearchIncludes(query: Record<string, "required" | "optional">): boolean {
|
|
531
|
+
return isSearchIncludes(this.record.search, query)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 此处的 Equal 语义与 Match 类似。
|
|
536
|
+
*
|
|
537
|
+
* 宽松相等:查询参数的键集合是目标查询参数键集合的子集,并且对应的值相等。
|
|
538
|
+
* 例如:`?a=1&b=2` 宽松相等 `?a=1`,但不宽松相等 `?a=1&c=3`。
|
|
539
|
+
*/
|
|
540
|
+
isSearchLooseEqual(query: SearchUnion): boolean {
|
|
541
|
+
return isSearchLooseEqual(this.record.search, query)
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* 严格相等:查询参数的键集合与目标查询参数键集合完全相同,并且对应的值相等。
|
|
546
|
+
* 例如:`?a=1&b=2` 严格相等 `?a=1&b=2`,但不严格相等 `?a=1` 或 `?a=1&c=3`。
|
|
547
|
+
*/
|
|
548
|
+
isSearchStrictEqual(query: SearchUnion): boolean {
|
|
549
|
+
return isSearchStrictEqual(this.record.search, query)
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* 宽松包含关系:hash 参数的键集合是目标 hash 参数键集合的子集。
|
|
554
|
+
* 例如:`#a=1&b=2` 包含 `#a=1`,但不包含 `#a=1&c=3`。
|
|
555
|
+
*/
|
|
556
|
+
isHashLooseIncludes(hashKeys: string | string[]): boolean {
|
|
557
|
+
return isHashLooseIncludes(this.record.hash, hashKeys)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* 严格包含关系:hash 参数的键集合与目标 hash 参数键集合完全相同。
|
|
562
|
+
* 例如:`#a=1&b=2` 严格包含 `#a=1&b=2`,但不严格包含 `#a=1` 或 `#a=1&c=3`。
|
|
563
|
+
*/
|
|
564
|
+
isHashStrictIncludes(hashKeys: string | string[]): boolean {
|
|
565
|
+
return isHashStrictIncludes(this.record.hash, hashKeys)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* 包含关系:hash 参数的键集合必须包含目标 hash 参数键集合,并且不能包含除目标 hash 参数键集合之外的其他键。
|
|
570
|
+
* 例如:`#a=1&b=2` 包含 `#a=1`,但不包含 `#a=1&c=3` 或 `#a=1&b=2&c=3`。
|
|
571
|
+
*/
|
|
572
|
+
isHashIncludes(hash: Record<string, "required" | "optional">): boolean {
|
|
573
|
+
return isHashIncludes(this.record.hash, hash)
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* 此处的 Equal 语义与 Match 类似。
|
|
578
|
+
*
|
|
579
|
+
* 宽松相等:hash 参数的键集合是目标 hash 参数键集合的子集,并且对应的值相等。
|
|
580
|
+
* 例如:`#a=1&b=2` 宽松相等 `#a=1`,但不宽松相等 `#a=1&c=3`。
|
|
581
|
+
*/
|
|
582
|
+
isHashLooseEqual(hash: HashUnion): boolean {
|
|
583
|
+
return isHashLooseEqual(this.record.hash, hash)
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* 严格相等:hash 参数的键集合与目标 hash 参数键集合完全相同,并且对应的值相等。
|
|
588
|
+
* 例如:`#a=1&b=2` 严格相等 `#a=1&b=2`,但不严格相等 `#a=1` 或 `#a=1&c=3`。
|
|
589
|
+
*/
|
|
590
|
+
isHashStrictEqual(hash: HashUnion): boolean {
|
|
591
|
+
return isHashStrictEqual(this.record.hash, hash)
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* 判断给定值是否为 Route 实例。
|
|
597
|
+
*/
|
|
598
|
+
export const isRoute = (target: unknown): target is Route => {
|
|
599
|
+
return target instanceof Route
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* 将多种 Route 输入统一转换为 Route 实例。
|
|
604
|
+
*
|
|
605
|
+
* 如果输入已经是 Route 实例,则直接返回原对象;否则会基于可识别的路由表达创建新实例。
|
|
606
|
+
*/
|
|
607
|
+
export const createRoute = (target: PartialUrl | RouteRecord | RouteOptions | Route): Route => {
|
|
608
|
+
if (isPartialUrl(target)) {
|
|
609
|
+
return new Route({ partialUrl: target })
|
|
610
|
+
}
|
|
611
|
+
else if (isRouteRecord(target)) {
|
|
612
|
+
return new Route({ partialUrl: target.partialUrl })
|
|
613
|
+
}
|
|
614
|
+
else if (isRouteOptions(target)) {
|
|
615
|
+
return new Route(target)
|
|
616
|
+
}
|
|
617
|
+
else if (isRoute(target)) {
|
|
618
|
+
return target
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
throw (new TypeError("\"target\" is expected to be type of \"String\" or \"Route\" or \"RouteRecord\" or \"RouteOptions\"."))
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* 创建一个指向根路径的空 Route。
|
|
627
|
+
*/
|
|
628
|
+
export const emptyRoute = (): Route => {
|
|
629
|
+
return new Route({ partialUrl: DEFAULT_ROUTE_RECORD.partialUrl })
|
|
630
|
+
}
|