@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,81 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ createTaggedError,
5
+ matchTaggedError,
6
+ matchTaggedErrorPartial,
7
+ } from "#Source/exception/index.ts"
8
+
9
+ class NetworkError extends createTaggedError("Network")<{
10
+ message: string
11
+ status: number
12
+ }>() {
13
+ //
14
+ }
15
+
16
+ class ValidationError extends createTaggedError("Validation")<{
17
+ message: string
18
+ field: string
19
+ }>() {
20
+ //
21
+ }
22
+
23
+ type ExampleTaggedError =
24
+ | NetworkError
25
+ | ValidationError
26
+
27
+ test("matchTaggedError dispatches by tag and throws when a handler is missing", () => {
28
+ const example1 = new NetworkError({
29
+ message: "bad gateway",
30
+ status: 502,
31
+ }) as ExampleTaggedError
32
+ const example2 = new ValidationError({
33
+ message: "invalid email",
34
+ field: "email",
35
+ }) as ExampleTaggedError
36
+ const example3 = matchTaggedError(example1, {
37
+ Network: (error) => `${error.tag}:${error.data.status}`,
38
+ Validation: (error) => `${error.tag}:${error.data.field}`,
39
+ })
40
+ const example4 = matchTaggedError(example2, {
41
+ Network: (error) => `${error.tag}:${error.data.status}`,
42
+ Validation: (error) => `${error.tag}:${error.data.field}`,
43
+ })
44
+
45
+ expect(example3).toBe("Network:502")
46
+ expect(example4).toBe("Validation:email")
47
+ expect(() => {
48
+ // @ts-expect-error Testing runtime error when handler is missing
49
+ return matchTaggedError(example2, {
50
+ Network: (error) => error.data.status,
51
+ })
52
+ }).toThrow("No handler for tag: Validation")
53
+ })
54
+
55
+ test("matchTaggedErrorPartial uses a fallback for unhandled tags", () => {
56
+ const example1 = new NetworkError({
57
+ message: "bad gateway",
58
+ status: 502,
59
+ }) as ExampleTaggedError
60
+ const example2 = new ValidationError({
61
+ message: "invalid email",
62
+ field: "email",
63
+ }) as ExampleTaggedError
64
+ const example3 = matchTaggedErrorPartial(
65
+ example1,
66
+ {
67
+ Network: (error) => `${error.tag}:${error.data.status}`,
68
+ },
69
+ (error) => `${error.tag}:${error.message}`,
70
+ )
71
+ const example4 = matchTaggedErrorPartial(
72
+ example2,
73
+ {
74
+ Network: (error) => `${error.tag}:${error.data.status}`,
75
+ },
76
+ (error) => `${error.tag}:${error.data.field}`,
77
+ )
78
+
79
+ expect(example3).toBe("Network:502")
80
+ expect(example4).toBe("Validation:email")
81
+ })
@@ -0,0 +1,458 @@
1
+ import { expect, test, vi } from "vitest"
2
+
3
+ import type {
4
+ BaseInputorContext,
5
+ BaseInputorControllerOptions,
6
+ BaseInputorSchema,
7
+ } from "#Source/form/index.ts"
8
+ import { BaseInputorController } from "#Source/form/index.ts"
9
+
10
+ const TEST_INPUTOR_TYPE_NAME = "test" as const
11
+
12
+ type TestExternalValue = string | undefined
13
+ type TestInternalValue = string | undefined
14
+
15
+ interface TestInputorSchema extends BaseInputorSchema<TestExternalValue> {
16
+ inputorTypeName: typeof TEST_INPUTOR_TYPE_NAME
17
+ }
18
+
19
+ interface TestInputorContext extends BaseInputorContext<TestExternalValue, TestInternalValue> {
20
+ }
21
+
22
+ interface TestInputorControllerOptions extends BaseInputorControllerOptions<TestExternalValue, TestInputorSchema> {
23
+ }
24
+
25
+ class TestInputorController extends BaseInputorController<
26
+ TestExternalValue,
27
+ TestInternalValue,
28
+ TestInputorSchema,
29
+ TestInputorContext
30
+ > {
31
+ declare schemaType: TestInputorSchema
32
+ declare optionsType: TestInputorControllerOptions
33
+ declare constructorType: typeof TestInputorController
34
+
35
+ static inputorTypeName = TEST_INPUTOR_TYPE_NAME
36
+
37
+ inputorTypeName = TEST_INPUTOR_TYPE_NAME
38
+ protected voidExternalValue = undefined
39
+ protected voidInternalValue = undefined
40
+
41
+ protected override async constrainExternalValue(value: TestExternalValue): Promise<TestExternalValue> {
42
+ if (value === undefined) {
43
+ return await Promise.resolve(undefined)
44
+ }
45
+ return await Promise.resolve(value.trim())
46
+ }
47
+
48
+ protected override async constrainInternalValue(value: TestInternalValue): Promise<TestInternalValue> {
49
+ if (value === undefined) {
50
+ return await Promise.resolve(undefined)
51
+ }
52
+ return await Promise.resolve(value.trim())
53
+ }
54
+ }
55
+
56
+ const createTestInputor = (
57
+ overrides: Partial<TestInputorControllerOptions> = {},
58
+ ): TestInputorController => {
59
+ return new TestInputorController({
60
+ id: "test-id",
61
+ name: "test-name",
62
+ placeholder: "test-placeholder",
63
+ value: " initial ",
64
+ ...overrides,
65
+ })
66
+ }
67
+
68
+ test("BaseInputorController.isReady and waitForReady initialize defaults and emit initial hooks", async () => {
69
+ const valueChanges: Array<{ newValue: TestExternalValue; oldValue: TestExternalValue }> = []
70
+ const schemaChanges: Array<{ newSchema: TestInputorSchema; oldSchema: TestInputorSchema }> = []
71
+ const controller = createTestInputor({
72
+ onValueChange: (context) => {
73
+ valueChanges.push(context)
74
+ },
75
+ onSchemaChange: (context) => {
76
+ schemaChanges.push(context)
77
+ },
78
+ })
79
+
80
+ expect(controller.isReady()).toBe(false)
81
+
82
+ await controller.waitForReady()
83
+
84
+ expect(controller.isReady()).toBe(true)
85
+ expect(await controller.getSchema()).toEqual({
86
+ inputorTypeName: TEST_INPUTOR_TYPE_NAME,
87
+ id: "test-id",
88
+ name: "test-name",
89
+ placeholder: "test-placeholder",
90
+ value: "initial",
91
+ isVoid: false,
92
+ isFocused: false,
93
+ isTouched: false,
94
+ isDirty: false,
95
+ isDisabled: false,
96
+ isDebugMode: false,
97
+ })
98
+ expect(valueChanges).toEqual([
99
+ {
100
+ newValue: "initial",
101
+ oldValue: "initial",
102
+ },
103
+ ])
104
+ expect(schemaChanges).toHaveLength(1)
105
+ expect(schemaChanges[0]!.newSchema).toEqual(schemaChanges[0]!.oldSchema)
106
+ expect(schemaChanges[0]!.newSchema.value).toBe("initial")
107
+ })
108
+
109
+ test("BaseInputorController.getInternalValue, getContext, and isEqualSchema expose current state", async () => {
110
+ const controller = createTestInputor({
111
+ value: " alpha ",
112
+ })
113
+
114
+ await controller.waitForReady()
115
+
116
+ expect(controller.getInternalValue()).toBe("alpha")
117
+ expect(await controller.getContext()).toEqual({
118
+ inputorTypeName: TEST_INPUTOR_TYPE_NAME,
119
+ id: "test-id",
120
+ name: "test-name",
121
+ placeholder: "test-placeholder",
122
+ value: "alpha",
123
+ isVoid: false,
124
+ isFocused: false,
125
+ isTouched: false,
126
+ isDirty: false,
127
+ isDisabled: false,
128
+ isDebugMode: false,
129
+ internalValue: "alpha",
130
+ isReady: true,
131
+ })
132
+ expect(await controller.isEqualSchema({ value: "alpha", placeholder: "test-placeholder" })).toBe(true)
133
+ expect(await controller.isEqualSchema({ value: "beta" })).toBe(false)
134
+ expect(await controller.isEqualSchema(null)).toBe(false)
135
+ })
136
+
137
+ test("BaseInputorController.subscribeInputChange and updateInputValue emit input updates after debounce", async () => {
138
+ vi.useFakeTimers()
139
+
140
+ try {
141
+ const controller = createTestInputor({
142
+ value: "start",
143
+ inputDebounce: 20,
144
+ })
145
+ const inputChanges: Array<{ newInput: TestExternalValue; oldInput: TestExternalValue; oldValue: TestExternalValue }> = []
146
+
147
+ await controller.waitForReady()
148
+ controller.subscribeInputChange((context) => {
149
+ inputChanges.push(context)
150
+ })
151
+
152
+ await controller.updateInputValue(" next ")
153
+
154
+ expect(inputChanges).toEqual([])
155
+
156
+ await vi.advanceTimersByTimeAsync(20)
157
+
158
+ expect(inputChanges).toEqual([
159
+ {
160
+ newInput: " next ",
161
+ oldInput: "start",
162
+ oldValue: "start",
163
+ },
164
+ ])
165
+ }
166
+ finally {
167
+ vi.useRealTimers()
168
+ }
169
+ })
170
+
171
+ test("BaseInputorController.unsubscribeInputChange removes one input subscriber", async () => {
172
+ vi.useFakeTimers()
173
+
174
+ try {
175
+ const controller = createTestInputor({ value: "start" })
176
+ let calls = 0
177
+ const subscriber = (): void => {
178
+ calls = calls + 1
179
+ }
180
+
181
+ await controller.waitForReady()
182
+ controller.subscribeInputChange(subscriber)
183
+ controller.unsubscribeInputChange(subscriber)
184
+
185
+ await controller.updateInputValue("next")
186
+ await vi.advanceTimersByTimeAsync(0)
187
+
188
+ expect(calls).toBe(0)
189
+ }
190
+ finally {
191
+ vi.useRealTimers()
192
+ }
193
+ })
194
+
195
+ test("BaseInputorController.clearInputChangeSubscribers removes all input subscribers", async () => {
196
+ vi.useFakeTimers()
197
+
198
+ try {
199
+ const controller = createTestInputor({ value: "start" })
200
+ let calls = 0
201
+
202
+ await controller.waitForReady()
203
+ controller.subscribeInputChange(() => {
204
+ calls = calls + 1
205
+ })
206
+ controller.subscribeInputChange(() => {
207
+ calls = calls + 1
208
+ })
209
+ controller.clearInputChangeSubscribers()
210
+
211
+ await controller.updateInputValue("next")
212
+ await vi.advanceTimersByTimeAsync(0)
213
+
214
+ expect(calls).toBe(0)
215
+ }
216
+ finally {
217
+ vi.useRealTimers()
218
+ }
219
+ })
220
+
221
+ test("BaseInputorController.subscribeValueChange, applyInputValue, and updateValue emit value changes", async () => {
222
+ const controller = createTestInputor({ value: "start" })
223
+ const valueChanges: Array<{ newValue: TestExternalValue; oldValue: TestExternalValue }> = []
224
+
225
+ await controller.waitForReady()
226
+ controller.subscribeValueChange((context) => {
227
+ valueChanges.push(context)
228
+ })
229
+
230
+ await controller.updateInputValue(" next ")
231
+ await controller.applyInputValue()
232
+ await controller.updateValue(" final ")
233
+
234
+ expect(valueChanges).toEqual([
235
+ {
236
+ newValue: "next",
237
+ oldValue: "start",
238
+ },
239
+ {
240
+ newValue: "final",
241
+ oldValue: "next",
242
+ },
243
+ ])
244
+ })
245
+
246
+ test("BaseInputorController.unsubscribeValueChange removes one value subscriber", async () => {
247
+ const controller = createTestInputor({ value: "start" })
248
+ let calls = 0
249
+ const subscriber = (): void => {
250
+ calls = calls + 1
251
+ }
252
+
253
+ await controller.waitForReady()
254
+ controller.subscribeValueChange(subscriber)
255
+ controller.unsubscribeValueChange(subscriber)
256
+ await controller.updateValue("next")
257
+
258
+ expect(calls).toBe(0)
259
+ })
260
+
261
+ test("BaseInputorController.clearValueChangeSubscribers removes all value subscribers", async () => {
262
+ const controller = createTestInputor({ value: "start" })
263
+ let calls = 0
264
+
265
+ await controller.waitForReady()
266
+ controller.subscribeValueChange(() => {
267
+ calls = calls + 1
268
+ })
269
+ controller.subscribeValueChange(() => {
270
+ calls = calls + 1
271
+ })
272
+ controller.clearValueChangeSubscribers()
273
+ await controller.updateValue("next")
274
+
275
+ expect(calls).toBe(0)
276
+ })
277
+
278
+ test("BaseInputorController.subscribeSchemaChange and updatePlaceholder emit schema changes", async () => {
279
+ const controller = createTestInputor({ value: "start" })
280
+ const schemaChanges: Array<{ newSchema: TestInputorSchema; oldSchema: TestInputorSchema }> = []
281
+
282
+ await controller.waitForReady()
283
+ await Promise.resolve()
284
+ controller.subscribeSchemaChange((context) => {
285
+ schemaChanges.push(context)
286
+ })
287
+ await controller.updatePlaceholder("next-placeholder")
288
+
289
+ expect(schemaChanges.length).toBeGreaterThanOrEqual(1)
290
+ expect(schemaChanges.at(-1)!.oldSchema.placeholder).toBe("test-placeholder")
291
+ expect(schemaChanges.at(-1)!.newSchema.placeholder).toBe("next-placeholder")
292
+ })
293
+
294
+ test("BaseInputorController.unsubscribeSchemaChange removes one schema subscriber", async () => {
295
+ const controller = createTestInputor({ value: "start" })
296
+ let calls = 0
297
+ const subscriber = (): void => {
298
+ calls = calls + 1
299
+ }
300
+
301
+ await controller.waitForReady()
302
+ controller.subscribeSchemaChange(subscriber)
303
+ controller.unsubscribeSchemaChange(subscriber)
304
+ await controller.updatePlaceholder("next-placeholder")
305
+
306
+ expect(calls).toBe(0)
307
+ })
308
+
309
+ test("BaseInputorController.clearSchemaChangeSubscribers removes all schema subscribers", async () => {
310
+ const controller = createTestInputor({ value: "start" })
311
+ let calls = 0
312
+
313
+ await controller.waitForReady()
314
+ controller.subscribeSchemaChange(() => {
315
+ calls = calls + 1
316
+ })
317
+ controller.subscribeSchemaChange(() => {
318
+ calls = calls + 1
319
+ })
320
+ controller.clearSchemaChangeSubscribers()
321
+ await controller.updatePlaceholder("next-placeholder")
322
+
323
+ expect(calls).toBe(0)
324
+ })
325
+
326
+ test("BaseInputorController.updateIsVoid resets the value to its void value", async () => {
327
+ const controller = createTestInputor({ value: "start" })
328
+
329
+ await controller.waitForReady()
330
+ await controller.updateIsVoid(true)
331
+
332
+ expect(await controller.getSchema()).toMatchObject({
333
+ value: undefined,
334
+ isVoid: true,
335
+ isDirty: true,
336
+ })
337
+ })
338
+
339
+ test("BaseInputorController.updateIsFocused also marks the controller as touched", async () => {
340
+ const controller = createTestInputor({ value: "start" })
341
+
342
+ await controller.waitForReady()
343
+ await controller.updateIsFocused(true)
344
+
345
+ expect(await controller.getSchema()).toMatchObject({
346
+ isFocused: true,
347
+ isTouched: true,
348
+ })
349
+ })
350
+
351
+ test("BaseInputorController.updateIsTouched updates touched state directly", async () => {
352
+ const controller = createTestInputor({ value: "start" })
353
+
354
+ await controller.waitForReady()
355
+ await controller.updateIsTouched(true)
356
+
357
+ expect(await controller.getSchema()).toMatchObject({
358
+ isTouched: true,
359
+ })
360
+ })
361
+
362
+ test("BaseInputorController.updateIsDirty updates dirty state directly", async () => {
363
+ const controller = createTestInputor({ value: "start" })
364
+
365
+ await controller.waitForReady()
366
+ await controller.updateIsDirty(true)
367
+
368
+ expect(await controller.getSchema()).toMatchObject({
369
+ isDirty: true,
370
+ })
371
+ })
372
+
373
+ test("BaseInputorController.updateIsDisabled updates disabled state directly", async () => {
374
+ const controller = createTestInputor({ value: "start" })
375
+
376
+ await controller.waitForReady()
377
+ await controller.updateIsDisabled(true)
378
+
379
+ expect(await controller.getSchema()).toMatchObject({
380
+ isDisabled: true,
381
+ })
382
+ })
383
+
384
+ test("BaseInputorController.updateIsDebugMode updates debug mode directly", async () => {
385
+ const controller = createTestInputor({ value: "start" })
386
+
387
+ await controller.waitForReady()
388
+ await controller.updateIsDebugMode(true)
389
+
390
+ expect(await controller.getSchema()).toMatchObject({
391
+ isDebugMode: true,
392
+ })
393
+ })
394
+
395
+ test("BaseInputorController.updateSchema applies multiple schema fields in one pass", async () => {
396
+ const controller = createTestInputor({ value: "start" })
397
+
398
+ await controller.waitForReady()
399
+ await controller.updateSchema({
400
+ ...(await controller.getSchema()),
401
+ placeholder: "schema-placeholder",
402
+ value: " schema-value ",
403
+ isFocused: true,
404
+ isTouched: true,
405
+ isDirty: true,
406
+ isDisabled: true,
407
+ isDebugMode: true,
408
+ })
409
+
410
+ expect(await controller.getSchema()).toEqual({
411
+ inputorTypeName: TEST_INPUTOR_TYPE_NAME,
412
+ id: "test-id",
413
+ name: "test-name",
414
+ placeholder: "schema-placeholder",
415
+ value: "schema-value",
416
+ isVoid: false,
417
+ isFocused: true,
418
+ isTouched: true,
419
+ isDirty: true,
420
+ isDisabled: true,
421
+ isDebugMode: true,
422
+ })
423
+ })
424
+
425
+ test("BaseInputorController.destroy clears every subscriber collection", async () => {
426
+ vi.useFakeTimers()
427
+
428
+ try {
429
+ const controller = createTestInputor({ value: "start" })
430
+ let inputCalls = 0
431
+ let valueCalls = 0
432
+ let schemaCalls = 0
433
+
434
+ await controller.waitForReady()
435
+ controller.subscribeInputChange(() => {
436
+ inputCalls = inputCalls + 1
437
+ })
438
+ controller.subscribeValueChange(() => {
439
+ valueCalls = valueCalls + 1
440
+ })
441
+ controller.subscribeSchemaChange(() => {
442
+ schemaCalls = schemaCalls + 1
443
+ })
444
+ controller.destroy()
445
+
446
+ await controller.updateInputValue("next")
447
+ await vi.advanceTimersByTimeAsync(0)
448
+ await controller.updateValue("final")
449
+ await controller.updatePlaceholder("after-destroy")
450
+
451
+ expect(inputCalls).toBe(0)
452
+ expect(valueCalls).toBe(0)
453
+ expect(schemaCalls).toBe(0)
454
+ }
455
+ finally {
456
+ vi.useRealTimers()
457
+ }
458
+ })
@@ -0,0 +1,30 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ BOOLEAN_INPUTOR_TYPE_NAME,
5
+ BooleanInputorController,
6
+ } from "#Source/form/index.ts"
7
+
8
+ test("BooleanInputorController.getSchema exposes boolean schema state and updateValue toggles it", async () => {
9
+ const controller = new BooleanInputorController({
10
+ id: "boolean-id",
11
+ value: false,
12
+ })
13
+
14
+ await controller.waitForReady()
15
+ await controller.updateValue(true)
16
+
17
+ expect(await controller.getSchema()).toEqual({
18
+ inputorTypeName: BOOLEAN_INPUTOR_TYPE_NAME,
19
+ id: "boolean-id",
20
+ name: `boolean-boolean-id`,
21
+ placeholder: "",
22
+ value: true,
23
+ isVoid: false,
24
+ isFocused: false,
25
+ isTouched: false,
26
+ isDirty: true,
27
+ isDisabled: false,
28
+ isDebugMode: false,
29
+ })
30
+ })
@@ -0,0 +1,27 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ FILE_INPUTOR_TYPE_NAME,
5
+ FileInputorController,
6
+ } from "#Source/form/index.ts"
7
+
8
+ test("FileInputorController.getSchema stores file values and reports file metadata", async () => {
9
+ const file = new File(["alpha"], "alpha.txt", {
10
+ type: "text/plain",
11
+ })
12
+ const controller = new FileInputorController({
13
+ id: "file-id",
14
+ value: file,
15
+ })
16
+
17
+ await controller.waitForReady()
18
+
19
+ const schema = await controller.getSchema()
20
+
21
+ expect(schema.inputorTypeName).toBe(FILE_INPUTOR_TYPE_NAME)
22
+ expect(schema.id).toBe("file-id")
23
+ expect(schema.name).toBe("file-file-id")
24
+ expect(schema.value?.name).toBe("alpha.txt")
25
+ expect(schema.value?.type).toBe("text/plain")
26
+ expect(schema.isDirty).toBe(false)
27
+ })
@@ -0,0 +1,120 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import {
4
+ FormInputorController,
5
+ NumberInputorController,
6
+ TextInputorController,
7
+ } from "#Source/form/index.ts"
8
+
9
+ const createItems = (): {
10
+ name: TextInputorController
11
+ age: NumberInputorController
12
+ } => {
13
+ return {
14
+ name: new TextInputorController({
15
+ id: "form-name-id",
16
+ value: "alpha",
17
+ }),
18
+ age: new NumberInputorController({
19
+ id: "form-age-id",
20
+ value: 1,
21
+ min: 0,
22
+ max: 99,
23
+ }),
24
+ }
25
+ }
26
+
27
+ test("FormInputorController.updateValue decodes external values into child items", async () => {
28
+ const items = createItems()
29
+ const controller = new FormInputorController<
30
+ { name: string | undefined; age: number | undefined },
31
+ typeof items
32
+ >({
33
+ id: "form-id",
34
+ value: {
35
+ name: "alpha",
36
+ age: 1,
37
+ },
38
+ items,
39
+ encodeValue: (schemaDict) => {
40
+ return {
41
+ name: schemaDict.name.value,
42
+ age: schemaDict.age.value,
43
+ }
44
+ },
45
+ decodeValue: (value) => {
46
+ return {
47
+ name: value.name,
48
+ age: value.age,
49
+ }
50
+ },
51
+ })
52
+
53
+ await Promise.all([
54
+ items.name.waitForReady(),
55
+ items.age.waitForReady(),
56
+ controller.waitForReady(),
57
+ ])
58
+ await controller.updateValue({
59
+ name: "beta",
60
+ age: 8,
61
+ })
62
+
63
+ expect((await items.name.getSchema()).value).toBe("beta")
64
+ expect((await items.age.getSchema()).value).toBe(8)
65
+ expect((await controller.getSchema()).value).toEqual({
66
+ name: "beta",
67
+ age: 8,
68
+ })
69
+ })
70
+
71
+ test("FormInputorController reacts to child schema changes through encodeValue", async () => {
72
+ const items = createItems()
73
+ const controller = new FormInputorController<
74
+ { name: string | undefined; age: number | undefined },
75
+ typeof items
76
+ >({
77
+ id: "form-id",
78
+ value: {
79
+ name: "alpha",
80
+ age: 1,
81
+ },
82
+ items,
83
+ encodeValue: (schemaDict) => {
84
+ return {
85
+ name: schemaDict.name.value,
86
+ age: schemaDict.age.value,
87
+ }
88
+ },
89
+ decodeValue: (value) => {
90
+ return {
91
+ name: value.name,
92
+ age: value.age,
93
+ }
94
+ },
95
+ })
96
+
97
+ await Promise.all([
98
+ items.name.waitForReady(),
99
+ items.age.waitForReady(),
100
+ controller.waitForReady(),
101
+ ])
102
+ await Promise.resolve()
103
+
104
+ const nextFormValue = new Promise<{ name: string | undefined; age: number | undefined }>((resolve) => {
105
+ controller.subscribeValueChange((context) => {
106
+ resolve(context.newValue)
107
+ })
108
+ })
109
+
110
+ await items.name.updateValue("gamma")
111
+
112
+ expect(await nextFormValue).toEqual({
113
+ name: "gamma",
114
+ age: 1,
115
+ })
116
+ expect((await controller.getSchema()).value).toEqual({
117
+ name: "gamma",
118
+ age: 1,
119
+ })
120
+ })