@planet-matrix/mobius-model 0.6.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/oxlint.config.ts +1 -2
  3. package/package.json +29 -17
  4. package/scripts/build.ts +2 -52
  5. package/src/ai/README.md +1 -0
  6. package/src/ai/ai.ts +107 -0
  7. package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
  8. package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
  9. package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
  10. package/src/ai/chat-completion-ai/index.ts +7 -0
  11. package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
  12. package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
  13. package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
  14. package/src/ai/embedding-ai/embedding-ai.ts +63 -0
  15. package/src/ai/embedding-ai/embedding.ts +50 -0
  16. package/src/ai/embedding-ai/index.ts +4 -0
  17. package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
  18. package/src/ai/index.ts +4 -0
  19. package/src/aio/README.md +100 -0
  20. package/src/aio/content.ts +141 -0
  21. package/src/aio/index.ts +3 -0
  22. package/src/aio/json.ts +127 -0
  23. package/src/aio/prompt.ts +246 -0
  24. package/src/basic/README.md +20 -15
  25. package/src/basic/error.ts +19 -5
  26. package/src/basic/function.ts +2 -2
  27. package/src/basic/index.ts +1 -0
  28. package/src/basic/promise.ts +141 -71
  29. package/src/basic/schedule.ts +111 -0
  30. package/src/basic/stream.ts +135 -25
  31. package/src/credential/README.md +107 -0
  32. package/src/credential/api-key.ts +158 -0
  33. package/src/credential/bearer.ts +73 -0
  34. package/src/credential/index.ts +4 -0
  35. package/src/credential/json-web-token.ts +96 -0
  36. package/src/credential/password.ts +170 -0
  37. package/src/cron/README.md +86 -0
  38. package/src/cron/cron.ts +87 -0
  39. package/src/cron/index.ts +1 -0
  40. package/src/drizzle/README.md +1 -0
  41. package/src/drizzle/drizzle.ts +1 -0
  42. package/src/drizzle/helper.ts +47 -0
  43. package/src/drizzle/index.ts +5 -0
  44. package/src/drizzle/infer.ts +52 -0
  45. package/src/drizzle/kysely.ts +8 -0
  46. package/src/drizzle/pagination.ts +198 -0
  47. package/src/email/README.md +1 -0
  48. package/src/email/index.ts +1 -0
  49. package/src/email/resend.ts +25 -0
  50. package/src/event/class-event-proxy.ts +5 -6
  51. package/src/event/common.ts +13 -3
  52. package/src/event/event-manager.ts +3 -3
  53. package/src/event/instance-event-proxy.ts +5 -6
  54. package/src/event/internal.ts +4 -4
  55. package/src/exception/README.md +28 -19
  56. package/src/exception/error/error.ts +123 -0
  57. package/src/exception/error/index.ts +2 -0
  58. package/src/exception/error/match.ts +38 -0
  59. package/src/exception/error/must-fix.ts +17 -0
  60. package/src/exception/index.ts +2 -0
  61. package/src/file-system/find.ts +53 -0
  62. package/src/file-system/index.ts +2 -0
  63. package/src/file-system/path.ts +76 -0
  64. package/src/file-system/resolve.ts +22 -0
  65. package/src/form/README.md +25 -0
  66. package/src/form/index.ts +1 -0
  67. package/src/form/inputor-controller/base.ts +861 -0
  68. package/src/form/inputor-controller/boolean.ts +39 -0
  69. package/src/form/inputor-controller/file.ts +39 -0
  70. package/src/form/inputor-controller/form.ts +179 -0
  71. package/src/form/inputor-controller/helper.ts +117 -0
  72. package/src/form/inputor-controller/index.ts +17 -0
  73. package/src/form/inputor-controller/multi-select.ts +99 -0
  74. package/src/form/inputor-controller/number.ts +116 -0
  75. package/src/form/inputor-controller/select.ts +109 -0
  76. package/src/form/inputor-controller/text.ts +82 -0
  77. package/src/http/READMD.md +1 -0
  78. package/src/http/api/api-core.ts +84 -0
  79. package/src/http/api/api-handler.ts +79 -0
  80. package/src/http/api/api-host.ts +47 -0
  81. package/src/http/api/api-result.ts +56 -0
  82. package/src/http/api/api-schema.ts +154 -0
  83. package/src/http/api/api-server.ts +130 -0
  84. package/src/http/api/api-test.ts +142 -0
  85. package/src/http/api/api-type.ts +34 -0
  86. package/src/http/api/api.ts +81 -0
  87. package/src/http/api/index.ts +11 -0
  88. package/src/http/api-adapter/api-core-node-http.ts +260 -0
  89. package/src/http/api-adapter/api-host-node-http.ts +156 -0
  90. package/src/http/api-adapter/api-result-arktype.ts +294 -0
  91. package/src/http/api-adapter/api-result-zod.ts +286 -0
  92. package/src/http/api-adapter/index.ts +5 -0
  93. package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
  94. package/src/http/bin/gen-api-list/index.ts +1 -0
  95. package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
  96. package/src/http/bin/gen-api-test/index.ts +1 -0
  97. package/src/http/bin/gen-api-type/calc-code.ts +25 -0
  98. package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
  99. package/src/http/bin/gen-api-type/index.ts +2 -0
  100. package/src/http/bin/index.ts +2 -0
  101. package/src/http/index.ts +3 -0
  102. package/src/huawei/README.md +1 -0
  103. package/src/huawei/index.ts +2 -0
  104. package/src/huawei/moderation/index.ts +1 -0
  105. package/src/huawei/moderation/moderation.ts +355 -0
  106. package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
  107. package/src/huawei/obs/index.ts +1 -0
  108. package/src/huawei/obs/obs.ts +42 -0
  109. package/src/index.ts +21 -2
  110. package/src/json/README.md +92 -0
  111. package/src/json/index.ts +1 -0
  112. package/src/json/repair.ts +18 -0
  113. package/src/log/logger.ts +15 -4
  114. package/src/openai/README.md +1 -0
  115. package/src/openai/index.ts +1 -0
  116. package/src/openai/openai.ts +509 -0
  117. package/src/orchestration/README.md +9 -7
  118. package/src/orchestration/dispatching/dispatcher.ts +83 -0
  119. package/src/orchestration/dispatching/index.ts +2 -0
  120. package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
  121. package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
  122. package/src/orchestration/dispatching/selector/index.ts +2 -0
  123. package/src/orchestration/index.ts +2 -0
  124. package/src/orchestration/scheduling/index.ts +2 -0
  125. package/src/orchestration/scheduling/scheduler.ts +103 -0
  126. package/src/orchestration/scheduling/task.ts +32 -0
  127. package/src/random/README.md +8 -7
  128. package/src/random/base.ts +66 -0
  129. package/src/random/index.ts +5 -1
  130. package/src/random/random-boolean.ts +40 -0
  131. package/src/random/random-integer.ts +60 -0
  132. package/src/random/random-number.ts +72 -0
  133. package/src/random/random-string.ts +66 -0
  134. package/src/request/README.md +108 -0
  135. package/src/request/fetch/base.ts +108 -0
  136. package/src/request/fetch/browser.ts +280 -0
  137. package/src/request/fetch/general.ts +20 -0
  138. package/src/request/fetch/index.ts +4 -0
  139. package/src/request/fetch/nodejs.ts +280 -0
  140. package/src/request/index.ts +2 -0
  141. package/src/request/request/base.ts +246 -0
  142. package/src/request/request/general.ts +63 -0
  143. package/src/request/request/index.ts +3 -0
  144. package/src/request/request/resource.ts +68 -0
  145. package/src/result/README.md +4 -0
  146. package/src/result/controller.ts +58 -0
  147. package/src/result/either.ts +363 -0
  148. package/src/result/generator.ts +168 -0
  149. package/src/result/index.ts +3 -0
  150. package/src/route/README.md +105 -0
  151. package/src/route/adapter/browser.ts +122 -0
  152. package/src/route/adapter/driver.ts +56 -0
  153. package/src/route/adapter/index.ts +2 -0
  154. package/src/route/index.ts +3 -0
  155. package/src/route/router/index.ts +2 -0
  156. package/src/route/router/route.ts +630 -0
  157. package/src/route/router/router.ts +1641 -0
  158. package/src/route/uri/hash.ts +307 -0
  159. package/src/route/uri/index.ts +7 -0
  160. package/src/route/uri/pathname.ts +376 -0
  161. package/src/route/uri/search.ts +412 -0
  162. package/src/service/README.md +1 -0
  163. package/src/service/index.ts +1 -0
  164. package/src/service/service.ts +110 -0
  165. package/src/socket/README.md +105 -0
  166. package/src/socket/client/index.ts +2 -0
  167. package/src/socket/client/socket-unit.ts +658 -0
  168. package/src/socket/client/socket.ts +203 -0
  169. package/src/socket/common/index.ts +2 -0
  170. package/src/socket/common/socket-unit-common.ts +23 -0
  171. package/src/socket/common/socket-unit-heartbeat.ts +427 -0
  172. package/src/socket/index.ts +3 -0
  173. package/src/socket/server/index.ts +3 -0
  174. package/src/socket/server/server.ts +183 -0
  175. package/src/socket/server/socket-unit.ts +448 -0
  176. package/src/socket/server/socket.ts +264 -0
  177. package/src/storage/table.ts +3 -3
  178. package/src/timer/expiration/expiration-manager.ts +3 -3
  179. package/src/timer/expiration/remaining-manager.ts +3 -3
  180. package/src/tube/README.md +99 -0
  181. package/src/tube/helper.ts +137 -0
  182. package/src/tube/index.ts +2 -0
  183. package/src/tube/tube.ts +880 -0
  184. package/src/weixin/README.md +1 -0
  185. package/src/weixin/index.ts +2 -0
  186. package/src/weixin/official-account/authorization.ts +157 -0
  187. package/src/weixin/official-account/index.ts +2 -0
  188. package/src/weixin/official-account/js-api.ts +132 -0
  189. package/src/weixin/open/index.ts +1 -0
  190. package/src/weixin/open/oauth2.ts +131 -0
  191. package/tests/unit/ai/ai.spec.ts +85 -0
  192. package/tests/unit/aio/content.spec.ts +105 -0
  193. package/tests/unit/aio/json.spec.ts +146 -0
  194. package/tests/unit/aio/prompt.spec.ts +111 -0
  195. package/tests/unit/basic/error.spec.ts +16 -4
  196. package/tests/unit/basic/promise.spec.ts +158 -50
  197. package/tests/unit/basic/schedule.spec.ts +74 -0
  198. package/tests/unit/basic/stream.spec.ts +90 -37
  199. package/tests/unit/credential/api-key.spec.ts +36 -0
  200. package/tests/unit/credential/bearer.spec.ts +23 -0
  201. package/tests/unit/credential/json-web-token.spec.ts +23 -0
  202. package/tests/unit/credential/password.spec.ts +40 -0
  203. package/tests/unit/cron/cron.spec.ts +84 -0
  204. package/tests/unit/event/class-event-proxy.spec.ts +3 -3
  205. package/tests/unit/event/event-manager.spec.ts +3 -3
  206. package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
  207. package/tests/unit/exception/error/error.spec.ts +83 -0
  208. package/tests/unit/exception/error/match.spec.ts +81 -0
  209. package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
  210. package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
  211. package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
  212. package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
  213. package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
  214. package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
  215. package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
  216. package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
  217. package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
  218. package/tests/unit/http/api/api-core-host.spec.ts +207 -0
  219. package/tests/unit/http/api/api-schema.spec.ts +120 -0
  220. package/tests/unit/http/api/api-server.spec.ts +363 -0
  221. package/tests/unit/http/api/api-test.spec.ts +117 -0
  222. package/tests/unit/http/api/api.spec.ts +121 -0
  223. package/tests/unit/http/api-adapter/node-http.spec.ts +187 -0
  224. package/tests/unit/identifier/uuid.spec.ts +0 -1
  225. package/tests/unit/json/repair.spec.ts +11 -0
  226. package/tests/unit/log/logger.spec.ts +19 -4
  227. package/tests/unit/openai/openai.spec.ts +64 -0
  228. package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
  229. package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
  230. package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
  231. package/tests/unit/random/base.spec.ts +58 -0
  232. package/tests/unit/random/random-boolean.spec.ts +25 -0
  233. package/tests/unit/random/random-integer.spec.ts +32 -0
  234. package/tests/unit/random/random-number.spec.ts +33 -0
  235. package/tests/unit/random/random-string.spec.ts +22 -0
  236. package/tests/unit/request/fetch/browser.spec.ts +222 -0
  237. package/tests/unit/request/fetch/general.spec.ts +43 -0
  238. package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
  239. package/tests/unit/request/request/base.spec.ts +382 -0
  240. package/tests/unit/request/request/general.spec.ts +160 -0
  241. package/tests/unit/result/controller.spec.ts +82 -0
  242. package/tests/unit/result/either.spec.ts +377 -0
  243. package/tests/unit/result/generator.spec.ts +273 -0
  244. package/tests/unit/route/router/route.spec.ts +430 -0
  245. package/tests/unit/route/router/router.spec.ts +407 -0
  246. package/tests/unit/route/uri/hash.spec.ts +72 -0
  247. package/tests/unit/route/uri/pathname.spec.ts +146 -0
  248. package/tests/unit/route/uri/search.spec.ts +107 -0
  249. package/tests/unit/socket/client.spec.ts +208 -0
  250. package/tests/unit/socket/server.spec.ts +133 -0
  251. package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
  252. package/tests/unit/tube/helper.spec.ts +139 -0
  253. package/tests/unit/tube/tube.spec.ts +501 -0
  254. package/vite.config.ts +2 -1
  255. package/dist/index.js +0 -50
  256. package/dist/index.js.map +0 -209
  257. package/src/random/string.ts +0 -35
  258. package/tests/unit/random/string.spec.ts +0 -11
