@planet-matrix/mobius-model 0.5.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +123 -36
- package/dist/index.js +715 -4
- package/dist/index.js.map +981 -13
- package/oxlint.config.ts +6 -0
- package/package.json +36 -18
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +72 -116
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +83 -64
- package/src/basic/index.ts +1 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -0
- package/src/encoding/README.md +66 -79
- package/src/encoding/base64.ts +13 -4
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +229 -0
- package/src/event/common.ts +29 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +187 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +33 -1
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +554 -0
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +91 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +3 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +56 -86
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +91 -38
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/credential/api-key.spec.ts +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/json/repair.spec.ts +11 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +237 -0
- package/tests/unit/openai/openai.spec.ts +64 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/.oxlintrc.json +0 -5
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { Tube } from "#Source/tube/index.ts"
|
|
4
|
+
|
|
5
|
+
const createTube = (options: Partial<ConstructorParameters<typeof Tube<number, string | Error>>[0]> = {}): Tube<number, string | Error> => {
|
|
6
|
+
return new Tube<number, string | Error>({
|
|
7
|
+
historyCount: 3,
|
|
8
|
+
replayHistory: false,
|
|
9
|
+
...options,
|
|
10
|
+
})
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const runAllTubeTasks = async (): Promise<void> => {
|
|
14
|
+
await vi.runAllTimersAsync()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
vi.useRealTimers()
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
test("Tube.safeGetLatestData returns the latest data and throws before any data arrives", async () => {
|
|
22
|
+
const tube = createTube()
|
|
23
|
+
|
|
24
|
+
expect(() => tube.safeGetLatestData()).toThrow("latestData is undefined")
|
|
25
|
+
|
|
26
|
+
await tube.pushData(1)
|
|
27
|
+
await tube.pushData(2)
|
|
28
|
+
|
|
29
|
+
expect(tube.safeGetLatestData()).toBe(2)
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("Tube.safeGetLatestError returns the latest error and throws before any error arrives", async () => {
|
|
33
|
+
const tube = createTube()
|
|
34
|
+
|
|
35
|
+
expect(() => tube.safeGetLatestError()).toThrow("latestError is undefined")
|
|
36
|
+
|
|
37
|
+
await tube.pushError("first")
|
|
38
|
+
await tube.pushError("second")
|
|
39
|
+
|
|
40
|
+
expect(tube.safeGetLatestError()).toBe("second")
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
test("Tube.isOpen reflects whether the tube is currently open", async () => {
|
|
44
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
45
|
+
|
|
46
|
+
expect(tube.isOpen()).toBe(false)
|
|
47
|
+
|
|
48
|
+
await tube.open()
|
|
49
|
+
|
|
50
|
+
expect(tube.isOpen()).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("Tube.hasOpened reflects whether the tube has ever been opened", async () => {
|
|
54
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
55
|
+
|
|
56
|
+
expect(tube.hasOpened()).toBe(false)
|
|
57
|
+
|
|
58
|
+
await tube.open()
|
|
59
|
+
|
|
60
|
+
expect(tube.hasOpened()).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("Tube.open opens only once and emits the open event", async () => {
|
|
64
|
+
vi.useFakeTimers()
|
|
65
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
66
|
+
const events: string[] = []
|
|
67
|
+
|
|
68
|
+
tube.subscribeOpenEvent({
|
|
69
|
+
subscriber: () => {
|
|
70
|
+
events.push("open")
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
await tube.open()
|
|
75
|
+
await tube.open()
|
|
76
|
+
await runAllTubeTasks()
|
|
77
|
+
|
|
78
|
+
expect(tube.isOpen()).toBe(true)
|
|
79
|
+
expect(events).toEqual(["open"])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
test("Tube.isClose reflects whether the tube is currently closed", async () => {
|
|
83
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
84
|
+
|
|
85
|
+
expect(tube.isClose()).toBe(true)
|
|
86
|
+
|
|
87
|
+
await tube.open()
|
|
88
|
+
|
|
89
|
+
expect(tube.isClose()).toBe(false)
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test("Tube.hasClosed reflects whether the tube has ever been closed", async () => {
|
|
93
|
+
const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false })
|
|
94
|
+
|
|
95
|
+
expect(tube.hasClosed()).toBe(false)
|
|
96
|
+
|
|
97
|
+
await tube.open()
|
|
98
|
+
await tube.close()
|
|
99
|
+
|
|
100
|
+
expect(tube.hasClosed()).toBe(true)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
test("Tube.close closes only once and emits the close event", async () => {
|
|
104
|
+
vi.useFakeTimers()
|
|
105
|
+
const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false })
|
|
106
|
+
const events: string[] = []
|
|
107
|
+
|
|
108
|
+
tube.subscribeCloseEvent({
|
|
109
|
+
subscriber: () => {
|
|
110
|
+
events.push("close")
|
|
111
|
+
},
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
await tube.open()
|
|
115
|
+
await tube.close()
|
|
116
|
+
await tube.close()
|
|
117
|
+
await runAllTubeTasks()
|
|
118
|
+
|
|
119
|
+
expect(tube.isClose()).toBe(true)
|
|
120
|
+
expect(events).toEqual(["close"])
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test("Tube.isStart reflects whether the tube is currently started", async () => {
|
|
124
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
125
|
+
|
|
126
|
+
expect(tube.isStart()).toBe(false)
|
|
127
|
+
|
|
128
|
+
await tube.start()
|
|
129
|
+
|
|
130
|
+
expect(tube.isStart()).toBe(true)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test("Tube.hasStarted reflects whether the tube has ever started", async () => {
|
|
134
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
135
|
+
|
|
136
|
+
expect(tube.hasStarted()).toBe(false)
|
|
137
|
+
|
|
138
|
+
await tube.start()
|
|
139
|
+
|
|
140
|
+
expect(tube.hasStarted()).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
test("Tube.start starts only once and auto-opens when needed", async () => {
|
|
144
|
+
vi.useFakeTimers()
|
|
145
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
146
|
+
const events: string[] = []
|
|
147
|
+
|
|
148
|
+
tube.subscribeStartEvent({
|
|
149
|
+
subscriber: () => {
|
|
150
|
+
events.push("start")
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
await tube.start()
|
|
155
|
+
await tube.start()
|
|
156
|
+
await runAllTubeTasks()
|
|
157
|
+
|
|
158
|
+
expect(tube.isOpen()).toBe(true)
|
|
159
|
+
expect(tube.isStart()).toBe(true)
|
|
160
|
+
expect(events).toEqual(["start"])
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test("Tube.isEnd reflects whether the tube is currently ended", async () => {
|
|
164
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
165
|
+
|
|
166
|
+
expect(tube.isEnd()).toBe(true)
|
|
167
|
+
|
|
168
|
+
await tube.start()
|
|
169
|
+
|
|
170
|
+
expect(tube.isEnd()).toBe(false)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test("Tube.hasEnded reflects whether the tube has ever ended", async () => {
|
|
174
|
+
const tube = createTube({ autoStartOnOpen: false, autoCloseOnEnd: false })
|
|
175
|
+
|
|
176
|
+
expect(tube.hasEnded()).toBe(false)
|
|
177
|
+
|
|
178
|
+
await tube.start()
|
|
179
|
+
await tube.end()
|
|
180
|
+
|
|
181
|
+
expect(tube.hasEnded()).toBe(true)
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
test("Tube.end ends only once and closes when configured", async () => {
|
|
185
|
+
vi.useFakeTimers()
|
|
186
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
187
|
+
const events: string[] = []
|
|
188
|
+
|
|
189
|
+
tube.subscribeEndEvent({
|
|
190
|
+
subscriber: () => {
|
|
191
|
+
events.push("end")
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
await tube.start()
|
|
196
|
+
await tube.end()
|
|
197
|
+
await tube.end()
|
|
198
|
+
await runAllTubeTasks()
|
|
199
|
+
|
|
200
|
+
expect(tube.isEnd()).toBe(true)
|
|
201
|
+
expect(tube.hasClosed()).toBe(true)
|
|
202
|
+
expect(events).toEqual(["end"])
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
test("Tube.isError reflects whether the tube has seen an error", async () => {
|
|
206
|
+
const tube = createTube()
|
|
207
|
+
|
|
208
|
+
expect(tube.isError()).toBe(false)
|
|
209
|
+
|
|
210
|
+
await tube.pushError("boom")
|
|
211
|
+
|
|
212
|
+
expect(tube.isError()).toBe(true)
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test("Tube.isWet reflects whether the tube has received any data", async () => {
|
|
216
|
+
const tube = createTube()
|
|
217
|
+
|
|
218
|
+
expect(tube.isWet()).toBe(false)
|
|
219
|
+
|
|
220
|
+
await tube.pushData(1)
|
|
221
|
+
|
|
222
|
+
expect(tube.isWet()).toBe(true)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test("Tube.subscribeOpenEvent returns a stable unsubscribe function and delivers open notifications", async () => {
|
|
226
|
+
vi.useFakeTimers()
|
|
227
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
228
|
+
const subscriber = vi.fn<() => void>(() => undefined)
|
|
229
|
+
|
|
230
|
+
const firstUnsubscribe = tube.subscribeOpenEvent({ subscriber })
|
|
231
|
+
const secondUnsubscribe = tube.subscribeOpenEvent({ subscriber })
|
|
232
|
+
|
|
233
|
+
await tube.open()
|
|
234
|
+
await runAllTubeTasks()
|
|
235
|
+
|
|
236
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
237
|
+
expect(subscriber).toHaveBeenCalledTimes(1)
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test("Tube.unsubscribeOpenEvent removes only the specified open subscriber", async () => {
|
|
241
|
+
vi.useFakeTimers()
|
|
242
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
243
|
+
const keptSubscriber = vi.fn<() => void>(() => undefined)
|
|
244
|
+
const removedSubscriber = vi.fn<() => void>(() => undefined)
|
|
245
|
+
|
|
246
|
+
tube.subscribeOpenEvent({ subscriber: keptSubscriber })
|
|
247
|
+
tube.subscribeOpenEvent({ subscriber: removedSubscriber })
|
|
248
|
+
tube.unsubscribeOpenEvent(removedSubscriber)
|
|
249
|
+
|
|
250
|
+
await tube.open()
|
|
251
|
+
await runAllTubeTasks()
|
|
252
|
+
|
|
253
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
254
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
test("Tube.subscribeCloseEvent returns a stable unsubscribe function and delivers close notifications", async () => {
|
|
258
|
+
vi.useFakeTimers()
|
|
259
|
+
const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false })
|
|
260
|
+
const subscriber = vi.fn<() => void>(() => undefined)
|
|
261
|
+
|
|
262
|
+
const firstUnsubscribe = tube.subscribeCloseEvent({ subscriber })
|
|
263
|
+
const secondUnsubscribe = tube.subscribeCloseEvent({ subscriber })
|
|
264
|
+
|
|
265
|
+
await tube.open()
|
|
266
|
+
await tube.close()
|
|
267
|
+
await runAllTubeTasks()
|
|
268
|
+
|
|
269
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
270
|
+
expect(subscriber).toHaveBeenCalledTimes(1)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test("Tube.unsubscribeCloseEvent removes only the specified close subscriber", async () => {
|
|
274
|
+
vi.useFakeTimers()
|
|
275
|
+
const tube = createTube({ autoStartOnOpen: false, autoEndOnClose: false })
|
|
276
|
+
const keptSubscriber = vi.fn<() => void>(() => undefined)
|
|
277
|
+
const removedSubscriber = vi.fn<() => void>(() => undefined)
|
|
278
|
+
|
|
279
|
+
tube.subscribeCloseEvent({ subscriber: keptSubscriber })
|
|
280
|
+
tube.subscribeCloseEvent({ subscriber: removedSubscriber })
|
|
281
|
+
tube.unsubscribeCloseEvent(removedSubscriber)
|
|
282
|
+
|
|
283
|
+
await tube.open()
|
|
284
|
+
await tube.close()
|
|
285
|
+
await runAllTubeTasks()
|
|
286
|
+
|
|
287
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
288
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
test("Tube.subscribeStartEvent returns a stable unsubscribe function and delivers start notifications", async () => {
|
|
292
|
+
vi.useFakeTimers()
|
|
293
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
294
|
+
const subscriber = vi.fn<() => void>(() => undefined)
|
|
295
|
+
|
|
296
|
+
const firstUnsubscribe = tube.subscribeStartEvent({ subscriber })
|
|
297
|
+
const secondUnsubscribe = tube.subscribeStartEvent({ subscriber })
|
|
298
|
+
|
|
299
|
+
await tube.start()
|
|
300
|
+
await runAllTubeTasks()
|
|
301
|
+
|
|
302
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
303
|
+
expect(subscriber).toHaveBeenCalledTimes(1)
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
test("Tube.unsubscribeStartEvent removes only the specified start subscriber", async () => {
|
|
307
|
+
vi.useFakeTimers()
|
|
308
|
+
const tube = createTube({ autoStartOnOpen: false })
|
|
309
|
+
const keptSubscriber = vi.fn<() => void>(() => undefined)
|
|
310
|
+
const removedSubscriber = vi.fn<() => void>(() => undefined)
|
|
311
|
+
|
|
312
|
+
tube.subscribeStartEvent({ subscriber: keptSubscriber })
|
|
313
|
+
tube.subscribeStartEvent({ subscriber: removedSubscriber })
|
|
314
|
+
tube.unsubscribeStartEvent(removedSubscriber)
|
|
315
|
+
|
|
316
|
+
await tube.start()
|
|
317
|
+
await runAllTubeTasks()
|
|
318
|
+
|
|
319
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
320
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
test("Tube.subscribeEndEvent returns a stable unsubscribe function and delivers end notifications", async () => {
|
|
324
|
+
vi.useFakeTimers()
|
|
325
|
+
const tube = createTube({ autoStartOnOpen: false, autoCloseOnEnd: false })
|
|
326
|
+
const subscriber = vi.fn<() => void>(() => undefined)
|
|
327
|
+
|
|
328
|
+
const firstUnsubscribe = tube.subscribeEndEvent({ subscriber })
|
|
329
|
+
const secondUnsubscribe = tube.subscribeEndEvent({ subscriber })
|
|
330
|
+
|
|
331
|
+
await tube.start()
|
|
332
|
+
await tube.end()
|
|
333
|
+
await runAllTubeTasks()
|
|
334
|
+
|
|
335
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
336
|
+
expect(subscriber).toHaveBeenCalledTimes(1)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
test("Tube.unsubscribeEndEvent removes only the specified end subscriber", async () => {
|
|
340
|
+
vi.useFakeTimers()
|
|
341
|
+
const tube = createTube({ autoStartOnOpen: false, autoCloseOnEnd: false })
|
|
342
|
+
const keptSubscriber = vi.fn<() => void>(() => undefined)
|
|
343
|
+
const removedSubscriber = vi.fn<() => void>(() => undefined)
|
|
344
|
+
|
|
345
|
+
tube.subscribeEndEvent({ subscriber: keptSubscriber })
|
|
346
|
+
tube.subscribeEndEvent({ subscriber: removedSubscriber })
|
|
347
|
+
tube.unsubscribeEndEvent(removedSubscriber)
|
|
348
|
+
|
|
349
|
+
await tube.start()
|
|
350
|
+
await tube.end()
|
|
351
|
+
await runAllTubeTasks()
|
|
352
|
+
|
|
353
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
354
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
test("Tube.subscribeWetEvent returns a stable unsubscribe function and delivers the first wet notification", async () => {
|
|
358
|
+
vi.useFakeTimers()
|
|
359
|
+
const tube = createTube()
|
|
360
|
+
const subscriber = vi.fn<() => void>(() => undefined)
|
|
361
|
+
|
|
362
|
+
const firstUnsubscribe = tube.subscribeWetEvent({ subscriber })
|
|
363
|
+
const secondUnsubscribe = tube.subscribeWetEvent({ subscriber })
|
|
364
|
+
|
|
365
|
+
await tube.pushData(1)
|
|
366
|
+
await tube.pushData(2)
|
|
367
|
+
await runAllTubeTasks()
|
|
368
|
+
|
|
369
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
370
|
+
expect(subscriber).toHaveBeenCalledTimes(1)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test("Tube.unsubscribeWetEvent removes only the specified wet subscriber", async () => {
|
|
374
|
+
vi.useFakeTimers()
|
|
375
|
+
const tube = createTube()
|
|
376
|
+
const keptSubscriber = vi.fn<() => void>(() => undefined)
|
|
377
|
+
const removedSubscriber = vi.fn<() => void>(() => undefined)
|
|
378
|
+
|
|
379
|
+
tube.subscribeWetEvent({ subscriber: keptSubscriber })
|
|
380
|
+
tube.subscribeWetEvent({ subscriber: removedSubscriber })
|
|
381
|
+
tube.unsubscribeWetEvent(removedSubscriber)
|
|
382
|
+
|
|
383
|
+
await tube.pushData(1)
|
|
384
|
+
await runAllTubeTasks()
|
|
385
|
+
|
|
386
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
387
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
test("Tube.pushError records the error, opens the tube when needed and emits error notifications", async () => {
|
|
391
|
+
vi.useFakeTimers()
|
|
392
|
+
const tube = createTube({ autoEndOnError: false, autoCloseOnError: false })
|
|
393
|
+
const errors: Array<string | Error> = []
|
|
394
|
+
|
|
395
|
+
tube.subscribeErrorEvent({
|
|
396
|
+
subscriber: (error) => {
|
|
397
|
+
errors.push(error)
|
|
398
|
+
},
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
await tube.pushError("boom")
|
|
402
|
+
await runAllTubeTasks()
|
|
403
|
+
|
|
404
|
+
expect(tube.isOpen()).toBe(true)
|
|
405
|
+
expect(tube.isError()).toBe(true)
|
|
406
|
+
expect(errors).toEqual(["boom"])
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
test("Tube.subscribeErrorEvent returns a stable unsubscribe function and delivers error notifications", async () => {
|
|
410
|
+
vi.useFakeTimers()
|
|
411
|
+
const tube = createTube({ autoEndOnError: false, autoCloseOnError: false })
|
|
412
|
+
const subscriber = vi.fn<(error: string | Error) => void>(() => undefined)
|
|
413
|
+
|
|
414
|
+
const firstUnsubscribe = tube.subscribeErrorEvent({ subscriber })
|
|
415
|
+
const secondUnsubscribe = tube.subscribeErrorEvent({ subscriber })
|
|
416
|
+
|
|
417
|
+
await tube.pushError("boom")
|
|
418
|
+
await runAllTubeTasks()
|
|
419
|
+
|
|
420
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
421
|
+
expect(subscriber).toHaveBeenCalledTimes(1)
|
|
422
|
+
expect(subscriber).toHaveBeenCalledWith("boom")
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test("Tube.unsubscribeErrorEvent removes only the specified error subscriber", async () => {
|
|
426
|
+
vi.useFakeTimers()
|
|
427
|
+
const tube = createTube({ autoEndOnError: false, autoCloseOnError: false })
|
|
428
|
+
const keptSubscriber = vi.fn<(error: string | Error) => void>(() => undefined)
|
|
429
|
+
const removedSubscriber = vi.fn<(error: string | Error) => void>(() => undefined)
|
|
430
|
+
|
|
431
|
+
tube.subscribeErrorEvent({ subscriber: keptSubscriber })
|
|
432
|
+
tube.subscribeErrorEvent({ subscriber: removedSubscriber })
|
|
433
|
+
tube.unsubscribeErrorEvent(removedSubscriber)
|
|
434
|
+
|
|
435
|
+
await tube.pushError("boom")
|
|
436
|
+
await runAllTubeTasks()
|
|
437
|
+
|
|
438
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
439
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
test("Tube.pushData records data, auto-starts the tube and trims history by historyCount", async () => {
|
|
443
|
+
vi.useFakeTimers()
|
|
444
|
+
const tube = createTube({ historyCount: 1 })
|
|
445
|
+
const values: number[] = []
|
|
446
|
+
|
|
447
|
+
tube.subscribeData({
|
|
448
|
+
subscriber: (value) => {
|
|
449
|
+
values.push(value)
|
|
450
|
+
},
|
|
451
|
+
})
|
|
452
|
+
|
|
453
|
+
await tube.pushData(1)
|
|
454
|
+
await tube.pushData(2)
|
|
455
|
+
await runAllTubeTasks()
|
|
456
|
+
|
|
457
|
+
expect(tube.isOpen()).toBe(true)
|
|
458
|
+
expect(tube.isStart()).toBe(true)
|
|
459
|
+
expect(tube.isWet()).toBe(true)
|
|
460
|
+
expect(tube.safeGetLatestData()).toBe(2)
|
|
461
|
+
expect(values).toEqual([1, 2])
|
|
462
|
+
})
|
|
463
|
+
|
|
464
|
+
test("Tube.subscribeData returns a stable unsubscribe function and can replay history", async () => {
|
|
465
|
+
vi.useFakeTimers()
|
|
466
|
+
const tube = createTube()
|
|
467
|
+
const values: number[] = []
|
|
468
|
+
const subscriber = vi.fn<(value: number) => void>((value) => {
|
|
469
|
+
values.push(value)
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
await tube.pushData(1)
|
|
473
|
+
await tube.pushData(2)
|
|
474
|
+
|
|
475
|
+
const firstUnsubscribe = tube.subscribeData({ subscriber, replayHistory: true })
|
|
476
|
+
const secondUnsubscribe = tube.subscribeData({ subscriber, replayHistory: true })
|
|
477
|
+
|
|
478
|
+
await tube.pushData(3)
|
|
479
|
+
await runAllTubeTasks()
|
|
480
|
+
|
|
481
|
+
expect(secondUnsubscribe).toBe(firstUnsubscribe)
|
|
482
|
+
expect(values).toEqual([1, 2, 3])
|
|
483
|
+
expect(subscriber).toHaveBeenCalledTimes(3)
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
test("Tube.unsubscribeData removes only the specified data subscriber", async () => {
|
|
487
|
+
vi.useFakeTimers()
|
|
488
|
+
const tube = createTube()
|
|
489
|
+
const keptSubscriber = vi.fn<(value: number) => void>(() => undefined)
|
|
490
|
+
const removedSubscriber = vi.fn<(value: number) => void>(() => undefined)
|
|
491
|
+
|
|
492
|
+
tube.subscribeData({ subscriber: keptSubscriber })
|
|
493
|
+
tube.subscribeData({ subscriber: removedSubscriber })
|
|
494
|
+
tube.unsubscribeData(removedSubscriber)
|
|
495
|
+
|
|
496
|
+
await tube.pushData(1)
|
|
497
|
+
await runAllTubeTasks()
|
|
498
|
+
|
|
499
|
+
expect(keptSubscriber).toHaveBeenCalledTimes(1)
|
|
500
|
+
expect(removedSubscriber).not.toHaveBeenCalled()
|
|
501
|
+
})
|
package/.oxlintrc.json
DELETED
package/src/random/uuid.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
const internalUUID_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
3
|
-
/**
|
|
4
|
-
* Check whether input is a valid UUID string.
|
|
5
|
-
*
|
|
6
|
-
* @example
|
|
7
|
-
* ```
|
|
8
|
-
* // Expect: true
|
|
9
|
-
* const example1 = isUuid("550e8400-e29b-41d4-a716-446655440000")
|
|
10
|
-
* // Expect: false
|
|
11
|
-
* const example2 = isUuid("not-a-uuid")
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
14
|
-
export const isUuid = (input: string): boolean => {
|
|
15
|
-
return internalUUID_REGEXP.test(input)
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Assert input is a valid UUID string.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```
|
|
23
|
-
* // Expect: no throw
|
|
24
|
-
* const example1 = assertUuid("550e8400-e29b-41d4-a716-446655440000")
|
|
25
|
-
* // Expect: throws TypeError
|
|
26
|
-
* const example2 = () => assertUuid("not-a-uuid")
|
|
27
|
-
* ```
|
|
28
|
-
*
|
|
29
|
-
* @throws {TypeError} when input is not a valid UUID
|
|
30
|
-
*/
|
|
31
|
-
export const assertUuid = (input: string): void => {
|
|
32
|
-
if (isUuid(input) === false) {
|
|
33
|
-
throw new TypeError(`Expected a valid UUID string, got: ${input}`)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Get the version number from a valid UUID string.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```
|
|
42
|
-
* // Expect: 4
|
|
43
|
-
* const example1 = getUuidVersion("550e8400-e29b-41d4-a716-446655440000")
|
|
44
|
-
* // Expect: 1
|
|
45
|
-
* const example2 = getUuidVersion("123e4567-e89b-12d3-a456-426614174000")
|
|
46
|
-
* ```
|
|
47
|
-
*
|
|
48
|
-
* @throws {TypeError} when input is not a valid UUID
|
|
49
|
-
*/
|
|
50
|
-
export const getUuidVersion = (input: string): number => {
|
|
51
|
-
// 1) Ensure the input is a syntactically valid UUID string.
|
|
52
|
-
// If invalid, assertUuid throws TypeError and prevents unsafe parsing.
|
|
53
|
-
assertUuid(input)
|
|
54
|
-
|
|
55
|
-
// 2) Per UUID canonical format xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx,
|
|
56
|
-
// the version nibble is the first hex digit of the 3rd group.
|
|
57
|
-
// In a 36-char UUID string, that position is index 14.
|
|
58
|
-
|
|
59
|
-
// 3) Convert the single hex character (for example "4") to a base-10 number.
|
|
60
|
-
// parseInt("4", 16) -> 4, parseInt("a", 16) -> 10.
|
|
61
|
-
return Number.parseInt(input[14]!, 16)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Generate a RFC 4122 version-4 UUID string.
|
|
66
|
-
*
|
|
67
|
-
* @example
|
|
68
|
-
* ```
|
|
69
|
-
* const example1 = generateUuid()
|
|
70
|
-
* // Expect: true
|
|
71
|
-
* const example2 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(example1)
|
|
72
|
-
* ```
|
|
73
|
-
*/
|
|
74
|
-
export const generateUuid = (): string => {
|
|
75
|
-
if (typeof crypto === "object") {
|
|
76
|
-
if (typeof crypto.randomUUID === "function") {
|
|
77
|
-
return crypto.randomUUID()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
if (typeof crypto.getRandomValues === "function" && typeof Uint8Array === "function") {
|
|
81
|
-
const buffer = new Uint8Array(16)
|
|
82
|
-
crypto.getRandomValues(buffer)
|
|
83
|
-
|
|
84
|
-
// Per RFC 4122, set bits for version and `clock_seq_hi_and_reserved`
|
|
85
|
-
buffer[6] = (buffer[6]! & 0x0F) | 0x40 // version 4
|
|
86
|
-
buffer[8] = (buffer[8]! & 0x3F) | 0x80 // variant 1
|
|
87
|
-
|
|
88
|
-
// Convert buffer to UUID string format
|
|
89
|
-
const hex = [...buffer].map(b => b.toString(16).padStart(2, "0")).join("")
|
|
90
|
-
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Fallback for environments without crypto support
|
|
95
|
-
const fallbackUUID = (): string => {
|
|
96
|
-
const random = (a: number): string => {
|
|
97
|
-
return ((a ^ ((Math.random() * 16) >> (a / 4))) & 15).toString(16)
|
|
98
|
-
}
|
|
99
|
-
return "10000000-1000-4000-8000-100000000000".replaceAll(/[018]/g, (char: string) => random(Number(char)))
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return fallbackUUID()
|
|
103
|
-
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "vitest"
|
|
2
|
-
|
|
3
|
-
import { assertUuid, generateUuid, getUuidVersion, isUuid } from "#Source/random/index.ts"
|
|
4
|
-
|
|
5
|
-
test("isUuid validates UUID input", () => {
|
|
6
|
-
expect(isUuid("550e8400-e29b-41d4-a716-446655440000")).toBe(true)
|
|
7
|
-
expect(isUuid("550E8400-E29B-41D4-A716-446655440000")).toBe(true)
|
|
8
|
-
expect(isUuid("123e4567-e89b-12d3-a456-426614174000")).toBe(true)
|
|
9
|
-
|
|
10
|
-
expect(isUuid("not-a-uuid")).toBe(false)
|
|
11
|
-
expect(isUuid("550e8400e29b41d4a716446655440000")).toBe(false)
|
|
12
|
-
expect(isUuid("550e8400-e29b-91d4-a716-446655440000")).toBe(false)
|
|
13
|
-
expect(isUuid("550e8400-e29b-41d4-c716-446655440000")).toBe(false)
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
test("assertUuid throws on malformed input", () => {
|
|
17
|
-
expect(() => assertUuid("550e8400-e29b-41d4-a716-446655440000")).not.toThrow()
|
|
18
|
-
expect(() => assertUuid("not-a-uuid")).toThrow(TypeError)
|
|
19
|
-
expect(() => assertUuid("550e8400e29b41d4a716446655440000")).toThrow(TypeError)
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
test("getUuidVersion returns UUID version and throws on malformed input", () => {
|
|
23
|
-
expect(getUuidVersion("550e8400-e29b-41d4-a716-446655440000")).toBe(4)
|
|
24
|
-
expect(getUuidVersion("123e4567-e89b-12d3-a456-426614174000")).toBe(1)
|
|
25
|
-
|
|
26
|
-
expect(() => getUuidVersion("not-a-uuid")).toThrow(TypeError)
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
test("generateUuid returns RFC 4122 version-4 UUID format", () => {
|
|
30
|
-
const UUID_V4_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
|
|
31
|
-
const value1 = generateUuid()
|
|
32
|
-
const value2 = generateUuid()
|
|
33
|
-
|
|
34
|
-
expect(value1).toMatch(UUID_V4_REGEXP)
|
|
35
|
-
expect(value2).toMatch(UUID_V4_REGEXP)
|
|
36
|
-
expect(value1).not.toBe(value2)
|
|
37
|
-
})
|