@planet-matrix/mobius-model 0.5.0 → 0.6.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 +24 -0
- package/README.md +123 -36
- package/dist/index.js +45 -4
- package/dist/index.js.map +183 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- 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/basic/README.md +69 -118
- package/src/basic/function.ts +81 -62
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- 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/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -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 +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -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/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 +16 -1
- 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 +543 -0
- package/src/orchestration/README.md +89 -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/index.ts +1 -0
- package/src/random/README.md +55 -86
- package/src/random/index.ts +1 -1
- package/src/random/string.ts +35 -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/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/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/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/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- 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/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/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -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 +222 -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/random/string.spec.ts +11 -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/singleton/singleton.spec.ts +49 -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/.oxlintrc.json +0 -5
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { ExpirationManager } from "#Source/timer/index.ts"
|
|
4
|
+
|
|
5
|
+
test("RemainingManager getRemaining only returns seconds for active expirations", () => {
|
|
6
|
+
vi.useFakeTimers()
|
|
7
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
11
|
+
enableRemainingManager: false,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 2_500)
|
|
15
|
+
expirationManager.upsertExpiration("beta", Date.now() - 1)
|
|
16
|
+
expirationManager.upsertExpiration("gamma", Date.now() + 2_500)
|
|
17
|
+
expirationManager.removeExpiration("gamma")
|
|
18
|
+
|
|
19
|
+
expect(expirationManager.remainingManager.getRemaining("alpha")).toBe(3)
|
|
20
|
+
expect(expirationManager.remainingManager.getRemaining("beta")).toBeUndefined()
|
|
21
|
+
expect(expirationManager.remainingManager.getRemaining("gamma")).toBeUndefined()
|
|
22
|
+
|
|
23
|
+
expirationManager.terminate()
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
vi.clearAllTimers()
|
|
27
|
+
vi.useRealTimers()
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test("RemainingManager getRemainingSnapshot only includes active expirations", () => {
|
|
32
|
+
vi.useFakeTimers()
|
|
33
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
37
|
+
enableRemainingManager: false,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_500)
|
|
41
|
+
expirationManager.upsertExpiration("beta", Date.now() - 1)
|
|
42
|
+
expirationManager.upsertExpiration("gamma", Date.now() + 1_500)
|
|
43
|
+
expirationManager.removeExpiration("gamma")
|
|
44
|
+
|
|
45
|
+
expect(expirationManager.remainingManager.getRemainingSnapshot()).toEqual({ alpha: 2 })
|
|
46
|
+
|
|
47
|
+
expirationManager.terminate()
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
vi.clearAllTimers()
|
|
51
|
+
vi.useRealTimers()
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("RemainingManager emit only publishes changed snapshots", () => {
|
|
56
|
+
vi.useFakeTimers()
|
|
57
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
61
|
+
enableRemainingManager: false,
|
|
62
|
+
})
|
|
63
|
+
const remainingSnapshots: Array<{ alpha?: number | undefined }> = []
|
|
64
|
+
|
|
65
|
+
expirationManager.remainingManager.event.subscribe("remaining", (remaining): void => {
|
|
66
|
+
remainingSnapshots.push({ ...remaining })
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 2_500)
|
|
70
|
+
expirationManager.remainingManager.emit()
|
|
71
|
+
vi.advanceTimersByTime(1_000)
|
|
72
|
+
expirationManager.remainingManager.emit()
|
|
73
|
+
|
|
74
|
+
expect(remainingSnapshots).toEqual([{ alpha: 3 }, { alpha: 2 }])
|
|
75
|
+
|
|
76
|
+
expirationManager.terminate()
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
vi.clearAllTimers()
|
|
80
|
+
vi.useRealTimers()
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("RemainingManager start enables interval-driven updates", () => {
|
|
85
|
+
vi.useFakeTimers()
|
|
86
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
90
|
+
enableRemainingManager: false,
|
|
91
|
+
})
|
|
92
|
+
const remainingSnapshots: Array<{ alpha?: number | undefined }> = []
|
|
93
|
+
|
|
94
|
+
expirationManager.remainingManager.event.subscribe("remaining", (remaining): void => {
|
|
95
|
+
remainingSnapshots.push({ ...remaining })
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 2_500)
|
|
99
|
+
vi.advanceTimersByTime(1_000)
|
|
100
|
+
|
|
101
|
+
expect(remainingSnapshots).toEqual([{ alpha: 3 }])
|
|
102
|
+
|
|
103
|
+
expirationManager.remainingManager.start()
|
|
104
|
+
vi.advanceTimersByTime(1_000)
|
|
105
|
+
|
|
106
|
+
expect(remainingSnapshots).toEqual([{ alpha: 3 }, { alpha: 1 }])
|
|
107
|
+
|
|
108
|
+
expirationManager.terminate()
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
vi.clearAllTimers()
|
|
112
|
+
vi.useRealTimers()
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test("RemainingManager stop disables interval-driven updates", () => {
|
|
117
|
+
vi.useFakeTimers()
|
|
118
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
122
|
+
enableRemainingManager: true,
|
|
123
|
+
})
|
|
124
|
+
const remainingSnapshots: Array<{ alpha?: number | undefined }> = []
|
|
125
|
+
|
|
126
|
+
expirationManager.remainingManager.event.subscribe("remaining", (remaining): void => {
|
|
127
|
+
remainingSnapshots.push({ ...remaining })
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 3_500)
|
|
131
|
+
vi.advanceTimersByTime(1_000)
|
|
132
|
+
|
|
133
|
+
expect(remainingSnapshots).toEqual([{ alpha: 4 }, { alpha: 3 }])
|
|
134
|
+
|
|
135
|
+
expirationManager.remainingManager.stop()
|
|
136
|
+
vi.advanceTimersByTime(1_000)
|
|
137
|
+
|
|
138
|
+
expect(remainingSnapshots).toEqual([{ alpha: 4 }, { alpha: 3 }])
|
|
139
|
+
|
|
140
|
+
expirationManager.terminate()
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
vi.clearAllTimers()
|
|
144
|
+
vi.useRealTimers()
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
test("RemainingManager pause suspends interval-driven updates", () => {
|
|
149
|
+
vi.useFakeTimers()
|
|
150
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
151
|
+
|
|
152
|
+
try {
|
|
153
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
154
|
+
enableRemainingManager: true,
|
|
155
|
+
})
|
|
156
|
+
const remainingSnapshots: Array<{ alpha?: number | undefined }> = []
|
|
157
|
+
|
|
158
|
+
expirationManager.remainingManager.event.subscribe("remaining", (remaining): void => {
|
|
159
|
+
remainingSnapshots.push({ ...remaining })
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 3_500)
|
|
163
|
+
vi.advanceTimersByTime(1_000)
|
|
164
|
+
expirationManager.remainingManager.pause()
|
|
165
|
+
vi.advanceTimersByTime(1_000)
|
|
166
|
+
|
|
167
|
+
expect(remainingSnapshots).toEqual([{ alpha: 4 }, { alpha: 3 }])
|
|
168
|
+
|
|
169
|
+
expirationManager.terminate()
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
vi.clearAllTimers()
|
|
173
|
+
vi.useRealTimers()
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
test("RemainingManager resume restarts interval-driven updates after pause", () => {
|
|
178
|
+
vi.useFakeTimers()
|
|
179
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
183
|
+
enableRemainingManager: true,
|
|
184
|
+
})
|
|
185
|
+
const remainingSnapshots: Array<{ alpha?: number | undefined }> = []
|
|
186
|
+
|
|
187
|
+
expirationManager.remainingManager.event.subscribe("remaining", (remaining): void => {
|
|
188
|
+
remainingSnapshots.push({ ...remaining })
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 3_500)
|
|
192
|
+
expirationManager.remainingManager.pause()
|
|
193
|
+
vi.advanceTimersByTime(1_000)
|
|
194
|
+
expirationManager.remainingManager.resume()
|
|
195
|
+
vi.advanceTimersByTime(1_000)
|
|
196
|
+
|
|
197
|
+
expect(remainingSnapshots).toEqual([{ alpha: 4 }, { alpha: 2 }])
|
|
198
|
+
|
|
199
|
+
expirationManager.terminate()
|
|
200
|
+
}
|
|
201
|
+
finally {
|
|
202
|
+
vi.clearAllTimers()
|
|
203
|
+
vi.useRealTimers()
|
|
204
|
+
}
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
test("RemainingManager terminate stops future emissions", () => {
|
|
208
|
+
vi.useFakeTimers()
|
|
209
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
213
|
+
enableRemainingManager: true,
|
|
214
|
+
})
|
|
215
|
+
const remainingSnapshots: Array<{ alpha?: number | undefined }> = []
|
|
216
|
+
|
|
217
|
+
expirationManager.remainingManager.event.subscribe("remaining", (remaining): void => {
|
|
218
|
+
remainingSnapshots.push({ ...remaining })
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 3_500)
|
|
222
|
+
expirationManager.remainingManager.terminate()
|
|
223
|
+
vi.advanceTimersByTime(2_000)
|
|
224
|
+
expirationManager.remainingManager.emit()
|
|
225
|
+
|
|
226
|
+
expect(remainingSnapshots).toEqual([{ alpha: 4 }])
|
|
227
|
+
|
|
228
|
+
expirationManager.terminate()
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
vi.clearAllTimers()
|
|
232
|
+
vi.useRealTimers()
|
|
233
|
+
}
|
|
234
|
+
})
|
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
|
-
})
|