@@ -0,0 +1,198 @@
1
+ import { d } from "./drizzle.ts"
2
+ import type { AnyPgTableWithColumns, InferModelFromPgTableWithColumns } from "./infer.ts"
3
+
4
+ export type Cursor = string | null
5
+ export interface WithCursorBasedPaginationOptions {
6
+ limit: number
7
+ cursor: Cursor
8
+ }
9
+ export interface WithCursorBasedPaginationResult {
10
+ nextCursor: Cursor
11
+ totalCount: number
12
+ }
13
+
14
+ interface OrderByItem<Key> {
15
+ key: Key
16
+ order: "asc" | "desc"
17
+ }
18
+ export interface EncodeCursorOptions<Model> {
19
+ model: Model
20
+ orderBy: Array<OrderByItem<keyof Model>>
21
+ }
22
+ export type EncodeCursorResult = string
23
+ export const encodeCursor = <Model>(
24
+ options: EncodeCursorOptions<Model>,
25
+ ): EncodeCursorResult => {
26
+ const { model, orderBy } = options
27
+ const encoded = orderBy
28
+ .map((item) => {
29
+ const { key } = item
30
+ if (model[key] === undefined) {
31
+ throw new Error(`model[${String(key)}] is undefined`)
32
+ }
33
+ return model[key]
34
+ })
35
+ .map((value) => {
36
+ if (value instanceof Date) {
37
+ return `Date(${value.getTime()})`
38
+ }
39
+ return String(value)
40
+ })
41
+ .join(":")
42
+ return btoa(encoded)
43
+ }
44
+
45
+ export interface DecodeCursorOptions<Model, Key extends keyof Model = keyof Model> {
46
+ modelForType: Model
47
+ cursor: Cursor
48
+ orderBy: Array<OrderByItem<Key>>
49
+ }
50
+ export type DecodedCursor<Model, Key extends keyof Model = keyof Model> = {
51
+ [K in Key]: Model[K] extends Date ? Date : Model[K]
52
+ }
53
+ export type DecodeCursorResult<Model, Key extends keyof Model = keyof Model> = DecodedCursor<Model, Key> | null
54
+ export const decodeCursor = <Model, Key extends keyof Model = keyof Model>(
55
+ options: DecodeCursorOptions<Model, Key>,
56
+ ): DecodeCursorResult<Model, Key> => {
57
+ const { cursor, orderBy } = options
58
+ if (cursor === null) {
59
+ return null
60
+ }
61
+ const decoded = atob(cursor)
62
+ .split(":")
63
+ .map((value) => {
64
+ if (value.startsWith("Date(")) {
65
+ return new Date(Number.parseInt(value.slice(5, -1), 10))
66
+ }
67
+ return value
68
+ })
69
+ .reduce((acc, value, index) => {
70
+ const key = orderBy[index]!.key
71
+ return { ...acc, [key]: value }
72
+ }, {})
73
+ return decoded as DecodedCursor<Model, Key>
74
+ }
75
+
76
+ export interface GetNextCursorOptions<Model> {
77
+ list: Model[]
78
+ limit: number
79
+ encodeOptions: Omit<EncodeCursorOptions<Model>, "model">
80
+ }
81
+ export const getNextCursor = <Model>(options: GetNextCursorOptions<Model>): Cursor => {
82
+ const { list, limit, encodeOptions } = options
83
+ if (list.length === 0) {
84
+ return null
85
+ }
86
+ if (list.length < limit) {
87
+ return null
88
+ }
89
+ const lastModel = list.at(-1)!
90
+ const cursor = encodeCursor({
91
+ model: lastModel,
92
+ ...encodeOptions,
93
+ })
94
+ return cursor
95
+ }
96
+
97
+ export interface BuildWhereSqlOptions<Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>> {
98
+ table: Table
99
+ orderBy: Array<OrderByItem<Key>>
100
+ decodeCursorResult: DecodeCursorResult<InferModelFromPgTableWithColumns<Table>, Key>
101
+ }
102
+ export const buildWhereSql = <Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>>(
103
+ options: BuildWhereSqlOptions<Table, Key>,
104
+ ): d.SQL | undefined => {
105
+ const { table, decodeCursorResult, orderBy } = options
106
+ if (decodeCursorResult === null) {
107
+ return undefined
108
+ }
109
+ if (orderBy.length === 0) {
110
+ return undefined
111
+ }
112
+ const conditionSqlList = orderBy.map((item) => {
113
+ const { key, order } = item
114
+ if (order === "asc") {
115
+ return d.gt(table[key], decodeCursorResult[key])
116
+ }
117
+ if (order === "desc") {
118
+ return d.lt(table[key], decodeCursorResult[key])
119
+ }
120
+ throw new Error(`Invalid order: ${String(order)}`)
121
+ })
122
+ return d.and(...conditionSqlList)
123
+ }
124
+
125
+ export interface BuildOrderBySql<Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>> {
126
+ table: Table
127
+ orderBy: Array<OrderByItem<Key>>
128
+ }
129
+ export const buildOrderBySql = <Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>>(
130
+ options: BuildOrderBySql<Table, Key>,
131
+ ): d.SQL[] => {
132
+ const { table, orderBy } = options
133
+ const sqlList = orderBy.map((item) => {
134
+ const { key, order } = item
135
+ if (order === "asc") {
136
+ return d.asc(table[key])
137
+ }
138
+ if (order === "desc") {
139
+ return d.desc(table[key])
140
+ }
141
+ throw new Error(`Invalid order: ${String(order)}`)
142
+ })
143
+ return sqlList
144
+ }
145
+
146
+ export interface BuildCursorBasedPaginationOptions<Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>> {
147
+ table: Table
148
+ orderBy: Array<OrderByItem<Key>>
149
+ cursor: Cursor
150
+ limit: number
151
+ }
152
+ export interface BuildCursorBasedPaginationResult<Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>> {
153
+ decodeCursorResult: DecodeCursorResult<InferModelFromPgTableWithColumns<Table>, Key>
154
+ whereSql: d.SQL | undefined
155
+ orderBySql: d.SQL[]
156
+ getNextCursor: (list: Array<InferModelFromPgTableWithColumns<Table>>) => Cursor
157
+ }
158
+ export const buildCursorBasedPagination = <Table extends AnyPgTableWithColumns, Key extends keyof InferModelFromPgTableWithColumns<Table>>(
159
+ options: BuildCursorBasedPaginationOptions<Table, Key>,
160
+ ): BuildCursorBasedPaginationResult<Table, Key> => {
161
+ const { table, orderBy, limit, cursor } = options
162
+
163
+ type Model = InferModelFromPgTableWithColumns<Table>
164
+
165
+ const decodeCursorResult = decodeCursor({
166
+ modelForType: {} as Model,
167
+ cursor,
168
+ orderBy,
169
+ })
170
+
171
+ const whereSql = buildWhereSql({
172
+ table,
173
+ decodeCursorResult,
174
+ orderBy,
175
+ })
176
+
177
+ const orderBySql = buildOrderBySql({
178
+ table,
179
+ orderBy,
180
+ })
181
+
182
+ const _getNextCursor = (list: Model[]): Cursor => {
183
+ return getNextCursor({
184
+ list,
185
+ limit,
186
+ encodeOptions: {
187
+ orderBy,
188
+ },
189
+ })
190
+ }
191
+
192
+ return {
193
+ decodeCursorResult,
194
+ whereSql,
195
+ orderBySql,
196
+ getNextCursor: _getNextCursor,
197
+ }
198
+ }
@@ -0,0 +1 @@
1
+ # Email
@@ -0,0 +1 @@
1
+ export * from "./resend.ts"
@@ -0,0 +1,25 @@
1
+ import { Resend } from "resend"
2
+
3
+ export interface SendEmailOptions {
4
+ from: string
5
+ to: string
6
+ subject: string
7
+ html: string
8
+ }
9
+ export type SendEmailResult = ReturnType<typeof Resend.prototype.emails.send>
10
+
11
+ export interface EmailOptions {
12
+ key: string
13
+ }
14
+ export class Email {
15
+ private sendClient: Resend
16
+
17
+ constructor(options: EmailOptions) {
18
+ const { key } = options
19
+ this.sendClient = new Resend(key)
20
+ }
21
+
22
+ async sendEmail(options: SendEmailOptions): Promise<SendEmailResult> {
23
+ return await this.sendClient.emails.send({ ...options })
24
+ }
25
+ }
@@ -1,13 +1,13 @@
1
1
  import type { InternalProxySubscriberEntryMap } from "./internal.ts"
