@planet-matrix/mobius-model 0.4.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.
Files changed (179) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +134 -21
  3. package/dist/index.js +45 -4
  4. package/dist/index.js.map +186 -11
  5. package/oxlint.config.ts +6 -0
  6. package/package.json +16 -10
  7. package/src/abort/README.md +92 -0
  8. package/src/abort/abort-manager.ts +278 -0
  9. package/src/abort/abort-signal-listener-manager.ts +81 -0
  10. package/src/abort/index.ts +2 -0
  11. package/src/basic/README.md +69 -117
  12. package/src/basic/enhance.ts +10 -0
  13. package/src/basic/function.ts +81 -62
  14. package/src/basic/index.ts +2 -0
  15. package/src/basic/is.ts +152 -71
  16. package/src/basic/object.ts +82 -0
  17. package/src/basic/promise.ts +29 -8
  18. package/src/basic/string.ts +2 -33
  19. package/src/color/README.md +105 -0
  20. package/src/color/index.ts +3 -0
  21. package/src/color/internal.ts +42 -0
  22. package/src/color/rgb/analyze.ts +236 -0
  23. package/src/color/rgb/construct.ts +130 -0
  24. package/src/color/rgb/convert.ts +227 -0
  25. package/src/color/rgb/derive.ts +303 -0
  26. package/src/color/rgb/index.ts +6 -0
  27. package/src/color/rgb/internal.ts +208 -0
  28. package/src/color/rgb/parse.ts +302 -0
  29. package/src/color/rgb/serialize.ts +144 -0
  30. package/src/color/types.ts +57 -0
  31. package/src/color/xyz/analyze.ts +80 -0
  32. package/src/color/xyz/construct.ts +19 -0
  33. package/src/color/xyz/convert.ts +71 -0
  34. package/src/color/xyz/index.ts +3 -0
  35. package/src/color/xyz/internal.ts +23 -0
  36. package/src/css/README.md +93 -0
  37. package/src/css/class.ts +559 -0
  38. package/src/css/index.ts +1 -0
  39. package/src/encoding/README.md +92 -0
  40. package/src/encoding/base64.ts +107 -0
  41. package/src/encoding/index.ts +1 -0
  42. package/src/environment/README.md +97 -0
  43. package/src/environment/basic.ts +26 -0
  44. package/src/environment/device.ts +311 -0
  45. package/src/environment/feature.ts +285 -0
  46. package/src/environment/geo.ts +337 -0
  47. package/src/environment/index.ts +7 -0
  48. package/src/environment/runtime.ts +400 -0
  49. package/src/environment/snapshot.ts +60 -0
  50. package/src/environment/variable.ts +239 -0
  51. package/src/event/README.md +90 -0
  52. package/src/event/class-event-proxy.ts +228 -0
  53. package/src/event/common.ts +19 -0
  54. package/src/event/event-manager.ts +203 -0
  55. package/src/event/index.ts +4 -0
  56. package/src/event/instance-event-proxy.ts +186 -0
  57. package/src/event/internal.ts +24 -0
  58. package/src/exception/README.md +96 -0
  59. package/src/exception/browser.ts +219 -0
  60. package/src/exception/index.ts +4 -0
  61. package/src/exception/nodejs.ts +169 -0
  62. package/src/exception/normalize.ts +106 -0
  63. package/src/exception/types.ts +99 -0
  64. package/src/identifier/README.md +92 -0
  65. package/src/identifier/id.ts +119 -0
  66. package/src/identifier/index.ts +2 -0
  67. package/src/identifier/uuid.ts +187 -0
  68. package/src/index.ts +18 -1
  69. package/src/log/README.md +79 -0
  70. package/src/log/index.ts +5 -0
  71. package/src/log/log-emitter.ts +72 -0
  72. package/src/log/log-record.ts +10 -0
  73. package/src/log/log-scheduler.ts +74 -0
  74. package/src/log/log-type.ts +8 -0
  75. package/src/log/logger.ts +543 -0
  76. package/src/orchestration/README.md +89 -0
  77. package/src/orchestration/coordination/barrier.ts +214 -0
  78. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  79. package/src/orchestration/coordination/errors.ts +98 -0
  80. package/src/orchestration/coordination/index.ts +16 -0
  81. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  82. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  83. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  84. package/src/orchestration/coordination/mutex.ts +257 -0
  85. package/src/orchestration/coordination/permit.ts +127 -0
  86. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  87. package/src/orchestration/coordination/semaphore.ts +280 -0
  88. package/src/orchestration/index.ts +1 -0
  89. package/src/random/README.md +78 -0
  90. package/src/random/index.ts +1 -0
  91. package/src/random/string.ts +35 -0
  92. package/src/reactor/README.md +4 -0
  93. package/src/reactor/reactor-core/primitive.ts +9 -9
  94. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  95. package/src/singleton/README.md +79 -0
  96. package/src/singleton/factory.ts +55 -0
  97. package/src/singleton/index.ts +2 -0
  98. package/src/singleton/manager.ts +204 -0
  99. package/src/storage/README.md +107 -0
  100. package/src/storage/index.ts +1 -0
  101. package/src/storage/table.ts +449 -0
  102. package/src/timer/README.md +86 -0
  103. package/src/timer/expiration/expiration-manager.ts +594 -0
  104. package/src/timer/expiration/index.ts +3 -0
  105. package/src/timer/expiration/min-heap.ts +208 -0
  106. package/src/timer/expiration/remaining-manager.ts +241 -0
  107. package/src/timer/index.ts +1 -0
  108. package/src/type/README.md +54 -307
  109. package/src/type/class.ts +2 -2
  110. package/src/type/index.ts +14 -14
  111. package/src/type/is.ts +265 -2
  112. package/src/type/object.ts +37 -0
  113. package/src/type/string.ts +7 -2
  114. package/src/type/tuple.ts +6 -6
  115. package/src/type/union.ts +16 -0
  116. package/src/web/README.md +77 -0
  117. package/src/web/capture.ts +35 -0
  118. package/src/web/clipboard.ts +97 -0
  119. package/src/web/dom.ts +117 -0
  120. package/src/web/download.ts +16 -0
  121. package/src/web/event.ts +46 -0
  122. package/src/web/index.ts +10 -0
  123. package/src/web/local-storage.ts +113 -0
  124. package/src/web/location.ts +28 -0
  125. package/src/web/permission.ts +172 -0
  126. package/src/web/script-loader.ts +432 -0
  127. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  128. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  129. package/tests/unit/basic/array.spec.ts +1 -1
  130. package/tests/unit/basic/object.spec.ts +32 -1
  131. package/tests/unit/basic/stream.spec.ts +1 -1
  132. package/tests/unit/basic/string.spec.ts +0 -9
  133. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  134. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  135. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  136. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  137. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  138. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  139. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  140. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  141. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  142. package/tests/unit/css/class.spec.ts +157 -0
  143. package/tests/unit/encoding/base64.spec.ts +40 -0
  144. package/tests/unit/environment/basic.spec.ts +20 -0
  145. package/tests/unit/environment/device.spec.ts +146 -0
  146. package/tests/unit/environment/feature.spec.ts +388 -0
  147. package/tests/unit/environment/geo.spec.ts +111 -0
  148. package/tests/unit/environment/runtime.spec.ts +364 -0
  149. package/tests/unit/environment/snapshot.spec.ts +4 -0
  150. package/tests/unit/environment/variable.spec.ts +190 -0
  151. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  152. package/tests/unit/event/event-manager.spec.ts +246 -0
  153. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  154. package/tests/unit/exception/browser.spec.ts +213 -0
  155. package/tests/unit/exception/nodejs.spec.ts +144 -0
  156. package/tests/unit/exception/normalize.spec.ts +57 -0
  157. package/tests/unit/identifier/id.spec.ts +71 -0
  158. package/tests/unit/identifier/uuid.spec.ts +85 -0
  159. package/tests/unit/log/log-emitter.spec.ts +33 -0
  160. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  161. package/tests/unit/log/log-type.spec.ts +7 -0
  162. package/tests/unit/log/logger.spec.ts +222 -0
  163. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  164. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  165. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  166. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  167. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  168. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  169. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  170. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  171. package/tests/unit/random/string.spec.ts +11 -0
  172. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  173. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  174. package/tests/unit/singleton/singleton.spec.ts +49 -0
  175. package/tests/unit/storage/table.spec.ts +620 -0
  176. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  177. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  178. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  179. package/.oxlintrc.json +0 -5
