@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,464 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { ExpirationManager } from "#Source/timer/index.ts"
|
|
4
|
+
|
|
5
|
+
test("ExpirationManager getNow delegates to the configured clock", () => {
|
|
6
|
+
let now = 100
|
|
7
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
8
|
+
enableRemainingManager: false,
|
|
9
|
+
clock: {
|
|
10
|
+
now: (): number => now,
|
|
11
|
+
},
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
expect(expirationManager.getNow()).toBe(100)
|
|
15
|
+
|
|
16
|
+
now = 250
|
|
17
|
+
|
|
18
|
+
expect(expirationManager.getNow()).toBe(250)
|
|
19
|
+
|
|
20
|
+
expirationManager.terminate()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("ExpirationManager hasExpiration reports retained active, expired, and removed entries", () => {
|
|
24
|
+
vi.useFakeTimers()
|
|
25
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma" | "missing">({
|
|
29
|
+
enableRemainingManager: false,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
33
|
+
expirationManager.upsertExpiration("beta", Date.now() - 1)
|
|
34
|
+
expirationManager.upsertExpiration("gamma", Date.now() + 1_000)
|
|
35
|
+
expirationManager.removeExpiration("gamma")
|
|
36
|
+
|
|
37
|
+
expect(expirationManager.hasExpiration("alpha")).toBe(true)
|
|
38
|
+
expect(expirationManager.hasExpiration("beta")).toBe(true)
|
|
39
|
+
expect(expirationManager.hasExpiration("gamma")).toBe(true)
|
|
40
|
+
expect(expirationManager.hasExpiration("missing")).toBe(false)
|
|
41
|
+
|
|
42
|
+
expirationManager.terminate()
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
vi.clearAllTimers()
|
|
46
|
+
vi.useRealTimers()
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("ExpirationManager getExpirationState returns a cloned state snapshot", () => {
|
|
51
|
+
vi.useFakeTimers()
|
|
52
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const expirationManager = new ExpirationManager<"alpha" | "missing">({
|
|
56
|
+
enableRemainingManager: false,
|
|
57
|
+
})
|
|
58
|
+
const endAt = Date.now() + 1_000
|
|
59
|
+
|
|
60
|
+
expirationManager.upsertExpiration("alpha", endAt)
|
|
61
|
+
|
|
62
|
+
const expirationState = expirationManager.getExpirationState("alpha")
|
|
63
|
+
|
|
64
|
+
expect(expirationState).toEqual({
|
|
65
|
+
name: "alpha",
|
|
66
|
+
endAt,
|
|
67
|
+
state: "active",
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (expirationState !== undefined) {
|
|
71
|
+
expirationState.state = "removed"
|
|
72
|
+
expirationState.endAt = 0
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
expect(expirationManager.getExpirationState("alpha")).toEqual({
|
|
76
|
+
name: "alpha",
|
|
77
|
+
endAt,
|
|
78
|
+
state: "active",
|
|
79
|
+
})
|
|
80
|
+
expect(expirationManager.getExpirationState("missing")).toBeUndefined()
|
|
81
|
+
|
|
82
|
+
expirationManager.terminate()
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
vi.clearAllTimers()
|
|
86
|
+
vi.useRealTimers()
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("ExpirationManager getExpirationSnapshot only includes active entries", () => {
|
|
91
|
+
vi.useFakeTimers()
|
|
92
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
96
|
+
enableRemainingManager: false,
|
|
97
|
+
})
|
|
98
|
+
const alphaEndAt = Date.now() + 1_000
|
|
99
|
+
|
|
100
|
+
expirationManager.upsertExpiration("alpha", alphaEndAt)
|
|
101
|
+
expirationManager.upsertExpiration("beta", Date.now() - 1)
|
|
102
|
+
expirationManager.upsertExpiration("gamma", Date.now() + 1_000)
|
|
103
|
+
expirationManager.removeExpiration("gamma")
|
|
104
|
+
|
|
105
|
+
expect(expirationManager.getExpirationSnapshot()).toEqual({
|
|
106
|
+
alpha: alphaEndAt,
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
expirationManager.terminate()
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
vi.clearAllTimers()
|
|
113
|
+
vi.useRealTimers()
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
test("ExpirationManager upsertExpiration stores state changes and rejects invalid timestamps", () => {
|
|
118
|
+
vi.useFakeTimers()
|
|
119
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
123
|
+
enableRemainingManager: false,
|
|
124
|
+
})
|
|
125
|
+
const expirationStates: Array<Record<string, unknown>> = []
|
|
126
|
+
const activeEndAt = Date.now() + 2_000
|
|
127
|
+
const expiredEndAt = Date.now() - 1
|
|
128
|
+
|
|
129
|
+
expirationManager.event.subscribe("expirationState", (expirationState): void => {
|
|
130
|
+
expirationStates.push(structuredClone(expirationState))
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
expirationManager.upsertExpiration("alpha", activeEndAt)
|
|
134
|
+
expirationManager.upsertExpiration("alpha", activeEndAt)
|
|
135
|
+
expirationManager.upsertExpiration("alpha", expiredEndAt)
|
|
136
|
+
|
|
137
|
+
expect(expirationStates).toEqual([
|
|
138
|
+
{
|
|
139
|
+
alpha: {
|
|
140
|
+
name: "alpha",
|
|
141
|
+
endAt: activeEndAt,
|
|
142
|
+
state: "active",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
alpha: {
|
|
147
|
+
name: "alpha",
|
|
148
|
+
endAt: expiredEndAt,
|
|
149
|
+
state: "expired",
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
])
|
|
153
|
+
|
|
154
|
+
expect(() => expirationManager.upsertExpiration("alpha", Number.NaN)).toThrow(RangeError)
|
|
155
|
+
|
|
156
|
+
expirationManager.terminate()
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
vi.clearAllTimers()
|
|
160
|
+
vi.useRealTimers()
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("ExpirationManager updateExpirationBatch applies partial updates and active-only removals", () => {
|
|
165
|
+
vi.useFakeTimers()
|
|
166
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
170
|
+
enableRemainingManager: false,
|
|
171
|
+
})
|
|
172
|
+
const alphaEndAt = Date.now() + 1_000
|
|
173
|
+
const betaEndAt = Date.now() - 1
|
|
174
|
+
const gammaEndAt = Date.now() + 2_000
|
|
175
|
+
|
|
176
|
+
expirationManager.updateExpirationBatch({
|
|
177
|
+
alpha: alphaEndAt,
|
|
178
|
+
beta: betaEndAt,
|
|
179
|
+
})
|
|
180
|
+
expirationManager.updateExpirationBatch({
|
|
181
|
+
alpha: undefined,
|
|
182
|
+
beta: undefined,
|
|
183
|
+
gamma: gammaEndAt,
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
expect(expirationManager.getExpirationState("alpha")).toEqual({
|
|
187
|
+
name: "alpha",
|
|
188
|
+
endAt: alphaEndAt,
|
|
189
|
+
state: "removed",
|
|
190
|
+
})
|
|
191
|
+
expect(expirationManager.getExpirationState("beta")).toEqual({
|
|
192
|
+
name: "beta",
|
|
193
|
+
endAt: betaEndAt,
|
|
194
|
+
state: "expired",
|
|
195
|
+
})
|
|
196
|
+
expect(expirationManager.getExpirationState("gamma")).toEqual({
|
|
197
|
+
name: "gamma",
|
|
198
|
+
endAt: gammaEndAt,
|
|
199
|
+
state: "active",
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
expirationManager.terminate()
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
vi.clearAllTimers()
|
|
206
|
+
vi.useRealTimers()
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
test("ExpirationManager removeExpiration only marks active entries as removed", () => {
|
|
211
|
+
vi.useFakeTimers()
|
|
212
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
216
|
+
enableRemainingManager: false,
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
220
|
+
expirationManager.upsertExpiration("beta", Date.now() - 1)
|
|
221
|
+
expirationManager.upsertExpiration("gamma", Date.now() + 1_000)
|
|
222
|
+
expirationManager.removeExpiration("gamma")
|
|
223
|
+
expirationManager.removeExpiration("alpha")
|
|
224
|
+
expirationManager.removeExpiration("beta")
|
|
225
|
+
|
|
226
|
+
expect(expirationManager.getExpirationState("alpha")?.state).toBe("removed")
|
|
227
|
+
expect(expirationManager.getExpirationState("beta")?.state).toBe("expired")
|
|
228
|
+
expect(expirationManager.getExpirationState("gamma")?.state).toBe("removed")
|
|
229
|
+
|
|
230
|
+
expirationManager.terminate()
|
|
231
|
+
}
|
|
232
|
+
finally {
|
|
233
|
+
vi.clearAllTimers()
|
|
234
|
+
vi.useRealTimers()
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test("ExpirationManager clearExpiredExpirations removes expired entries and returns the count", () => {
|
|
239
|
+
vi.useFakeTimers()
|
|
240
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
244
|
+
enableRemainingManager: false,
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
expirationManager.upsertExpiration("alpha", Date.now() - 1)
|
|
248
|
+
expirationManager.upsertExpiration("beta", Date.now() - 2)
|
|
249
|
+
expirationManager.upsertExpiration("gamma", Date.now() + 1_000)
|
|
250
|
+
|
|
251
|
+
expect(expirationManager.clearExpiredExpirations()).toBe(2)
|
|
252
|
+
expect(expirationManager.hasExpiration("alpha")).toBe(false)
|
|
253
|
+
expect(expirationManager.hasExpiration("beta")).toBe(false)
|
|
254
|
+
expect(expirationManager.hasExpiration("gamma")).toBe(true)
|
|
255
|
+
|
|
256
|
+
expirationManager.terminate()
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
vi.clearAllTimers()
|
|
260
|
+
vi.useRealTimers()
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test("ExpirationManager clearRemovedExpirations removes removed entries and returns the count", () => {
|
|
265
|
+
vi.useFakeTimers()
|
|
266
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta" | "gamma">({
|
|
270
|
+
enableRemainingManager: false,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
274
|
+
expirationManager.upsertExpiration("beta", Date.now() + 2_000)
|
|
275
|
+
expirationManager.upsertExpiration("gamma", Date.now() - 1)
|
|
276
|
+
expirationManager.removeExpiration("alpha")
|
|
277
|
+
expirationManager.removeExpiration("beta")
|
|
278
|
+
|
|
279
|
+
expect(expirationManager.clearRemovedExpirations()).toBe(2)
|
|
280
|
+
expect(expirationManager.hasExpiration("alpha")).toBe(false)
|
|
281
|
+
expect(expirationManager.hasExpiration("beta")).toBe(false)
|
|
282
|
+
expect(expirationManager.getExpirationState("gamma")?.state).toBe("expired")
|
|
283
|
+
|
|
284
|
+
expirationManager.terminate()
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
vi.clearAllTimers()
|
|
288
|
+
vi.useRealTimers()
|
|
289
|
+
}
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
test("ExpirationManager pause blocks queued emissions and scheduler ticks", () => {
|
|
293
|
+
vi.useFakeTimers()
|
|
294
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
298
|
+
enableRemainingManager: false,
|
|
299
|
+
})
|
|
300
|
+
const expirationStates: Array<Record<string, unknown>> = []
|
|
301
|
+
|
|
302
|
+
expirationManager.event.subscribe("expirationState", (expirationState): void => {
|
|
303
|
+
expirationStates.push(structuredClone(expirationState))
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
307
|
+
expirationManager.pause()
|
|
308
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 3_000)
|
|
309
|
+
vi.advanceTimersByTime(4_000)
|
|
310
|
+
|
|
311
|
+
expect(expirationStates).toHaveLength(1)
|
|
312
|
+
expect(expirationManager.getExpirationState("alpha")?.state).toBe("active")
|
|
313
|
+
|
|
314
|
+
expirationManager.terminate()
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
vi.clearAllTimers()
|
|
318
|
+
vi.useRealTimers()
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test("ExpirationManager resume flushes the latest paused snapshot and expires overdue entries", () => {
|
|
323
|
+
vi.useFakeTimers()
|
|
324
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
325
|
+
|
|
326
|
+
try {
|
|
327
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta">({
|
|
328
|
+
enableRemainingManager: false,
|
|
329
|
+
})
|
|
330
|
+
const expirationStates: Array<Record<string, unknown>> = []
|
|
331
|
+
const alphaInitialEndAt = Date.now() + 2_000
|
|
332
|
+
const alphaUpdatedEndAt = Date.now() + 5_000
|
|
333
|
+
const betaEndAt = Date.now() + 1_000
|
|
334
|
+
|
|
335
|
+
expirationManager.event.subscribe("expirationState", (expirationState): void => {
|
|
336
|
+
expirationStates.push(structuredClone(expirationState))
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
expirationManager.upsertExpiration("alpha", alphaInitialEndAt)
|
|
340
|
+
expirationManager.pause()
|
|
341
|
+
expirationManager.updateExpirationBatch({
|
|
342
|
+
alpha: alphaUpdatedEndAt,
|
|
343
|
+
beta: betaEndAt,
|
|
344
|
+
})
|
|
345
|
+
expirationManager.updateExpirationBatch({
|
|
346
|
+
beta: undefined,
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
vi.advanceTimersByTime(6_000)
|
|
350
|
+
expirationManager.resume()
|
|
351
|
+
|
|
352
|
+
expect(expirationStates).toEqual([
|
|
353
|
+
{
|
|
354
|
+
alpha: {
|
|
355
|
+
name: "alpha",
|
|
356
|
+
endAt: alphaInitialEndAt,
|
|
357
|
+
state: "active",
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
alpha: {
|
|
362
|
+
name: "alpha",
|
|
363
|
+
endAt: alphaUpdatedEndAt,
|
|
364
|
+
state: "expired",
|
|
365
|
+
},
|
|
366
|
+
beta: {
|
|
367
|
+
name: "beta",
|
|
368
|
+
endAt: betaEndAt,
|
|
369
|
+
state: "removed",
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
])
|
|
373
|
+
|
|
374
|
+
expirationManager.terminate()
|
|
375
|
+
}
|
|
376
|
+
finally {
|
|
377
|
+
vi.clearAllTimers()
|
|
378
|
+
vi.useRealTimers()
|
|
379
|
+
}
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
test("ExpirationManager terminate stops future scheduling and ignores later writes", () => {
|
|
383
|
+
vi.useFakeTimers()
|
|
384
|
+
vi.setSystemTime(new Date(2_026, 0, 1, 0, 0, 0))
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
const expirationManager = new ExpirationManager<"alpha" | "beta">({
|
|
388
|
+
enableRemainingManager: false,
|
|
389
|
+
})
|
|
390
|
+
const expirationStates: Array<Record<string, unknown>> = []
|
|
391
|
+
|
|
392
|
+
expirationManager.event.subscribe("expirationState", (expirationState): void => {
|
|
393
|
+
expirationStates.push(structuredClone(expirationState))
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
397
|
+
expirationManager.terminate()
|
|
398
|
+
vi.advanceTimersByTime(2_000)
|
|
399
|
+
expirationManager.upsertExpiration("beta", Date.now() + 1_000)
|
|
400
|
+
|
|
401
|
+
expect(expirationStates).toHaveLength(1)
|
|
402
|
+
expect(expirationManager.hasExpiration("beta")).toBe(false)
|
|
403
|
+
}
|
|
404
|
+
finally {
|
|
405
|
+
vi.clearAllTimers()
|
|
406
|
+
vi.useRealTimers()
|
|
407
|
+
}
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
test("ExpirationManager pause during expirationState does not interrupt the current emission", () => {
|
|
411
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
412
|
+
enableRemainingManager: false,
|
|
413
|
+
})
|
|
414
|
+
const eventOrder: string[] = []
|
|
415
|
+
|
|
416
|
+
expirationManager.event.subscribe("expirationState", () => {
|
|
417
|
+
eventOrder.push("state")
|
|
418
|
+
expirationManager.pause()
|
|
419
|
+
})
|
|
420
|
+
|
|
421
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
422
|
+
|
|
423
|
+
expect(eventOrder).toEqual(["state"])
|
|
424
|
+
|
|
425
|
+
expirationManager.terminate()
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
test("ExpirationManager resume does not replay an already emitted state snapshot", () => {
|
|
429
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
430
|
+
enableRemainingManager: false,
|
|
431
|
+
})
|
|
432
|
+
const eventOrder: string[] = []
|
|
433
|
+
|
|
434
|
+
expirationManager.event.subscribe("expirationState", () => {
|
|
435
|
+
eventOrder.push("state")
|
|
436
|
+
|
|
437
|
+
if (eventOrder.length === 1) {
|
|
438
|
+
expirationManager.pause()
|
|
439
|
+
}
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
443
|
+
expirationManager.resume()
|
|
444
|
+
|
|
445
|
+
expect(eventOrder).toEqual(["state"])
|
|
446
|
+
|
|
447
|
+
expirationManager.terminate()
|
|
448
|
+
})
|
|
449
|
+
|
|
450
|
+
test("ExpirationManager terminate during expirationState does not throw", () => {
|
|
451
|
+
const expirationManager = new ExpirationManager<"alpha">({
|
|
452
|
+
enableRemainingManager: false,
|
|
453
|
+
})
|
|
454
|
+
const eventOrder: string[] = []
|
|
455
|
+
|
|
456
|
+
expirationManager.event.subscribe("expirationState", () => {
|
|
457
|
+
eventOrder.push("state")
|
|
458
|
+
expirationManager.terminate()
|
|
459
|
+
})
|
|
460
|
+
|
|
461
|
+
expirationManager.upsertExpiration("alpha", Date.now() + 1_000)
|
|
462
|
+
|
|
463
|
+
expect(eventOrder).toEqual(["state"])
|
|
464
|
+
})
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { MinHeap } from "#Source/timer/index.ts"
|
|
4
|
+
|
|
5
|
+
test("MinHeap size returns the current node count", () => {
|
|
6
|
+
const heap = new MinHeap<"alpha" | "beta">()
|
|
7
|
+
|
|
8
|
+
expect(heap.size()).toBe(0)
|
|
9
|
+
|
|
10
|
+
heap.push({ name: "alpha", endAt: 3 })
|
|
11
|
+
heap.push({ name: "beta", endAt: 1 })
|
|
12
|
+
|
|
13
|
+
expect(heap.size()).toBe(2)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test("MinHeap peek returns the smallest endAt without removing it", () => {
|
|
17
|
+
const heap = new MinHeap<"alpha" | "beta" | "gamma">()
|
|
18
|
+
|
|
19
|
+
expect(heap.peek()).toBeUndefined()
|
|
20
|
+
|
|
21
|
+
heap.push({ name: "alpha", endAt: 5 })
|
|
22
|
+
heap.push({ name: "beta", endAt: 2 })
|
|
23
|
+
heap.push({ name: "gamma", endAt: 4 })
|
|
24
|
+
|
|
25
|
+
expect(heap.peek()).toEqual({ name: "beta", endAt: 2 })
|
|
26
|
+
expect(heap.size()).toBe(3)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("MinHeap push maintains heap order for later reads", () => {
|
|
30
|
+
const heap = new MinHeap<"alpha" | "beta" | "gamma">()
|
|
31
|
+
|
|
32
|
+
heap.push({ name: "alpha", endAt: 7 })
|
|
33
|
+
heap.push({ name: "beta", endAt: 3 })
|
|
34
|
+
heap.push({ name: "gamma", endAt: 5 })
|
|
35
|
+
|
|
36
|
+
expect(heap.peek()).toEqual({ name: "beta", endAt: 3 })
|
|
37
|
+
expect(heap.pop()).toEqual({ name: "beta", endAt: 3 })
|
|
38
|
+
expect(heap.pop()).toEqual({ name: "gamma", endAt: 5 })
|
|
39
|
+
expect(heap.pop()).toEqual({ name: "alpha", endAt: 7 })
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("MinHeap remove deletes the named node and keeps the remaining order valid", () => {
|
|
43
|
+
const heap = new MinHeap<"alpha" | "beta" | "gamma" | "delta" | "missing">()
|
|
44
|
+
|
|
45
|
+
heap.push({ name: "alpha", endAt: 7 })
|
|
46
|
+
heap.push({ name: "beta", endAt: 3 })
|
|
47
|
+
heap.push({ name: "gamma", endAt: 5 })
|
|
48
|
+
heap.push({ name: "delta", endAt: 1 })
|
|
49
|
+
|
|
50
|
+
heap.remove("beta")
|
|
51
|
+
heap.remove("missing")
|
|
52
|
+
|
|
53
|
+
expect(heap.pop()).toEqual({ name: "delta", endAt: 1 })
|
|
54
|
+
expect(heap.pop()).toEqual({ name: "gamma", endAt: 5 })
|
|
55
|
+
expect(heap.pop()).toEqual({ name: "alpha", endAt: 7 })
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("MinHeap pop removes and returns nodes in ascending endAt order", () => {
|
|
59
|
+
const heap = new MinHeap<"alpha" | "beta" | "gamma">()
|
|
60
|
+
|
|
61
|
+
expect(heap.pop()).toBeUndefined()
|
|
62
|
+
|
|
63
|
+
heap.push({ name: "alpha", endAt: 4 })
|
|
64
|
+
heap.push({ name: "beta", endAt: 2 })
|
|
65
|
+
heap.push({ name: "gamma", endAt: 6 })
|
|
66
|
+
|
|
67
|
+
expect(heap.pop()).toEqual({ name: "beta", endAt: 2 })
|
|
68
|
+
expect(heap.pop()).toEqual({ name: "alpha", endAt: 4 })
|
|
69
|
+
expect(heap.pop()).toEqual({ name: "gamma", endAt: 6 })
|
|
70
|
+
expect(heap.pop()).toBeUndefined()
|
|
71
|
+
})
|