2
2
  import type {
3
- BaseEvents,
3
+ EventsConstraint,
4
4
  SubscriberEntry,
5
5
  } from "./common.ts"
6
6
 
7
7
  /**
8
8
  * 表示 ClassEventProxy 用于接入目标实例及其事件系统的适配器。
9
9
  */
10
- export interface ClassEventProxyTargetAdapter<Target, Events extends BaseEvents> {
10
+ export interface ClassEventProxyTargetAdapter<Target, Events extends EventsConstraint<Events>> {
11
11
  emit<K extends keyof Events>(target: Target, event: K, ...args: Parameters<Events[K]>): boolean
12
12
  subscribe<K extends keyof Events>(target: Target, event: K, subscriber: Events[K]): void
13
13
  unsubscribe<K extends keyof Events>(target: Target, event: K, subscriber: Events[K]): void
@@ -16,7 +16,7 @@ export interface ClassEventProxyTargetAdapter<Target, Events extends BaseEvents>
16
16
  /**
17
17
  * 表示 ClassEventProxy 的构造选项。
18
18
  */
19
- export interface ClassEventProxyOptions<Target, Events extends BaseEvents> {
19
+ export interface ClassEventProxyOptions<Target, Events extends EventsConstraint<Events>> {
20
20
  targetAdapter: ClassEventProxyTargetAdapter<Target, Events>
21
21
  /**
22
22
  * 在代理管理的订阅者执行出错时接收错误与对应订阅项。
@@ -27,7 +27,7 @@ export interface ClassEventProxyOptions<Target, Events extends BaseEvents> {
27
27
  /**
28
28
  * 将一组目标实例的事件系统适配为统一的代理订阅模型。
29
29
  */
30
- export class ClassEventProxy<Target, Events extends BaseEvents> {
30
+ export class ClassEventProxy<Target, Events extends EventsConstraint<Events>> {
31
31
  protected options: ClassEventProxyOptions<Target, Events>
32
32
  protected subscribers: Map<Target, InternalProxySubscriberEntryMap<Events>>
33
33
 
@@ -66,7 +66,6 @@ export class ClassEventProxy<Target, Events extends BaseEvents> {
66
66
 
67
67
  if (proxySubscriberEntry === undefined) {
68
68
  const managedSubscribers = new Map<Events[K], SubscriberEntry<Events, K>>()
69
- // oxlint-disable-next-line no-unsafe-type-assertion
70
69
  const proxySubscriber = ((...args: Parameters<Events[K]>) => {
71
70
  const managedSubscriberEntries = [...managedSubscribers.values()]
72
71
  for (const managedSubscriberEntry of managedSubscriberEntries) {
@@ -196,7 +195,7 @@ export class ClassEventProxy<Target, Events extends BaseEvents> {
196
195
 
197
196
  const events = Object.keys(proxySubscriberEntryMap)
198
197
  for (const event of events) {
199
- this.removeSubscribersOfEvent(target, event)
198
+ this.removeSubscribersOfEvent(target, event as keyof Events)
200
199
  }
201
200
  this.subscribers.delete(target)
202
201
 
@@ -4,14 +4,24 @@
4
4
  // oxlint-disable-next-line no-explicit-any
5
5
  export type BaseSubscriber = (...args: any[]) => void
6
6
  /**
7
- * 表示由事件名到订阅者函数类型构成的事件表。
7
+ * 表示事件表中允许的事件-订阅者关系的约束类型。
8
+ * 该类型要求事件表的每个事件都必须对应一个 BaseSubscriber 类型的订阅者函数。
9
+ * 通过该约束类型,EventManager 和相关组件能够在编译时确保事件表的正确性。
8
10
  */
9
- export type BaseEvents = Record<string, BaseSubscriber>
11
+ export type EventsConstraint<Events extends EventsConstraint<Events>> = {
12
+ [K in keyof Events]: BaseSubscriber
13
+ }
14
+ export type BuildEvents<Events extends EventsConstraint<Events>> = {
15
+ [K in keyof Events]: Events[K]
16
+ }
17
+ export interface AnyEvents {
18
+ [event: string]: BaseSubscriber
19
+ }
10
20
 
11
21
  /**
12
22
  * 表示一次订阅关系的标准化描述。
13
23
  */
14
- export interface SubscriberEntry<Events extends BaseEvents, K extends keyof Events> {
24
+ export interface SubscriberEntry<Events extends EventsConstraint<Events>, K extends keyof Events> {
15
25
  event: K
16
26
  once: boolean
17
27
  subscriber: Events[K]
@@ -1,10 +1,10 @@
1
1
  import type { InternalSubscriberEntryMap } from "./internal.ts"
2
- import type { BaseEvents, SubscriberEntry } from "./common.ts"
2
+ import type { EventsConstraint, SubscriberEntry } from "./common.ts"
3
3
 
4
4
  /**
5
5
  * 表示 EventManager 的构造选项。
6
6
  */
7
- export interface EventManagerOptions<Events extends BaseEvents> {
7
+ export interface EventManagerOptions<Events extends EventsConstraint<Events>> {
8
8
  /**
9
9
  * 在订阅者执行出错时接收错误与对应订阅项。
10
10
  */
@@ -14,7 +14,7 @@ export interface EventManagerOptions<Events extends BaseEvents> {
14
14
  /**
15
15
  * 管理同一事件表下的订阅、取消订阅与事件派发。
16
16
  */
17
- export class EventManager<Events extends BaseEvents> {
17
+ export class EventManager<Events extends EventsConstraint<Events>> {
18
18
  protected options: EventManagerOptions<Events>
19
19
  /**
20
20
  * 每一类事件对应一个内部订阅映射,映射中的 key 是订阅者函数,value 是 SubscriberEntry 对象。
@@ -1,13 +1,13 @@
1
1
  import type { InternalProxySubscriberEntryMap } from "./internal.ts"
2
2
  import type {
3
- BaseEvents,
3
+ EventsConstraint,
4
4
  SubscriberEntry,
5
5
  } from "./common.ts"
6
6
 
7
7
  /**
8
8
  * 表示 InstanceEventProxy 用于接入底层事件目标的适配器。
9
9
  */
10
- export interface InstanceEventProxyTargetAdapter<Events extends BaseEvents> {
10
+ export interface InstanceEventProxyTargetAdapter<Events extends EventsConstraint<Events>> {
11
11
  emit<K extends keyof Events>(event: K, ...args: Parameters<Events[K]>): boolean
12
12
  subscribe<K extends keyof Events>(event: K, subscriber: Events[K]): void
13
13
  unsubscribe<K extends keyof Events>(event: K, subscriber: Events[K]): void
@@ -16,7 +16,7 @@ export interface InstanceEventProxyTargetAdapter<Events extends BaseEvents> {
16
16
  /**
17
17
  * 表示 InstanceEventProxy 的构造选项。
18
18
  */
19
- export interface InstanceEventProxyOptions<Events extends BaseEvents> {
19
+ export interface InstanceEventProxyOptions<Events extends EventsConstraint<Events>> {
20
20
  targetAdapter: InstanceEventProxyTargetAdapter<Events>
21
21
  /**
22
22
  * 在代理管理的订阅者执行出错时接收错误与对应订阅项。
@@ -27,7 +27,7 @@ export interface InstanceEventProxyOptions<Events extends BaseEvents> {
27
27
  /**
28
28
  * 将单个事件目标适配为可管理下游订阅关系的代理。
29
29
  */
30
- export class InstanceEventProxy<Events extends BaseEvents> {
30
+ export class InstanceEventProxy<Events extends EventsConstraint<Events>> {
31
31
  protected options: InstanceEventProxyOptions<Events>
32
32
  protected subscribers: InternalProxySubscriberEntryMap<Events>
33
33
 
@@ -58,7 +58,6 @@ export class InstanceEventProxy<Events extends BaseEvents> {
58
58
 
59
59
  if (proxySubscriberEntry === undefined) {
60
60
  const managedSubscribers = new Map<Events[K], SubscriberEntry<Events, K>>()
61
- // oxlint-disable-next-line no-unsafe-type-assertion
62
61
  const proxySubscriber = ((...args: Parameters<Events[K]>) => {
63
62
  const managedSubscriberEntries = [...managedSubscribers.values()]
64
63
  for (const managedSubscriberEntry of managedSubscriberEntries) {
@@ -168,7 +167,7 @@ export class InstanceEventProxy<Events extends BaseEvents> {
168
167
  const events = Object.keys(this.subscribers)
169
168
 
170
169
  for (const event of events) {
171
- this.removeSubscribersOfEvent(event)
170
+ this.removeSubscribersOfEvent(event as keyof Events)
172
171
  }
173
172
  this.subscribers = {}
174
173
 
@@ -1,9 +1,9 @@
1
- import type { BaseEvents, SubscriberEntry } from "./common.ts"
1
+ import type { EventsConstraint, SubscriberEntry } from "./common.ts"
2
2
 
3
3
  /**
4
4
  * 表示某一事件名下由订阅者函数到订阅项的内部映射。
5
5
  */
6
- export type InternalSubscriberEntryMap<Events extends BaseEvents, K extends keyof Events> = Map<
6
+ export type InternalSubscriberEntryMap<Events extends EventsConstraint<Events>, K extends keyof Events> = Map<
7
7
  Events[K],
8
8
  SubscriberEntry<Events, K>
9
9
  >
@@ -11,7 +11,7 @@ export type InternalSubscriberEntryMap<Events extends BaseEvents, K extends keyo
11
11
  /**
12
12
  * 表示代理订阅项及其所管理的下游订阅集合的内部结构。
13
13
  */
14
- export interface InternalProxySubscriberEntry<Events extends BaseEvents, K extends keyof Events> {
14
+ export interface InternalProxySubscriberEntry<Events extends EventsConstraint<Events>, K extends keyof Events> {
15
15
  managedSubscribers: InternalSubscriberEntryMap<Events, K>
16
16
  proxySubscriber: Events[K]
17
17
  }
@@ -19,6 +19,6 @@ export interface InternalProxySubscriberEntry<Events extends BaseEvents, K exten
19
19
  /**
20
20
  * 使用对象而非 Map 来保存事件到代理订阅项的内部映射。
21
21
  */
22
- export type InternalProxySubscriberEntryMap<Events extends BaseEvents> = {
22
+ export type InternalProxySubscriberEntryMap<Events extends EventsConstraint<Events>> = {
23
23
  [K in keyof Events]?: InternalProxySubscriberEntry<Events, K> | undefined
24
24
  }
@@ -2,49 +2,57 @@
2
2
 
3
3
  ## Description
4
4
 
5
- Exception 模块提供围绕宿主环境全局异常入口(global exception entry)的监听、标准化与组合能力,用于把浏览器和 Node.js 暴露出来的异常来源整理成稳定、可清理、可组合的公共异常模型。
5
+ Exception 模块提供围绕异常(exception)的基础模型能力,用于承载宿主环境全局异常入口的监听与标准化,以及其中一类可判别、可匹配、可序列化的结构化错误建模。
6
6
 
7
- 它关注的是“异常从哪里被捕获、如何被整理成稳定记录”,而不是“异常之后如何打印、上报、恢复、吞掉或终止进程”。
7
+ 它关注的是“异常从哪里进入系统、错误如何被稳定表达”,而不是“异常之后如何打印、上报、恢复、吞掉或终止进程”。
8
8
 
9
9
  ## For Understanding
10
10
 
11
- 理解 Exception 模块时,应把它视为“异常来源层”,而不是“异常处理策略层”。这个模块的职责边界非常明确:
11
+ 理解 Exception 模块时,更合适的方式是把它视为“异常模型层”,而不是“异常处理策略层”。这个模块当前关注的是异常这一上位概念,其中既包括异常如何从宿主环境进入系统,也包括其中一类适合被稳定建模的异常形式,也就是结构化错误。
12
12
 
13
- - 它负责封装宿主提供的全局异常入口。
14
- - 它负责把宿主原始事件整理成更稳定的异常记录结构。
15
- - 它负责提供可清理的监听语义与批量组合入口。
16
- - 它不负责日志发送、监控上报、默认阻止、错误恢复或退出进程等策略决策。
13
+ - 宿主异常入口:负责封装浏览器和 Node.js 暴露出来的全局异常入口,并把原始输入整理成稳定记录。
14
+ - 结构化错误:负责把应用内部需要长期存在的某些异常表达成带判别信息的 Error 变体,以便进行分派、组合和序列化。
17
15
 
18
- 当前模块主要覆盖两类宿主:
16
+ 之所以可以把它们放在同一模块内,不是因为它们的实现方式相似,而是因为它们都在回答同一类问题:系统中的异常应该以什么稳定模型进入后续流程。前者解决“异常从哪里来”,后者解决“某些异常在系统中应如何被表达与判别”。
17
+
18
+ 因此,这个模块并不承担日志发送、监控上报、错误恢复、默认阻止、重试编排或进程退出等策略职责。它只负责把异常整理成更稳定、更清楚、更可组合的输入模型,让上层的 logger、telemetry、workflow、Result/Either 或业务恢复逻辑在其之上继续工作。
19
+
20
+ 对于宿主异常入口部分,当前主要覆盖两类运行时:
19
21
 
20
22
  - 浏览器侧异常入口,例如 `error` 事件、`window.onerror` 与 `unhandledrejection`。
21
23
  - Node.js 侧异常入口,例如 `uncaughtExceptionMonitor`、`uncaughtException` 与 `unhandledRejection`。
22
24
 
23
- 在建模上,这个模块采用“保留来源差异,同时提供标准化记录”的方式,而不是过早追求绝对统一。也就是说,浏览器与 Node.js 的来源种类、原始参数和补充字段可以不同,但它们都可以被整理成带有 runtimesource、exception、message、timestamp 等稳定字段的公共记录。这种分层有助于同时满足宿主特定处理与跨宿主消费。
25
+ 对于异常建模中的结构化错误部分,当前采用 Tagged Error 的思路:这类错误既保留原生 Error 的 name、message、stack 与 `cause` 语义,也额外拥有稳定的 `tag` 与 `data` 字段,便于在联合类型中进行穷举或部分匹配。
24
26
 
25
27
  ## For Using
26
28
 
27
- 当应用需要在入口层、基础设施层、日志层或诊断层统一接入宿主全局异常时,可以使用这个模块,把分散在不同宿主 API 上的异常来源整理成更清楚的订阅接口。
29
+ 当应用需要从宿主环境统一接入全局异常,并把这些异常整理成稳定记录时,可以使用这个模块的异常入口能力,把分散在不同运行时 API 上的异常来源收敛成更清楚的订阅接口。
30
+
31
+ 当应用需要在模块边界、基础设施层、工作流编排、Result/Either 风格流程或跨层协议中表达一组稳定、可判别、可序列化的异常时,也可以使用这个模块中的结构化错误能力,把其中适合显式建模的一类异常从“临时字符串或松散对象”提升为更清楚的公共模型。
28
32
 
29
33
  当前公共能力大致可以按以下几类理解:
30
34
 
31
- - 单一入口监听:用于单独监听浏览器或 Node.js 的某一种异常来源。
32
- - 批量入口监听:用于按宿主一次性安装一组常见异常入口,并返回统一清理函数。
35
+ - 单一入口监听:用于单独监听浏览器或 Node.js 的某一种全局异常来源。
36
+ - 批量入口监听:用于按运行时一次性安装一组常见异常入口,并返回统一清理函数。
33
37
  - 异常记录标准化:用于把浏览器或 Node.js 的原始输入整理成稳定的异常记录结构。
34
- - 类型与清理语义:用于表达异常来源种类、记录形状以及取消监听的统一返回类型。
38
+ - Tagged Error 建模:用于定义一类带 `tag`、`data`、`cause` 与序列化能力的结构化异常类型。
39
+ - Tagged Error 匹配:用于按 tag 分派这一类结构化异常的处理逻辑,并在类型层保留分支信息。
40
+ - 类型与清理语义:用于表达异常来源种类、异常记录形状、结构化异常实例能力以及取消监听的统一返回类型。
35
41
 
36
- 使用时应把它接在 logger、遥测、监控、恢复策略或业务错误处理的上游,而不是期待它直接替代这些能力。它更适合作为异常输入层,而不是最终处理层。
42
+ 使用时应把它接在 logger、遥测、监控、恢复策略或业务错误处理的上游,而不是期待它直接替代这些能力。更准确地说,它适合作为异常输入与异常表达的基础层,而不是最终处理层。
37
43
 
38
44
  ## For Contributing
39
45
 
40
- 贡献 Exception 模块时,应优先判断新增能力是否表达了稳定、可复用的宿主级异常来源,还是只是某个框架、某个应用内部错误流的便捷包装。只有前者才适合进入这个模块。
46
+ 贡献 Exception 模块时,应优先判断新增能力是否真正扩展了“异常模型”这个问题域,而不是只是在当前实现里顺手添加一个便捷函数。只有当某项能力表达了稳定、可复用、值得长期维护的异常来源语义,或表达了某类异常的稳定建模语义时,它才适合进入这个模块。
41
47
 
42
48
  在扩展时,应优先遵守以下边界:
43
49
 
44
- - 公共能力应围绕宿主异常入口、标准化记录与监听清理语义展开。
45
- - 不要在监听逻辑中默认执行日志输出、上报请求、`preventDefault`、`process.exit` 或其它强策略动作,除非 API 名称与文档已经明确承诺。
50
+ - 公共能力应围绕宿主异常入口、异常记录标准化,以及其中可被稳定表达的结构化异常建模与匹配语义展开。
51
+ - 若新增的是模块内稳定异常模型,且它适合以 Error 形式存在,应优先使用 Tagged Error 形式表达,而不是仅靠裸字符串或无判别字段的对象。
52
+ - 不要在监听逻辑或异常模型中默认执行日志输出、上报请求、`preventDefault`、`process.exit`、重试、恢复或其它强策略动作,除非 API 名称与文档已经明确承诺。
46
53
  - 组合入口只负责批量安装监听器与汇总清理,不负责掩盖宿主差异或替调用方做策略决策。
47
- - Environment 的关系应保持清楚:Environment 可以帮助判断和获取宿主上下文,Exception 则负责安装异常监听器。
54
+ - Tagged Error 的职责是提供一类稳定异常表达、判别和匹配语义,而不是承载某个单一应用的临时上下文容器。
55
+ - 与 Environment 的关系应保持清楚:Environment 可以帮助判断和获取宿主上下文,Exception 则负责异常入口与异常模型本身。
48
56
 
49
57
  ### JSDoc 注释格式要求
50
58
 
@@ -82,7 +90,7 @@ Exception 模块提供围绕宿主环境全局异常入口(global exception en
82
90
  - 保持内部辅助项和内部符号为私有,不要让外部接入依赖临时性的内部结构。
83
91
  - 每个模块都应有一个用于重导出所有公共 API 的 Barrel 文件。
84
92
  - Barrel 文件应命名为 `index.ts`,放在模块目录根部,并且所有公共 API 都应从该文件导出。
85
- - 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的异常入口语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
93
+ - 新增公共能力时,应优先检查它是否表达稳定、清楚且值得长期维护的异常模型语义,而不是某段实现细节的便捷暴露;仅在确认需要长期对外承诺时再加入 Barrel 导出。
86
94
 
87
95
  ### 测试要求
88
96
 
@@ -94,3 +102,4 @@ Exception 模块提供围绕宿主环境全局异常入口(global exception en
94
102
  - 若该模块不需要测试,必须在说明文件中明确说明该模块不需要测试,并说明理由。一般来说,只有在该模块没有可执行的公共函数、只承载类型层表达,或其语义已被上层模块的测试完整覆盖且重复测试几乎不再带来额外价值时,才适合这样处理。
95
103
  - 模块的单元测试文件目录是 `./tests/unit/exception`,若模块包含子模块,则子模块的单元测试文件目录为 `./tests/unit/exception/<sub-module-name>`。
96
104
  - 测试应重点覆盖监听注册、清理行为、默认包含的入口集合、记录标准化结果,以及宿主不可用时的失败或空操作语义是否稳定。
105
+ - 若新增 Tagged Error 能力,应重点覆盖 tag 判别、message 与 `cause` 推导、序列化结果,以及按 tag 进行穷举或部分匹配时的返回语义是否稳定。
@@ -0,0 +1,123 @@
1
+
2
+ const isString = (value: unknown): value is string => {
3
+ return typeof value === "string"
4
+ }
5
+ const isObject = (value: unknown): value is object => {
6
+ return typeof value === "object" && value !== null
7
+ }
8
+ const extractMessage = (data: unknown): string | undefined => {
9
+ if (isString(data)) {
10
+ return data
11
+ } else if (isObject(data) && "message" in data && isString(data.message)) {
12
+ return data.message
13
+ } else {
14
+ return undefined
15
+ }
16
+ }
17
+ const extractCause = (data: unknown): unknown => {
18
+ if (isObject(data) && "cause" in data) {
19
+ return data.cause
20
+ } else {
21
+ return undefined
22
+ }
23
+ }
24
+ const formatCauseStack = (cause: unknown): string => {
25
+ if ((cause instanceof Error) === false || cause.stack === undefined) {
26
+ return ""
27
+ }
28
+ const indented = cause.stack.replaceAll("\n", "\n ")
29
+ return `\nCaused by: ${indented}`
30
+ }
31
+ const serializeCause = (cause: unknown): unknown => {
32
+ if (cause instanceof Error) {
33
+ return { name: cause.name, message: cause.message, stack: cause.stack };
34
+ }
35
+ return cause
36
+ }
37
+
38
+ export interface SerializedTaggedError<Tag extends string, Data = unknown> {
39
+ version: "1" // for future compatibility
40
+ tag: Tag
41
+ data: Data
42
+ name: string
43
+ message: string
44
+ cause: unknown | undefined
45
+ stack: string | undefined
46
+ timestamp: number
47
+ }
48
+ export interface TaggedErrorAugments<Tag extends string, Data = unknown> {
49
+ readonly tag: Tag
50
+ readonly data: Data
51
+ toJSON(): SerializedTaggedError<Tag, Data>
52
+ }
53
+ export type TaggedErrorInstance<Tag extends string, Data = unknown> =
54
+ & Error
55
+ & TaggedErrorAugments<Tag, Data>
56
+ export type TaggedErrorConstructor<Tag extends string, Data = unknown> = {
57
+ new(data: Data): TaggedErrorInstance<Tag, Data>
58
+ is(target: unknown): target is TaggedErrorInstance<Tag, Data>
59
+ }
60
+ export const createTaggedError = <const Tag extends string>(tag: Tag) => {
61
+ return <Data = unknown>(): TaggedErrorConstructor<Tag, Data> => {
62
+ return class Impl extends Error {
63
+ readonly tag: Tag
64
+ readonly data: Data
65
+
66
+ constructor(data: Data) {
67
+ const message = extractMessage(data)
68
+ const cause = extractCause(data)
69
+
70
+ super(message, cause !== undefined ? { cause } : undefined)
71
+ Object.setPrototypeOf(this, new.target.prototype)
72
+ this.name = tag
73
+ this.stack = `${this.stack}${formatCauseStack(cause)}`
74
+
75
+ this.tag = tag
76
+ this.data = data
77
+ }
78
+
79
+ static is(target: unknown): target is TaggedErrorInstance<Tag, Data> {
80
+ if (target instanceof Impl) {
81
+ return true
82
+ }
83
+ if (isTaggedError(target) && target.tag === tag) {
84
+ return true
85
+ }
86
+ return false
87
+ }
88
+
89
+ toJSON(): SerializedTaggedError<Tag, Data> {
90
+ return {
91
+ version: "1",
92
+ tag: this.tag,
93
+ data: this.data,
94
+ name: this.name,
95
+ message: this.message,
96
+ cause: serializeCause(this.cause),
97
+ stack: this.stack,
98
+ timestamp: Date.now(),
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+
105
+ export type AnyTaggedError = TaggedErrorInstance<string, unknown>
106
+ export const isTaggedError = (target: unknown): target is AnyTaggedError => {
107
+ if (typeof target !== "object" || target === null) {
108
+ return false
109
+ }
110
+ if (target instanceof Error === false) {
111
+ return false
112
+ }
113
+ if (!("tag" in target) || typeof target.tag !== "string") {
114
+ return false
115
+ }
116
+ if (!("data" in target)) {
117
+ return false
118
+ }
119
+ if (!("toJSON" in target) || typeof target.toJSON !== "function") {
120
+ return false
121
+ }
122
+ return true
123
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./error.ts"
2
+ export * from "./match.ts"
@@ -0,0 +1,38 @@
1
+ import type { AnyTaggedError } from "./error.ts"
2
+
3
+ export type MatchTaggedErrorHandlers<E extends AnyTaggedError, R> = {
4
+ [Tag in E["tag"]]: (error: Extract<E, { tag: Tag }>) => R
5
+ }
6
+ export const matchTaggedError = <E extends AnyTaggedError, R>(
7
+ taggedError: E,
8
+ handlers: MatchTaggedErrorHandlers<E, R>,
9
+ ): R => {
10
+ const handler = handlers[taggedError.tag as E["tag"]]
11
+ if (handler === undefined) {
12
+ throw new Error(`No handler for tag: ${taggedError.tag}`)
13
+ }
14
+ return handler(taggedError as Extract<E, { tag: E["tag"] }>)
15
+ }
16
+
17
+ export type MatchTaggedErrorPartialHandlers<E extends AnyTaggedError, R> = {
18
+ [Tag in E["tag"]]?: ((error: Extract<E, { tag: Tag }>) => R) | undefined
19
+ }
20
+ type UnhandledTaggedError<E extends AnyTaggedError, H> = Exclude<
21
+ E,
22
+ { tag: keyof H }
23
+ >
24
+ export const matchTaggedErrorPartial = <
25
+ E extends AnyTaggedError,
26
+ R,
27
+ const Handlers extends MatchTaggedErrorPartialHandlers<E, R> = MatchTaggedErrorPartialHandlers<E, R>
28
+ >(
29
+ taggedError: E,
30
+ handlers: Handlers,
31
+ fallback: (error: UnhandledTaggedError<E, Handlers>) => R,
32
+ ): R => {
33
+ const handler = handlers[taggedError.tag as E["tag"]]
34
+ if (handler !== undefined) {
35
+ return handler(taggedError as Extract<E, { tag: E["tag"] }>)
36
+ }
37
+ return fallback(taggedError as UnhandledTaggedError<E, Handlers>)
38
+ }