@@ -0,0 +1,594 @@
1
+ import type { BaseEvents } from "#Source/event/index.ts"
2
+ import { EventManager } from "#Source/event/index.ts"
3
+
4
+ import { MinHeap } from "./min-heap.ts"
5
+ import { RemainingManager } from "./remaining-manager.ts"
6
+
7
+ const MAX_SCHEDULER_DELAY = 2_147_483_647
8
+
9
+ /**
10
+ * 提供当前墙钟时间(wall-clock time)的毫秒值。
11
+ */
12
+ export interface ExpirationManagerClock {
13
+ /**
14
+ * 返回当前时间戳,单位为毫秒。
15
+ */
16
+ now(): number
17
+ }
18
+
19
+ /**
20
+ * 表示一个以毫秒为单位的过期时点。
21
+ */
22
+ export type ExpirationEndAt = number
23
+
24
+ /**
25
+ * 描述一组按过期名称索引的过期时点字典。
26
+ */
27
+ export type ExpirationDict<ExpirationName extends string> = {
28
+ [K in ExpirationName]?: ExpirationEndAt | undefined
29
+ }
30
+
31
+ /**
32
+ * 描述 `ExpirationManager` 对外发出的事件表。
33
+ *
34
+ * 当管理器处于暂停状态时,事件不会立即发出,而是延后并折叠为恢复时的最新快照。
35
+ */
36
+ export interface ExpirationManagerEvents<ExpirationName extends string> extends BaseEvents {
37
+ /**
38
+ * 在保留的过期状态快照发生可观察变化时发出最新结果。
39
+ */
40
+ expirationState: (expirationState: ExpirationState<ExpirationName>) => void
41
+ }
42
+
43
+ /**
44
+ * Hold one coalesced emission payload derived from current manager state.
45
+ */
46
+ interface ExpirationEmission<ExpirationName extends string> {
47
+ expirationState: ExpirationState<ExpirationName>
48
+ }
49
+
50
+ /**
51
+ * 描述一条被管理器保留的过期状态记录。
52
+ */
53
+ export interface ExpirationStateItem<ExpirationName extends string> {
54
+ /**
55
+ * 该过期项的名称。
56
+ */
57
+ name: ExpirationName
58
+
59
+ /**
60
+ * 该过期项对应的绝对到期时点,单位为毫秒。
61
+ */
62
+ endAt: ExpirationEndAt
63
+
64
+ /**
65
+ * 该过期项当前的状态。
66
+ */
67
+ state: "active" | "expired" | "removed"
68
+ }
69
+
70
+ /**
71
+ * 描述一组按过期名称索引的保留状态字典。
72
+ */
73
+ export type ExpirationState<ExpirationName extends string> = {
74
+ [K in ExpirationName]?: ExpirationStateItem<ExpirationName> | undefined
75
+ }
76
+
77
+ /**
78
+ * 描述创建 `ExpirationManager` 时可用的配置项。
79
+ */
80
+ export interface ExpirationManagerOptions<ExpirationName extends string> {
81
+ /**
82
+ * 用于初始化管理器的过期项集合。
83
+ */
84
+ initialExpirations?: ExpirationDict<ExpirationName> | undefined
85
+
86
+ /**
87
+ * 控制是否同时启用派生剩余时间管理器的周期性检查。
88
+ */
89
+ enableRemainingManager?: boolean | undefined
90
+
91
+ /**
92
+ * 提供自定义时间来源;未提供时默认使用 `Date.now()`。
93
+ */
94
+ clock?: ExpirationManagerClock | undefined
95
+ }
96
+
97
+ /**
98
+ * 管理具名过期项的状态、事件与定时调度。
99
+ *
100
+ * `ExpirationManager` owns the source-of-truth expiration state dictionary and uses a `MinHeap`
101
+ * to keep the nearest `endAt` value at the top. That makes rescheduling efficient because
102
+ * the manager only needs to inspect the heap root to determine the next wake-up time,
103
+ * instead of scanning all expiration entries on every update.
104
+ *
105
+ * When the next expiration is farther away than the platform timer limit, scheduling is
106
+ * performed in bounded segments until the target timestamp becomes reachable.
107
+ *
108
+ * Pausing does not freeze time or reject writes. State updates may continue while paused,
109
+ * but expiration events and derived remaining events are deferred. Multiple paused-time
110
+ * state changes are coalesced into the latest snapshot and flushed after `resume()`.
111
+ *
112
+ * Remaining time is treated as derived state rather than primary state. The companion
113
+ * `RemainingManager` reads snapshots from `ExpirationManager`, computes second-based
114
+ * remaining values on demand, and can periodically emit those derived values without
115
+ * duplicating expiration ownership.
116
+ */
117
+ export class ExpirationManager<ExpirationName extends string = string> {
118
+ private expirationState: ExpirationState<ExpirationName>
119
+
120
+ private clock: ExpirationManagerClock
121
+ private heap: MinHeap<ExpirationName>
122
+ private schedulerTimer: ReturnType<typeof setTimeout> | null
123
+ private paused: boolean
124
+ private terminated: boolean
125
+ private expirationEmissionQueue: Array<ExpirationEmission<ExpirationName>>
126
+ private flushingExpirationEmissionQueue: boolean
127
+
128
+ readonly event: EventManager<ExpirationManagerEvents<ExpirationName>>
129
+ readonly remainingManager: RemainingManager<ExpirationName>
130
+
131
+ constructor(options: ExpirationManagerOptions<ExpirationName>) {
132
+ const { initialExpirations, enableRemainingManager, clock } = options
133
+ const initialExpirationSource: ExpirationDict<ExpirationName> = initialExpirations ?? {}
134
+
135
+ this.expirationState = {}
136
+
137
+ this.clock = clock ?? {
138
+ now: (): number => Date.now()
139
+ }
140
+ this.heap = new MinHeap()
141
+ this.schedulerTimer = null
142
+ this.paused = false
143
+ this.terminated = false
144
+ this.expirationEmissionQueue = []
145
+ this.flushingExpirationEmissionQueue = false
146
+
147
+ this.event = new EventManager()
148
+ this.remainingManager = new RemainingManager<ExpirationName>({
149
+ expirationManager: this,
150
+ enabled: enableRemainingManager
151
+ })
152
+
153
+ this.updateExpirationBatch(initialExpirationSource)
154
+ }
155
+
156
+ /**
157
+ * 返回当前管理器使用的墙钟时间。
158
+ */
159
+ getNow(): number {
160
+ return this.clock.now()
161
+ }
162
+
163
+ /**
164
+ * 判断某个具名过期项当前是否仍有保留状态。
165
+ *
166
+ * 只要该名称当前处于 `active`、`expired` 或 `removed` 任一保留状态,就会返回 `true`。
167
+ */
168
+ hasExpiration(name: ExpirationName): boolean {
169
+ return this.expirationState[name] !== undefined
170
+ }
171
+
172
+ /**
173
+ * 返回某个具名过期项的状态快照。
174
+ */
175
+ getExpirationState(name: ExpirationName): ExpirationStateItem<ExpirationName> | undefined {
176
+ return structuredClone(this.expirationState[name])
177
+ }
178
+
179
+ /**
180
+ * 返回当前所有活跃过期项的绝对到期时点快照。
181
+ */
182
+ getExpirationSnapshot(): ExpirationDict<ExpirationName> {
183
+ const expirationDict: ExpirationDict<ExpirationName> = {}
184
+
185
+ for (const name of Object.keys(this.expirationState)) {
186
+ // oxlint-disable-next-line no-unsafe-type-assertion
187
+ const expirationName = name as ExpirationName
188
+ const expirationState = this.expirationState[expirationName]
189
+
190
+ if (expirationState === undefined) {
191
+ continue
192
+ }
193
+ if (expirationState.state !== "active") {
194
+ continue
195
+ }
196
+
197
+ expirationDict[expirationName] = expirationState.endAt
198
+ }
199
+
200
+ return expirationDict
201
+ }
202
+
203
+ /**
204
+ * Check whether event emission is currently blocked.
205
+ */
206
+ private isEmissionBlocked(): boolean {
207
+ return this.terminated === true || this.paused === true
208
+ }
209
+
210
+ /**
211
+ * Flush queued state emissions in order until emission becomes blocked again.
212
+ */
213
+ private flushExpirationEmissionQueue(): void {
214
+ if (this.isEmissionBlocked() === true) {
215
+ return
216
+ }
217
+ if (this.flushingExpirationEmissionQueue === true) {
218
+ return
219
+ }
220
+
221
+ this.flushingExpirationEmissionQueue = true
222
+
223
+ try {
224
+ while (true) {
225
+ if (this.isEmissionBlocked() === true) {
226
+ return
227
+ }
228
+
229
+ const emission = this.expirationEmissionQueue.shift()
230
+ if (emission === undefined) {
231
+ return
232
+ }
233
+
234
+ this.event.emit("expirationState", emission.expirationState)
235
+ }
236
+ } finally {
237
+ this.flushingExpirationEmissionQueue = false
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Build the latest event payload from current manager state.
243
+ */
244
+ private createExpirationEmission(): ExpirationEmission<ExpirationName> {
245
+ return {
246
+ expirationState: structuredClone(this.expirationState),
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Replace any queued paused-time emission with the latest coalesced snapshot.
252
+ */
253
+ private replaceQueuedExpirationEmission(): void {
254
+ const emission = this.createExpirationEmission()
255
+ this.expirationEmissionQueue = [emission]
256
+ }
257
+
258
+ /**
259
+ * Queue an emission for the current state.
260
+ *
261
+ * While paused, the queue is collapsed to a single latest snapshot so `resume()`
262
+ * only flushes the final observable state.
263
+ */
264
+ private enqueueExpirationEmission(): void {
265
+ if (this.paused === true) {
266
+ this.replaceQueuedExpirationEmission()
267
+ return
268
+ }
269
+
270
+ const emission = this.createExpirationEmission()
271
+ this.expirationEmissionQueue.push(emission)
272
+ this.flushExpirationEmissionQueue()
273
+ }
274
+
275
+ /**
276
+ * Mark overdue active entries as expired when their stored timestamps are no longer in the future.
277
+ */
278
+ private updateExpired(now: number): boolean {
279
+ let changed = false
280
+
281
+ while (true) {
282
+ const top = this.heap.peek()
283
+ if (top === undefined || top.endAt > now) {
284
+ return changed
285
+ }
286
+
287
+ this.heap.pop()
288
+ const expiration = this.expirationState[top.name]
289
+ if (expiration === undefined) {
290
+ continue
291
+ }
292
+ if (expiration.state === "active" && expiration.endAt === top.endAt) {
293
+ this.expirationState[top.name] = { ...expiration, state: "expired" }
294
+ changed = true
295
+ }
296
+ }
297
+ }
298
+
299
+ private tick(): void {
300
+ if (this.terminated === true || this.paused === true) {
301
+ return
302
+ }
303
+
304
+ const changed = this.updateExpired(this.getNow())
305
+ if (changed === true) {
306
+ this.enqueueExpirationEmission()
307
+ }
308
+
309
+ this.schedule()
310
+ }
311
+
312
+ private schedule(): void {
313
+ if (this.terminated === true || this.paused === true) {
314
+ return
315
+ }
316
+
317
+ if (this.schedulerTimer !== null) {
318
+ clearTimeout(this.schedulerTimer)
319
+ this.schedulerTimer = null
320
+ }
321
+
322
+ const next = this.heap.peek()
323
+ if (next === undefined) {
324
+ return
325
+ }
326
+
327
+ const delay = Math.min(MAX_SCHEDULER_DELAY, Math.max(0, next.endAt - this.getNow()))
328
+ this.schedulerTimer = setTimeout(() => this.tick(), delay)
329
+ }
330
+
331
+ private validateExpirationEndAt(endAt: number): void {
332
+ if (Number.isFinite(endAt) !== true || endAt < 0) {
333
+ throw new RangeError("Expiration endAt must be a finite timestamp greater than or equal to 0.")
334
+ }
335
+ }
336
+
337
+ private internalUpsertExpiration(name: ExpirationName, endAt: number, now: number): boolean {
338
+ this.validateExpirationEndAt(endAt)
339
+
340
+ const oldState = this.expirationState[name]
341
+ const newState = { name, endAt, state: endAt <= now ? "expired" : "active" } as const
342
+
343
+ if (
344
+ oldState !== undefined &&
345
+ oldState.name === newState.name &&
346
+ oldState.endAt === newState.endAt &&
347
+ oldState.state === newState.state
348
+ ) {
349
+ return false
350
+ }
351
+
352
+ this.expirationState[name] = newState
353
+ this.heap.remove(name)
354
+ if (newState.state === "active") {
355
+ this.heap.push({ name, endAt })
356
+ }
357
+
358
+ return true
359
+ }
360
+
361
+ /**
362
+ * 使用毫秒级绝对时点设置或替换一个具名过期项。
363
+ */
364
+ upsertExpiration(name: ExpirationName, endAt: number): void {
365
+ if (this.terminated === true) {
366
+ return
367
+ }
368
+
369
+ const changed = this.internalUpsertExpiration(name, endAt, this.getNow())
370
+ if (changed === true) {
371
+ this.enqueueExpirationEmission()
372
+
373
+ this.schedule()
374
+ }
375
+ }
376
+
377
+ /**
378
+ * 使用毫秒级绝对时点执行一次局部批量更新。
379
+ *
380
+ * 对于 `dict` 中每个被显式提供的键,行为如下:
381
+ * - 数值会把对应过期项设置或替换为给定的绝对 `endAt` 时点。
382
+ * - 显式的 `undefined` 只会在当前状态为 `active` 时把该项标记为 `removed`。
383
+ * - 当前已处于 `expired` 或 `removed` 的项,遇到显式 `undefined` 时保持不变。
384
+ * - `dict` 中未出现的键不会被处理,现有状态保持不变。
385
+ *
386
+ * 当至少有一项真实发生变化时,会在所有显式更新完成后统一发出一次状态变化通知。
387
+ */
388
+ updateExpirationBatch(dict: ExpirationDict<ExpirationName>): void {
389
+ if (this.terminated === true) {
390
+ return
391
+ }
392
+
393
+ for (const name of Object.keys(dict)) {
394
+ // oxlint-disable-next-line no-unsafe-type-assertion
395
+ const expirationName = name as ExpirationName
396
+ const endAt = dict[expirationName]
397
+
398
+ if (endAt === undefined) {
399
+ continue
400
+ }
401
+
402
+ this.validateExpirationEndAt(endAt)
403
+ }
404
+
405
+ let changed = false
406
+ const now = this.getNow()
407
+ for (const name of Object.keys(dict)) {
408
+ // oxlint-disable-next-line no-unsafe-type-assertion
409
+ const expirationName = name as ExpirationName
410
+ const endAt = dict[expirationName]
411
+
412
+ if (endAt === undefined) {
413
+ const expiration = this.expirationState[expirationName]
414
+ if (expiration === undefined) {
415
+ continue
416
+ }
417
+ if (expiration.state === "active") {
418
+ this.expirationState[expirationName] = { ...expiration, state: "removed" }
419
+ this.heap.remove(expirationName)
420
+ changed = true
421
+ }
422
+
423
+ continue
424
+ }
425
+
426
+ changed = this.internalUpsertExpiration(expirationName, endAt, now) || changed
427
+ }
428
+
429
+ if (changed === true) {
430
+ this.enqueueExpirationEmission()
431
+
432
+ this.schedule()
433
+ }
434
+ }
435
+
436
+ /**
437
+ * 将一个当前处于活跃状态的过期项标记为 `removed`。
438
+ *
439
+ * 已经处于 `expired` 或 `removed` 的项不会被再次修改。
440
+ */
441
+ removeExpiration(name: ExpirationName): void {
442
+ if (this.terminated === true) {
443
+ return
444
+ }
445
+
446
+ const expiration = this.expirationState[name]
447
+ if (expiration === undefined) {
448
+ return
449
+ }
450
+ if (expiration.state !== "active") {
451
+ return
452
+ }
453
+
454
+ this.expirationState[name] = { ...expiration, state: "removed" }
455
+ this.heap.remove(name)
456
+
457
+ this.enqueueExpirationEmission()
458
+
459
+ this.schedule()
460
+ }
461
+
462
+ /**
463
+ * 清除所有当前状态为 `expired` 的保留项,并返回清除数量。
464
+ */
465
+ clearExpiredExpirations(): number {
466
+ if (this.terminated === true) {
467
+ return 0
468
+ }
469
+
470
+ let clearedCount = 0
471
+
472
+ for (const name of Object.keys(this.expirationState)) {
473
+ // oxlint-disable-next-line no-unsafe-type-assertion
474
+ const expirationName = name as ExpirationName
475
+ const expirationState = this.expirationState[expirationName]
476
+ if (expirationState === undefined) {
477
+ continue
478
+ }
479
+ if (expirationState.state !== "expired") {
480
+ continue
481
+ }
482
+
483
+ this.heap.remove(expirationName)
484
+ delete this.expirationState[expirationName]
485
+ clearedCount = clearedCount + 1
486
+ }
487
+
488
+ if (clearedCount > 0) {
489
+ this.enqueueExpirationEmission()
490
+ }
491
+
492
+ return clearedCount
493
+ }
494
+
495
+ /**
496
+ * 清除所有当前状态为 `removed` 的保留项,并返回清除数量。
497
+ */
498
+ clearRemovedExpirations(): number {
499
+ if (this.terminated === true) {
500
+ return 0
501
+ }
502
+
503
+ let clearedCount = 0
504
+
505
+ for (const name of Object.keys(this.expirationState)) {
506
+ // oxlint-disable-next-line no-unsafe-type-assertion
507
+ const expirationName = name as ExpirationName
508
+ const expirationState = this.expirationState[expirationName]
509
+ if (expirationState === undefined) {
510
+ continue
511
+ }
512
+ if (expirationState.state !== "removed") {
513
+ continue
514
+ }
515
+
516
+ this.heap.remove(expirationName)
517
+ delete this.expirationState[expirationName]
518
+ clearedCount = clearedCount + 1
519
+ }
520
+
521
+ if (clearedCount > 0) {
522
+ this.enqueueExpirationEmission()
523
+ }
524
+
525
+ return clearedCount
526
+ }
527
+
528
+ /**
529
+ * 暂停过期调度与事件发出。
530
+ *
531
+ * 这不会冻结绝对到期时点,也不会阻止状态写入。暂停期间时间仍会继续流逝,
532
+ * 只是过期推进与事件通知会延后到后续恢复时再统一处理。
533
+ */
534
+ pause(): void {
535
+ if (this.terminated === true) {
536
+ return
537
+ }
538
+
539
+ this.paused = true
540
+
541
+ if (this.schedulerTimer !== null) {
542
+ clearTimeout(this.schedulerTimer)
543
+ this.schedulerTimer = null
544
+ }
545
+
546
+ this.remainingManager.pause()
547
+ }
548
+
549
+ /**
550
+ * 恢复过期调度,并刷新暂停期间积累的最新状态通知。
551
+ *
552
+ * 在暂停期间已经越过其绝对 `endAt` 的项,可能会在恢复后立即转为 `expired`。
553
+ * 若暂停期间发生过多次状态变更,对外只会发出折叠后的最新快照。
554
+ */
555
+ resume(): void {
556
+ if (this.terminated === true) {
557
+ return
558
+ }
559
+ if (this.paused !== true) {
560
+ return
561
+ }
562
+
563
+ this.paused = false
564
+
565
+ const changed = this.updateExpired(this.getNow())
566
+ if (changed === true) {
567
+ this.replaceQueuedExpirationEmission()
568
+ }
569
+
570
+ this.schedule()
571
+ this.remainingManager.resume()
572
+ this.flushExpirationEmissionQueue()
573
+ }
574
+
575
+ /**
576
+ * 终止当前管理器及其派生剩余时间管理器,并释放它们拥有的定时资源。
577
+ */
578
+ terminate(): void {
579
+ if (this.terminated === true) {
580
+ return
581
+ }
582
+
583
+ this.terminated = true
584
+ this.paused = true
585
+
586
+ if (this.schedulerTimer !== null) {
587
+ clearTimeout(this.schedulerTimer)
588
+ this.schedulerTimer = null
589
+ }
590
+
591
+ this.expirationEmissionQueue = []
592
+ this.remainingManager.terminate()
593
+ }
594
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./min-heap.ts"
2
+ export * from "./remaining-manager.ts"
3
+ export * from "./expiration-manager.ts"