@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.
- package/CHANGELOG.md +50 -0
- package/oxlint.config.ts +1 -2
- package/package.json +29 -17
- package/scripts/build.ts +2 -52
- 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/promise.ts +141 -71
- 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 +198 -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 +5 -6
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +5 -6
- package/src/event/internal.ts +4 -4
- package/src/exception/README.md +28 -19
- package/src/exception/error/error.ts +123 -0
- package/src/exception/error/index.ts +2 -0
- package/src/exception/error/match.ts +38 -0
- package/src/exception/error/must-fix.ts +17 -0
- package/src/exception/index.ts +2 -0
- package/src/file-system/find.ts +53 -0
- package/src/file-system/index.ts +2 -0
- package/src/file-system/path.ts +76 -0
- package/src/file-system/resolve.ts +22 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +861 -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 +179 -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 +34 -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 +294 -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 +21 -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 +509 -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 +280 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +280 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +246 -0
- package/src/request/request/general.ts +63 -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 +58 -0
- package/src/result/either.ts +363 -0
- package/src/result/generator.ts +168 -0
- package/src/result/index.ts +3 -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 +1641 -0
- package/src/route/uri/hash.ts +307 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +412 -0
- package/src/service/README.md +1 -0
- package/src/service/index.ts +1 -0
- package/src/service/service.ts +110 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +658 -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 +448 -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 +137 -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 +157 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +132 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +131 -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 +146 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/promise.spec.ts +158 -50
- 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 +36 -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 +40 -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/exception/error/error.spec.ts +83 -0
- package/tests/unit/exception/error/match.spec.ts +81 -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 +187 -0
- package/tests/unit/identifier/uuid.spec.ts +0 -1
- 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 +382 -0
- package/tests/unit/request/request/general.spec.ts +160 -0
- package/tests/unit/result/controller.spec.ts +82 -0
- package/tests/unit/result/either.spec.ts +377 -0
- package/tests/unit/result/generator.spec.ts +273 -0
- package/tests/unit/route/router/route.spec.ts +430 -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 +146 -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 +133 -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/vite.config.ts +2 -1
- package/dist/index.js +0 -50
- package/dist/index.js.map +0 -209
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
|
|
2
|
+
// oxlint-disable-next-line typescript/no-explicit-any
|
|
3
|
+
export type AnyEither = Either<any, any>
|
|
4
|
+
export type GetLeft<T> = T extends Either<infer Left, unknown> ? Left : never
|
|
5
|
+
export type GetRight<T> = T extends Either<unknown, infer Right> ? Right : never
|
|
6
|
+
export type GetRights<Eithers> = Eithers extends []
|
|
7
|
+
? []
|
|
8
|
+
: (
|
|
9
|
+
Eithers extends [infer Item, ...infer xs]
|
|
10
|
+
? (
|
|
11
|
+
Item extends Either<unknown, infer Right>
|
|
12
|
+
? [Right, ...GetRights<xs>]
|
|
13
|
+
: never
|
|
14
|
+
)
|
|
15
|
+
: never
|
|
16
|
+
)
|
|
17
|
+
export type GetLefts<Eithers> = Eithers extends []
|
|
18
|
+
? []
|
|
19
|
+
: (
|
|
20
|
+
Eithers extends [infer Item, ...infer xs]
|
|
21
|
+
? (
|
|
22
|
+
Item extends Either<infer Left, unknown>
|
|
23
|
+
? [Left, ...GetLefts<xs>]
|
|
24
|
+
: never
|
|
25
|
+
)
|
|
26
|
+
: never
|
|
27
|
+
)
|
|
28
|
+
type TupleToUnion<Tuple> = Tuple extends unknown[] ? Tuple[number] : never
|
|
29
|
+
|
|
30
|
+
export abstract class Either<L, R> {
|
|
31
|
+
static isEither<L, R>(target: unknown): target is Either<L, R> {
|
|
32
|
+
return target instanceof Either
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static isLeft<L, R>(target: unknown): target is Left<L, R> {
|
|
36
|
+
return Left.isLeft(target)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
static isRight<L, R>(target: unknown): target is Right<L, R> {
|
|
40
|
+
return Right.isRight(target)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static left<L>(value: L): Either<L, never>
|
|
44
|
+
static left<L, R>(value: L): Either<L, R>
|
|
45
|
+
static left<L, R>(value: L): Either<L, R> {
|
|
46
|
+
return new Left(value)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
static right<R>(value: R): Either<never, R>
|
|
50
|
+
static right<L, R>(value: R): Either<L, R>
|
|
51
|
+
static right<L, R>(value: R): Either<L, R> {
|
|
52
|
+
return new Right(value)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
static pure<R>(value: R): Either<never, R>
|
|
56
|
+
static pure<L, R>(value: R): Either<L, R>
|
|
57
|
+
static pure<L, R>(value: R): Either<L, R> {
|
|
58
|
+
return new Right(value)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
static lift<NewR, Eithers extends AnyEither[]>(
|
|
62
|
+
eithers: [...Eithers],
|
|
63
|
+
map: (eitherRights: GetRights<Eithers>) => NewR,
|
|
64
|
+
): Either<TupleToUnion<GetLefts<Eithers>>, NewR> {
|
|
65
|
+
const firstLeft = eithers.find(either => either.isLeft())
|
|
66
|
+
if (firstLeft !== undefined) {
|
|
67
|
+
return firstLeft as Either<TupleToUnion<GetLefts<Eithers>>, NewR>
|
|
68
|
+
}
|
|
69
|
+
const rights = eithers.map(either => {
|
|
70
|
+
// oxlint-disable-next-line typescript/no-unsafe-return
|
|
71
|
+
return either.assertRight().getRight()
|
|
72
|
+
}) as GetRights<Eithers>
|
|
73
|
+
const newRightValue = map(rights)
|
|
74
|
+
return Either.pure(newRightValue)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
abstract getValue(): L | R
|
|
78
|
+
|
|
79
|
+
abstract getLeft(): L
|
|
80
|
+
abstract getRight(): R
|
|
81
|
+
abstract getRightOr(or: R): R
|
|
82
|
+
|
|
83
|
+
abstract isLeft(): this is Left<L, R>
|
|
84
|
+
abstract isRight(): this is Right<L, R>
|
|
85
|
+
|
|
86
|
+
abstract assertLeft(): Left<L, R>
|
|
87
|
+
abstract assertRight(): Right<L, R>
|
|
88
|
+
|
|
89
|
+
abstract tap(effects: {
|
|
90
|
+
left?: ((value: L) => void) | undefined
|
|
91
|
+
right?: ((value: R) => void) | undefined
|
|
92
|
+
}): Either<L, R>
|
|
93
|
+
abstract tapLeft(effect: (value: L) => void): Either<L, R>
|
|
94
|
+
abstract tapRight(effect: (value: R) => void): Either<L, R>
|
|
95
|
+
|
|
96
|
+
abstract mapLeft<NewL>(map: (value: L) => NewL): Either<NewL, R>
|
|
97
|
+
abstract mapRight<NewR>(map: (value: R) => NewR): Either<L, NewR>
|
|
98
|
+
abstract bind<NewL, NewR>(bind: (value: R) => Either<NewL, NewR>): Either<L | NewL, NewR>
|
|
99
|
+
abstract bindAsync<NewL, NewR>(bind: (value: R) => Promise<Either<NewL, NewR>>): Promise<Either<L | NewL, NewR>>
|
|
100
|
+
|
|
101
|
+
abstract match<X>(
|
|
102
|
+
left: (value: L) => X,
|
|
103
|
+
right: (value: R) => X
|
|
104
|
+
): X
|
|
105
|
+
abstract matchAsync<X>(
|
|
106
|
+
left: (value: L) => Promise<X>,
|
|
107
|
+
right: (value: R) => Promise<X>
|
|
108
|
+
): Promise<X>
|
|
109
|
+
|
|
110
|
+
abstract mplus(either: Either<L, R>): Either<L, R>
|
|
111
|
+
|
|
112
|
+
abstract [Symbol.iterator](): Generator<Either<L, R>, R, R>
|
|
113
|
+
// Async consumers can fall back to Symbol.iterator, but we still expose the
|
|
114
|
+
// same semantics explicitly so Either remains self-descriptive as an async iterable.
|
|
115
|
+
abstract [Symbol.asyncIterator](): AsyncGenerator<Either<L, R>, R, unknown>
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export class Left<L, R> extends Either<L, R> {
|
|
119
|
+
protected value: L
|
|
120
|
+
|
|
121
|
+
constructor(value: L) {
|
|
122
|
+
super()
|
|
123
|
+
this.value = value
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
getValue(): L | R {
|
|
127
|
+
return this.value
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getLeft(): L {
|
|
131
|
+
return this.value
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
getRight(): R {
|
|
135
|
+
throw new Error("Cannot get right value from Left")
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getRightOr(or: R): R {
|
|
139
|
+
return or
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
isLeft(): this is Left<L, R> {
|
|
143
|
+
return true
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
isRight(): this is Right<L, R> {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
assertLeft(): this {
|
|
151
|
+
return this
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
assertRight(): Right<L, R> {
|
|
155
|
+
throw new Error("Assert failed")
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
tap(effects: {
|
|
159
|
+
left?: ((value: L) => void) | undefined
|
|
160
|
+
right?: ((value: R) => void) | undefined
|
|
161
|
+
}): Either<L, R> {
|
|
162
|
+
if (effects.left !== undefined) {
|
|
163
|
+
effects.left(this.value)
|
|
164
|
+
}
|
|
165
|
+
return this
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
tapLeft(effect: (value: L) => void): Either<L, R> {
|
|
169
|
+
effect(this.value)
|
|
170
|
+
return this
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
tapRight(_effect: (value: R) => void): Either<L, R> {
|
|
174
|
+
return this
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
mapLeft<NewL>(map: (value: L) => NewL): Either<NewL, R> {
|
|
178
|
+
return new Left(map(this.value))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
mapRight<NewR>(_map: (value: R) => NewR): Either<L, NewR> {
|
|
182
|
+
return new Left(this.value)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
bind<NewL, NewR>(_map: (value: R) => Either<NewL, NewR>): Either<L | NewL, NewR> {
|
|
186
|
+
return new Left(this.value) as Either<L | NewL, NewR>
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async bindAsync<NewL, NewR>(_bind: (value: R) => Promise<Either<NewL, NewR>>): Promise<Either<L | NewL, NewR>> {
|
|
190
|
+
return await Promise.resolve(new Left(this.value) as Either<L | NewL, NewR>)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
match<X>(
|
|
194
|
+
left: (value: L) => X,
|
|
195
|
+
_right: (value: R) => X
|
|
196
|
+
): X {
|
|
197
|
+
return left(this.value)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async matchAsync<X>(
|
|
201
|
+
left: (value: L) => Promise<X>,
|
|
202
|
+
_right: (value: R) => Promise<X>
|
|
203
|
+
): Promise<X> {
|
|
204
|
+
return await left(this.value)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
mplus(either: Either<L, R>): Either<L, R> {
|
|
208
|
+
return either
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
*[Symbol.iterator](): Generator<Either<L, R>, R, R> {
|
|
212
|
+
yield this
|
|
213
|
+
throw new Error("Cannot extract right value from Left")
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// This mirrors the sync iterator exactly; it is explicit protocol support,
|
|
217
|
+
// not a requirement for async yield* fallback.
|
|
218
|
+
async *[Symbol.asyncIterator](): AsyncGenerator<Either<L, R>, R, unknown> {
|
|
219
|
+
await Promise.resolve()
|
|
220
|
+
yield this
|
|
221
|
+
throw new Error("Cannot extract right value from Left")
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export class Right<L, R> extends Either<L, R> {
|
|
226
|
+
protected value: R
|
|
227
|
+
|
|
228
|
+
constructor(value: R) {
|
|
229
|
+
super()
|
|
230
|
+
this.value = value
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
getValue(): L | R {
|
|
234
|
+
return this.value
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
getLeft(): L {
|
|
238
|
+
throw new Error("Cannot get left value from Right")
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
getRight(): R {
|
|
242
|
+
return this.value
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
getRightOr(_or: R): R {
|
|
246
|
+
return this.value
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
isLeft(): this is Left<L, R> {
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
isRight(): this is Right<L, R> {
|
|
254
|
+
return true
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
assertLeft(): Left<L, R> {
|
|
258
|
+
throw new Error("Assert failed")
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
assertRight(): this {
|
|
262
|
+
return this
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
tap(effects: {
|
|
266
|
+
left?: ((value: L) => void) | undefined
|
|
267
|
+
right?: ((value: R) => void) | undefined
|
|
268
|
+
}): Either<L, R> {
|
|
269
|
+
if (effects.right !== undefined) {
|
|
270
|
+
effects.right(this.value)
|
|
271
|
+
}
|
|
272
|
+
return this
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
tapLeft(_effect: (value: L) => void): Either<L, R> {
|
|
276
|
+
return this
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
tapRight(effect: (value: R) => void): Either<L, R> {
|
|
280
|
+
effect(this.value)
|
|
281
|
+
return this
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
mapLeft<NewL>(_map: (value: L) => NewL): Either<NewL, R> {
|
|
285
|
+
return new Right(this.value)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
mapRight<NewR>(map: (value: R) => NewR): Either<L, NewR> {
|
|
289
|
+
return new Right(map(this.value))
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
bind<NewL, NewR>(map: (value: R) => Either<NewL, NewR>): Either<L | NewL, NewR> {
|
|
293
|
+
return map(this.value) as Either<L | NewL, NewR>
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async bindAsync<NewL, NewR>(bind: (value: R) => Promise<Either<NewL, NewR>>): Promise<Either<L | NewL, NewR>> {
|
|
297
|
+
return await bind(this.value) as Either<L | NewL, NewR>
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
match<X>(
|
|
301
|
+
_left: (value: L) => X,
|
|
302
|
+
right: (value: R) => X
|
|
303
|
+
): X {
|
|
304
|
+
return right(this.value)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async matchAsync<X>(
|
|
308
|
+
_left: (value: L) => Promise<X>,
|
|
309
|
+
right: (value: R) => Promise<X>
|
|
310
|
+
): Promise<X> {
|
|
311
|
+
return await right(this.value)
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
mplus(_either: Either<L, R>): Either<L, R> {
|
|
315
|
+
return this
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
*[Symbol.iterator](): Generator<Either<L, R>, R, R> {
|
|
319
|
+
return this.value
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// This mirrors the sync iterator exactly; async consumers could fall back to
|
|
323
|
+
// Symbol.iterator, but the explicit async form keeps the contract obvious.
|
|
324
|
+
async *[Symbol.asyncIterator](): AsyncGenerator<Either<L, R>, R, unknown> {
|
|
325
|
+
await Promise.resolve()
|
|
326
|
+
return this.value
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export const isEither = <L, R>(target: unknown): target is Either<L, R> => {
|
|
331
|
+
return Either.isEither(target)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export const isLeft = <L, R>(target: unknown): target is Left<L, R> => {
|
|
335
|
+
return Left.isLeft(target)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
export const isRight = <L, R>(target: unknown): target is Right<L, R> => {
|
|
339
|
+
return Right.isRight(target)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
type LeftFunction = {
|
|
343
|
+
<L>(value: L): Either<L, never>
|
|
344
|
+
<L, R>(value: L): Either<L, R>
|
|
345
|
+
}
|
|
346
|
+
export const left = (<L, R>(value: L): Either<L, R> => {
|
|
347
|
+
return Either.left<L, R>(value)
|
|
348
|
+
}) as LeftFunction
|
|
349
|
+
|
|
350
|
+
type RightFunction = {
|
|
351
|
+
<R>(value: R): Either<never, R>
|
|
352
|
+
<L, R>(value: R): Either<L, R>
|
|
353
|
+
}
|
|
354
|
+
export const right = (<L, R>(value: R): Either<L, R> => {
|
|
355
|
+
return Either.right<L, R>(value)
|
|
356
|
+
}) as RightFunction
|
|
357
|
+
|
|
358
|
+
export type TupleOfEither<Left, Right> = [undefined, Right] | [Left, undefined]
|
|
359
|
+
export const eitherToTuple = <L, R>(either: Either<L, R>): TupleOfEither<L, R> => {
|
|
360
|
+
const left = either.isLeft() ? either.getLeft() : undefined
|
|
361
|
+
const right = either.isRight() ? either.getRight() : undefined
|
|
362
|
+
return [left, right] as TupleOfEither<L, R>
|
|
363
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { mustFixError } from "#Source/exception/error/must-fix.ts"
|
|
2
|
+
|
|
3
|
+
import type { AnyEither, GetLeft, GetRight } from "./either.ts"
|
|
4
|
+
import { Either } from "./either.ts"
|
|
5
|
+
|
|
6
|
+
export type EitherSyncGenerator<Yield extends AnyEither, Return extends AnyEither>
|
|
7
|
+
= Generator<Yield, Return, unknown>
|
|
8
|
+
export type EitherAsyncGenerator<Yield extends AnyEither, Return extends AnyEither>
|
|
9
|
+
= AsyncGenerator<Yield, Return, unknown>
|
|
10
|
+
|
|
11
|
+
export type GetEitherGeneratorResult<Yield extends AnyEither, Return extends AnyEither> = Either<
|
|
12
|
+
GetLeft<Yield> | GetLeft<Return>,
|
|
13
|
+
GetRight<Return>
|
|
14
|
+
>
|
|
15
|
+
|
|
16
|
+
export const runSyncGenerator = <Yield extends AnyEither, Return extends AnyEither>(
|
|
17
|
+
generator: EitherSyncGenerator<Yield, Return>,
|
|
18
|
+
): GetEitherGeneratorResult<Yield, Return> => {
|
|
19
|
+
let iteratorResult: IteratorResult<Yield, Return>
|
|
20
|
+
try {
|
|
21
|
+
iteratorResult = generator.next()
|
|
22
|
+
} catch (exception) {
|
|
23
|
+
throw mustFixError("Generator threw an exception", { cause: exception })
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (Either.isEither(iteratorResult.value) === false) {
|
|
27
|
+
throw mustFixError("Generator yielded a non-Either value", { cause: iteratorResult.value })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (iteratorResult.done === false) {
|
|
31
|
+
try {
|
|
32
|
+
generator.return?.(undefined as never)
|
|
33
|
+
} catch (exception) {
|
|
34
|
+
throw mustFixError("Generator threw an exception during cleanup", { cause: exception })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return iteratorResult.value
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const runAsyncGenerator = async <Yield extends AnyEither, Return extends AnyEither>(
|
|
42
|
+
generator: EitherAsyncGenerator<Yield, Return>,
|
|
43
|
+
): Promise<GetEitherGeneratorResult<Yield, Return>> => {
|
|
44
|
+
let iteratorResult: IteratorResult<Yield, Return>
|
|
45
|
+
try {
|
|
46
|
+
iteratorResult = await generator.next()
|
|
47
|
+
} catch (exception) {
|
|
48
|
+
throw mustFixError("Generator threw an exception", { cause: exception })
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (Either.isEither(iteratorResult.value) === false) {
|
|
52
|
+
throw mustFixError("Generator yielded a non-Either value", { cause: iteratorResult.value })
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (iteratorResult.done === false) {
|
|
56
|
+
try {
|
|
57
|
+
await generator.return?.(undefined as never)
|
|
58
|
+
} catch (exception) {
|
|
59
|
+
throw mustFixError("Generator threw an exception during cleanup", { cause: exception })
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return iteratorResult.value
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface GenSync {
|
|
67
|
+
<Yield extends AnyEither, Return extends AnyEither>(
|
|
68
|
+
generatorFunction: () => EitherSyncGenerator<Yield, Return>,
|
|
69
|
+
): GetEitherGeneratorResult<Yield, Return>
|
|
70
|
+
<Yield extends AnyEither, Return extends AnyEither, This>(
|
|
71
|
+
generatorFunction: (this: This) => EitherSyncGenerator<Yield, Return>,
|
|
72
|
+
thisArg: This,
|
|
73
|
+
): GetEitherGeneratorResult<Yield, Return>
|
|
74
|
+
}
|
|
75
|
+
export const genSync: GenSync = <Yield extends AnyEither, Return extends AnyEither, This>(
|
|
76
|
+
generatorFunction:
|
|
77
|
+
| (() => EitherSyncGenerator<Yield, Return>)
|
|
78
|
+
| ((this: This) => EitherSyncGenerator<Yield, Return>),
|
|
79
|
+
thisArg?:
|
|
80
|
+
| This
|
|
81
|
+
| undefined,
|
|
82
|
+
): GetEitherGeneratorResult<Yield, Return> => {
|
|
83
|
+
let generator: EitherSyncGenerator<Yield, Return>
|
|
84
|
+
if (thisArg !== undefined) {
|
|
85
|
+
generator = (
|
|
86
|
+
generatorFunction as ((this: This) => EitherSyncGenerator<Yield, Return>)
|
|
87
|
+
).call(thisArg)
|
|
88
|
+
} else {
|
|
89
|
+
generator = (
|
|
90
|
+
generatorFunction as (() => EitherSyncGenerator<Yield, Return>)
|
|
91
|
+
)()
|
|
92
|
+
}
|
|
93
|
+
const result = runSyncGenerator(generator)
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface GenAsync {
|
|
98
|
+
<Yield extends AnyEither, Return extends AnyEither>(
|
|
99
|
+
generatorFunction: () => EitherAsyncGenerator<Yield, Return>,
|
|
100
|
+
): Promise<GetEitherGeneratorResult<Yield, Return>>
|
|
101
|
+
<Yield extends AnyEither, Return extends AnyEither, This>(
|
|
102
|
+
generatorFunction: (this: This) => EitherAsyncGenerator<Yield, Return>,
|
|
103
|
+
thisArg: This,
|
|
104
|
+
): Promise<GetEitherGeneratorResult<Yield, Return>>
|
|
105
|
+
}
|
|
106
|
+
export const genAsync: GenAsync = async <Yield extends AnyEither, Return extends AnyEither, This>(
|
|
107
|
+
generatorFunction:
|
|
108
|
+
| (() => EitherAsyncGenerator<Yield, Return>)
|
|
109
|
+
| ((this: This) => EitherAsyncGenerator<Yield, Return>),
|
|
110
|
+
thisArg?:
|
|
111
|
+
| This
|
|
112
|
+
| undefined,
|
|
113
|
+
): Promise<GetEitherGeneratorResult<Yield, Return>> => {
|
|
114
|
+
let generator: EitherAsyncGenerator<Yield, Return>
|
|
115
|
+
if (thisArg !== undefined) {
|
|
116
|
+
generator = (
|
|
117
|
+
generatorFunction as ((this: This) => EitherAsyncGenerator<Yield, Return>)
|
|
118
|
+
).call(thisArg)
|
|
119
|
+
} else {
|
|
120
|
+
generator = (
|
|
121
|
+
generatorFunction as (() => EitherAsyncGenerator<Yield, Return>)
|
|
122
|
+
)()
|
|
123
|
+
}
|
|
124
|
+
const result = await runAsyncGenerator(generator)
|
|
125
|
+
return result
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface Gen extends GenSync, GenAsync {
|
|
129
|
+
}
|
|
130
|
+
export const gen = (<Yield extends AnyEither, Return extends AnyEither, This>(
|
|
131
|
+
generatorFunction:
|
|
132
|
+
| (() => EitherSyncGenerator<Yield, Return>)
|
|
133
|
+
| ((thisArg: This) => EitherSyncGenerator<Yield, Return>)
|
|
134
|
+
| (() => EitherAsyncGenerator<Yield, Return>)
|
|
135
|
+
| ((thisArg: This) => EitherAsyncGenerator<Yield, Return>),
|
|
136
|
+
thisArg?:
|
|
137
|
+
| This
|
|
138
|
+
| undefined,
|
|
139
|
+
): (
|
|
140
|
+
| GetEitherGeneratorResult<Yield, Return>
|
|
141
|
+
| Promise<GetEitherGeneratorResult<Yield, Return>>
|
|
142
|
+
) => {
|
|
143
|
+
let generator: EitherSyncGenerator<Yield, Return> | EitherAsyncGenerator<Yield, Return>
|
|
144
|
+
if (thisArg !== undefined) {
|
|
145
|
+
generator = (generatorFunction as (
|
|
146
|
+
(this: This) => EitherSyncGenerator<Yield, Return> | EitherAsyncGenerator<Yield, Return>
|
|
147
|
+
)).call(thisArg)
|
|
148
|
+
} else {
|
|
149
|
+
generator = (generatorFunction as (
|
|
150
|
+
() => EitherSyncGenerator<Yield, Return> | EitherAsyncGenerator<Yield, Return>
|
|
151
|
+
))()
|
|
152
|
+
}
|
|
153
|
+
if (Symbol.asyncIterator in generator) {
|
|
154
|
+
return runAsyncGenerator(generator)
|
|
155
|
+
}
|
|
156
|
+
if (Symbol.iterator in generator) {
|
|
157
|
+
return runSyncGenerator(generator)
|
|
158
|
+
}
|
|
159
|
+
throw new TypeError("Invalid generator function")
|
|
160
|
+
}) as Gen
|
|
161
|
+
|
|
162
|
+
export const genAwait = async function* <Left, Right>(
|
|
163
|
+
promise: Promise<Either<Left, Right>>
|
|
164
|
+
): AsyncGenerator<Either<Left, Right>, Right, unknown> {
|
|
165
|
+
const either = await promise
|
|
166
|
+
const result = yield* either
|
|
167
|
+
return result
|
|
168
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Route
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
Route 模块提供围绕命中入口表达、地址片段归一化、路由历史推进以及宿主桥接的基础建模能力。
|
|
6
|
+
|
|
7
|
+
它关注的不是某个框架路由器的页面约定,也不是对浏览器 `location`、`history` 或某个导航 API 的临时封装,而是“一个系统中的当前位置或目标入口,如何被表达为稳定、可比较、可派生、可推进的公共语义”这一更一般的问题。浏览器 URL 是 route 的典型形态,但不是唯一形态。只要某个系统里存在“某个输入命中某个稳定入口,并伴随一组可解释片段或参数”的过程,就存在 route 语义。因此,这个模块更适合被理解为路由领域的基础模型集合,而不是浏览器导航工具箱、前端框架路由方案或零散的 URL 处理函数集合。
|
|
8
|
+
|
|
9
|
+
## For Understanding
|
|
10
|
+
|
|
11
|
+
理解 Route 模块时,应优先把它看作“当前命中入口及其变化过程”的建模层,而不是把它缩窄成页面跳转工具。这个模块当前表达的核心问题有三层:一是地址片段如何被归一化为稳定表示;二是当前路由如何作为一个可读取、可比较、可派生的对象存在;三是路由历史如何被推进、替换、漫游、回退,并以清楚语义向上层暴露。
|
|
12
|
+
|
|
13
|
+
这里的 route 不必被限制在浏览器页面地址中。更一般地说,只要某个系统存在“命中入口”的过程,就可以出现 route 语义。例如浏览器应用中的页面或视图入口、服务端系统中的路径分发入口、CLI 中的命令入口,以及任务系统中的某个目标节点入口,本质上都在回答“输入如何命中某个稳定目标,以及这个目标如何和片段、参数、历史一起被组织起来”这一问题。Route 模块关心的是这个抽象层,而不是某一种宿主上的具体表现。
|
|
14
|
+
|
|
15
|
+
从当前目录结构看,这个模块由三个子边界构成:
|
|
16
|
+
|
|
17
|
+
- `uri`:负责 `pathname`、`search`、`hash` 等片段的归一化、转换与比较语义。
|
|
18
|
+
- `router`:负责 `Route`、`Router`、历史记录以及各类导航动作的模型化表达。
|
|
19
|
+
- `adapter`:负责把 Route 模型接到具体宿主上,例如浏览器环境或响应式消费面。
|
|
20
|
+
|
|
21
|
+
理解这个模块时,还应守住几条边界:
|
|
22
|
+
|
|
23
|
+
- Route 模块表达的是“入口及其历史”的基础模型,不负责替代页面渲染系统、框架路由约定、服务端业务编排、CLI 参数解析器或权限体系。
|
|
24
|
+
- 它可以和浏览器、服务端、CLI 或响应式系统对接,但不应让某个宿主的临时 API 细节反过来定义模块公共语义。
|
|
25
|
+
- 它适合承载入口片段、标准化路由记录、历史动作以及宿主同步边界,但不应直接承担组件装载、数据预取、业务参数解释、命令执行副作用或框架专属生命周期。
|
|
26
|
+
- `adapter` 更适合作为桥接层理解。它的职责是把既有 Route 模型接入宿主,而不是把宿主细节直接提升为父模块语义。
|
|
27
|
+
|
|
28
|
+
## For Using
|
|
29
|
+
|
|
30
|
+
当你希望在应用或库中复用一套不依赖具体页面结构、能够稳定表达“当前命中入口”和“入口历史”的基础能力时,可以使用 Route 模块。
|
|
31
|
+
|
|
32
|
+
从使用角度看,这个模块大致可以分为三类能力:
|
|
33
|
+
|
|
34
|
+
- 地址片段能力:把 `pathname`、`search`、`hash` 等结构收束成稳定表示,并支持统一的转换、包含与比较语义。
|
|
35
|
+
- 路由模型能力:把当前地址、标准化记录、历史链路以及导航动作组织为一套清楚的对象模型。
|
|
36
|
+
- 宿主桥接能力:把内部 Router 与浏览器历史或响应式消费面连接起来,使上层系统能够围绕统一的 Route 语义工作。
|
|
37
|
+
|
|
38
|
+
更合适的接入方式,通常是先判断你的问题是否真的属于 route 边界。如果你需要的是:
|
|
39
|
+
|
|
40
|
+
- 让地址片段形成稳定且可比较的统一表示;
|
|
41
|
+
- 把当前地址收束到一个可复用的路由对象和标准化记录中;
|
|
42
|
+
- 明确区分 navigate、redirect、replace、go、roaming 等历史动作的语义;
|
|
43
|
+
- 将内部 Router 同浏览器历史或响应式消费面连接起来;
|
|
44
|
+
|
|
45
|
+
那么这个模块就是合适的边界。
|
|
46
|
+
|
|
47
|
+
相反,如果你的需求主要是页面组件树装载、业务参数校验、访问权限决策、SEO 约定、框架页面生命周期、命令执行副作用或服务端领域逻辑分派,那么这些通常不应直接成为 Route 模块本身的职责,而应放在更外层系统或具体框架集成层中。
|
|
48
|
+
|
|
49
|
+
## For Contributing
|
|
50
|
+
|
|
51
|
+
为 Route 模块贡献内容时,优先判断新增能力是否真的在澄清“入口如何表达、如何标准化、如何形成历史、如何与宿主同步”这一稳定问题域,而不是只是在给某个当前宿主补一层便捷封装。这个模块应长期服务于入口片段、路由对象、历史语义与桥接边界这几个方向,而不是退化成浏览器导航工具目录或某个框架的外围辅助层。
|
|
52
|
+
|
|
53
|
+
扩展时应特别警惕两类偏差。一类是把 `uri` 层做成只对某组业务参数、某条服务路径或某套 CLI 选项成立的临时工具,导致公共语义不稳定;另一类是让 `adapter` 下沉过多桥接之外的职责,例如页面装载、业务状态解释、命令执行、副作用编排或框架专属生命周期。更稳妥的做法,是先确认新增能力究竟属于片段语义、路由历史语义,还是宿主桥接语义,再把它放到对应子边界中。
|
|
54
|
+
|
|
55
|
+
当前 `adapter` 下的能力更适合被理解为桥接层,因此不要求为其编写单元测试。这并不是说这些代码永远不可测试,而是因为它们主要依赖浏览器事件或响应式系统等宿主前提;在当前模块边界下,优先保证 `uri` 与 `router` 这些环境中立、语义稳定的核心部分被充分测试,通常比为桥接细节重复编写单元测试更有价值。如果未来某个适配器逐渐沉淀出脱离宿主也成立的稳定公共语义,再考虑为其引入单元测试。
|
|
56
|
+
|
|
57
|
+
### JSDoc 注释格式要求
|
|
58
|
+
|
|
59
|
+
- 每个公开导出的目标(类型、函数、变量、类等)都应包含 JSDoc 注释,让人在不跳转实现的情况下就能理解用途。
|
|
60
|
+
- JSDoc 注释第一行应为清晰且简洁的描述,该描述优先使用中文(英文也可以)。
|
|
61
|
+
- 如果描述后还有其他内容,应在描述后加一个空行。
|
|
62
|
+
- 如果有示例,应使用 `@example` 标签,后接三重反引号代码块(不带语言标识)。
|
|
63
|
+
- 如果有示例,应包含多个场景,展示不同用法,尤其要覆盖常见组合方式或边界输入。
|
|
64
|
+
- 如果有示例,应使用注释格式说明每个场景:`// Expect: <result>`。
|
|
65
|
+
- 如果有示例,应将结果赋值给 `example1`、`example2` 之类的变量,以保持示例易读。
|
|
66
|
+
- 如果有示例,`// Expect: <result>` 应该位于 `example1`、`example2` 之前,以保持示例的逻辑清晰。
|
|
67
|
+
- 如果有示例,应优先使用确定性示例;避免断言精确的随机输出。
|
|
68
|
+
- 如果函数返回结构化字符串,应展示其预期格式特征。
|
|
69
|
+
- 如果有参考资料,应将 `@see` 放在 `@example` 代码块之后,并用一个空行分隔。
|
|
70
|
+
|
|
71
|
+
### 实现规范要求
|
|
72
|
+
|
|
73
|
+
- 不同程序元素之间使用一个空行分隔,保持结构清楚。这里的程序元素,通常指函数、类型、常量,以及直接服务于它们的辅助元素。
|
|
74
|
+
- 某程序元素独占的辅助元素与该程序元素本身视为一个整体,不要在它们之间添加空行。
|
|
75
|
+
- 程序元素的辅助元素应该放置在该程序元素的上方,以保持阅读时的逻辑顺序。
|
|
76
|
+
- 若辅助元素被多个程序元素共享,则应将其视为独立的程序元素,放在这些程序元素中第一个相关目标的上方,并与后续程序元素之间保留一个空行。
|
|
77
|
+
- 辅助元素也应该像其它程序元素一样,保持清晰的命名和适当的注释,以便在需要阅读实现细节时能够快速理解它们的作用和使用方式。
|
|
78
|
+
- 辅助元素的命名必须以前缀 `internal` 开头(或 `Internal`,大小写不敏感)。
|
|
79
|
+
- 辅助元素永远不要公开导出。
|
|
80
|
+
- 被模块内多个不同文件中的程序元素共享的辅助元素,应该放在一个单独的文件中,例如 `./src/route/internal.ts`。
|
|
81
|
+
- 模块内可以包含子模块。只有当某个子目录表达一个稳定、可单独理解、且可能被父模块重导出的子问题域时,才应将其视为子模块。
|
|
82
|
+
- 子模块包含多个文件时,应该为其单独创建子文件夹,并为其创建单独的 Barrel 文件;父模块的 Barrel 文件再重导出子模块的 Barrel 文件。
|
|
83
|
+
- 子模块不需要有自己的 `README.md`。
|
|
84
|
+
- 子模块可以有自己的 `internal.ts` 文件,多个子模块共享的辅助元素应该放在父模块的 `internal.ts` 文件中,单个子模块共享的辅助元素应该放在该子模块的 `internal.ts` 文件中。
|
|
85
|
+
- 对模块依赖关系的要求(通常是不循环依赖或不反向依赖)与对 DRY 的要求可能产生冲突。此时,若复用的代码数量不大,可以适当牺牲 DRY,复制粘贴并保留必要的注释说明;若复用的代码数量较大,则可以将其抽象到新的文件或子模块中,如 `common.ts`,并在需要的地方导入使用。
|
|
86
|
+
- 与 route 相关的实现应优先围绕入口片段语义、路由对象、历史推进动作和宿主桥接边界组织,避免把页面装载、服务端业务处理、CLI 命令执行、业务专属参数解释或框架专属生命周期直接提升为 Route 的长期公共语义。
|
|
87
|
+
|
|
88
|
+
### 导出策略要求
|
|
89
|
+
|
|
90
|
+
- 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
|
|
91
|
+
- 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
|
|
92
|
+
- Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
|
|
93
|
+
- 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的路由语义,而不是某段浏览器实现细节、宿主桥接细节或业务约定的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
|
|
94
|
+
|
|
95
|
+
### 测试要求
|
|
96
|
+
|
|
97
|
+
- 若程序元素是函数,则只为该函数编写一个测试,如果该函数需要测试多个用例,应放在同一个测试中。
|
|
98
|
+
- 若程序元素是类,则至少要为该类的每一个方法编写一个测试,如果该方法需要测试多个用例,应放在同一个测试中。
|
|
99
|
+
- 若程序元素是类,除了为该类的每一个方法编写至少一个测试之外,还可以为该类编写任意多个测试,以覆盖该类的不同使用场景或边界情况。
|
|
100
|
+
- 若编写测试时需要用到辅助元素(Mock 或 Spy 等),可以在测试文件中直接定义这些辅助元素。若辅助元素较为简单,则可以直接放在每一个测试内部,优先保证每个测试的独立性,而不是追求极致 DRY;若辅助元素较为复杂或需要在多个测试中复用,则可以放在测试文件顶部,供该测试文件中的所有测试使用。
|
|
101
|
+
- 测试顺序应与源文件中被测试目标的原始顺序保持一致。
|
|
102
|
+
- 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
|
|
103
|
+
- 模块的单元测试文件目录是 `./tests/unit/route`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/route/<sub-module-name>`。
|
|
104
|
+
- `adapter` 目录当前无需编写单元测试,并应在模块文档中明确说明这是因为它主要承担宿主桥接职责,核心语义应优先由 `uri` 与 `router` 层测试覆盖。
|
|
105
|
+
- 对这个模块来说,测试应优先覆盖入口片段归一化、Route 标准化记录、Router 的历史推进与替换语义、回退与漫游边界,以及关键动作产生的历史记录与事件行为。
|