@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.
Files changed (175) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +123 -36
  3. package/dist/index.js +45 -4
  4. package/dist/index.js.map +183 -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 -118
  12. package/src/basic/function.ts +81 -62
  13. package/src/basic/is.ts +152 -71
  14. package/src/basic/promise.ts +29 -8
  15. package/src/basic/string.ts +2 -33
  16. package/src/color/README.md +105 -0
  17. package/src/color/index.ts +3 -0
  18. package/src/color/internal.ts +42 -0
  19. package/src/color/rgb/analyze.ts +236 -0
  20. package/src/color/rgb/construct.ts +130 -0
  21. package/src/color/rgb/convert.ts +227 -0
  22. package/src/color/rgb/derive.ts +303 -0
  23. package/src/color/rgb/index.ts +6 -0
  24. package/src/color/rgb/internal.ts +208 -0
  25. package/src/color/rgb/parse.ts +302 -0
  26. package/src/color/rgb/serialize.ts +144 -0
  27. package/src/color/types.ts +57 -0
  28. package/src/color/xyz/analyze.ts +80 -0
  29. package/src/color/xyz/construct.ts +19 -0
  30. package/src/color/xyz/convert.ts +71 -0
  31. package/src/color/xyz/index.ts +3 -0
  32. package/src/color/xyz/internal.ts +23 -0
  33. package/src/css/README.md +93 -0
  34. package/src/css/class.ts +559 -0
  35. package/src/css/index.ts +1 -0
  36. package/src/encoding/README.md +66 -79
  37. package/src/encoding/base64.ts +13 -4
  38. package/src/environment/README.md +97 -0
  39. package/src/environment/basic.ts +26 -0
  40. package/src/environment/device.ts +311 -0
  41. package/src/environment/feature.ts +285 -0
  42. package/src/environment/geo.ts +337 -0
  43. package/src/environment/index.ts +7 -0
  44. package/src/environment/runtime.ts +400 -0
  45. package/src/environment/snapshot.ts +60 -0
  46. package/src/environment/variable.ts +239 -0
  47. package/src/event/README.md +90 -0
  48. package/src/event/class-event-proxy.ts +228 -0
  49. package/src/event/common.ts +19 -0
  50. package/src/event/event-manager.ts +203 -0
  51. package/src/event/index.ts +4 -0
  52. package/src/event/instance-event-proxy.ts +186 -0
  53. package/src/event/internal.ts +24 -0
  54. package/src/exception/README.md +96 -0
  55. package/src/exception/browser.ts +219 -0
  56. package/src/exception/index.ts +4 -0
  57. package/src/exception/nodejs.ts +169 -0
  58. package/src/exception/normalize.ts +106 -0
  59. package/src/exception/types.ts +99 -0
  60. package/src/identifier/README.md +92 -0
  61. package/src/identifier/id.ts +119 -0
  62. package/src/identifier/index.ts +2 -0
  63. package/src/identifier/uuid.ts +187 -0
  64. package/src/index.ts +16 -1
  65. package/src/log/README.md +79 -0
  66. package/src/log/index.ts +5 -0
  67. package/src/log/log-emitter.ts +72 -0
  68. package/src/log/log-record.ts +10 -0
  69. package/src/log/log-scheduler.ts +74 -0
  70. package/src/log/log-type.ts +8 -0
  71. package/src/log/logger.ts +543 -0
  72. package/src/orchestration/README.md +89 -0
  73. package/src/orchestration/coordination/barrier.ts +214 -0
  74. package/src/orchestration/coordination/count-down-latch.ts +215 -0
  75. package/src/orchestration/coordination/errors.ts +98 -0
  76. package/src/orchestration/coordination/index.ts +16 -0
  77. package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
  78. package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
  79. package/src/orchestration/coordination/keyed-lock.ts +168 -0
  80. package/src/orchestration/coordination/mutex.ts +257 -0
  81. package/src/orchestration/coordination/permit.ts +127 -0
  82. package/src/orchestration/coordination/read-write-lock.ts +444 -0
  83. package/src/orchestration/coordination/semaphore.ts +280 -0
  84. package/src/orchestration/index.ts +1 -0
  85. package/src/random/README.md +55 -86
  86. package/src/random/index.ts +1 -1
  87. package/src/random/string.ts +35 -0
  88. package/src/reactor/README.md +4 -0
  89. package/src/reactor/reactor-core/primitive.ts +9 -9
  90. package/src/reactor/reactor-core/reactive-system.ts +5 -5
  91. package/src/singleton/README.md +79 -0
  92. package/src/singleton/factory.ts +55 -0
  93. package/src/singleton/index.ts +2 -0
  94. package/src/singleton/manager.ts +204 -0
  95. package/src/storage/README.md +107 -0
  96. package/src/storage/index.ts +1 -0
  97. package/src/storage/table.ts +449 -0
  98. package/src/timer/README.md +86 -0
  99. package/src/timer/expiration/expiration-manager.ts +594 -0
  100. package/src/timer/expiration/index.ts +3 -0
  101. package/src/timer/expiration/min-heap.ts +208 -0
  102. package/src/timer/expiration/remaining-manager.ts +241 -0
  103. package/src/timer/index.ts +1 -0
  104. package/src/type/README.md +54 -307
  105. package/src/type/class.ts +2 -2
  106. package/src/type/index.ts +14 -14
  107. package/src/type/is.ts +265 -2
  108. package/src/type/object.ts +37 -0
  109. package/src/type/string.ts +7 -2
  110. package/src/type/tuple.ts +6 -6
  111. package/src/type/union.ts +16 -0
  112. package/src/web/README.md +77 -0
  113. package/src/web/capture.ts +35 -0
  114. package/src/web/clipboard.ts +97 -0
  115. package/src/web/dom.ts +117 -0
  116. package/src/web/download.ts +16 -0
  117. package/src/web/event.ts +46 -0
  118. package/src/web/index.ts +10 -0
  119. package/src/web/local-storage.ts +113 -0
  120. package/src/web/location.ts +28 -0
  121. package/src/web/permission.ts +172 -0
  122. package/src/web/script-loader.ts +432 -0
  123. package/tests/unit/abort/abort-manager.spec.ts +225 -0
  124. package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
  125. package/tests/unit/basic/array.spec.ts +1 -1
  126. package/tests/unit/basic/stream.spec.ts +1 -1
  127. package/tests/unit/basic/string.spec.ts +0 -9
  128. package/tests/unit/color/rgb/analyze.spec.ts +110 -0
  129. package/tests/unit/color/rgb/construct.spec.ts +56 -0
  130. package/tests/unit/color/rgb/convert.spec.ts +60 -0
  131. package/tests/unit/color/rgb/derive.spec.ts +103 -0
  132. package/tests/unit/color/rgb/parse.spec.ts +66 -0
  133. package/tests/unit/color/rgb/serialize.spec.ts +46 -0
  134. package/tests/unit/color/xyz/analyze.spec.ts +33 -0
  135. package/tests/unit/color/xyz/construct.spec.ts +10 -0
  136. package/tests/unit/color/xyz/convert.spec.ts +18 -0
  137. package/tests/unit/css/class.spec.ts +157 -0
  138. package/tests/unit/environment/basic.spec.ts +20 -0
  139. package/tests/unit/environment/device.spec.ts +146 -0
  140. package/tests/unit/environment/feature.spec.ts +388 -0
  141. package/tests/unit/environment/geo.spec.ts +111 -0
  142. package/tests/unit/environment/runtime.spec.ts +364 -0
  143. package/tests/unit/environment/snapshot.spec.ts +4 -0
  144. package/tests/unit/environment/variable.spec.ts +190 -0
  145. package/tests/unit/event/class-event-proxy.spec.ts +225 -0
  146. package/tests/unit/event/event-manager.spec.ts +246 -0
  147. package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
  148. package/tests/unit/exception/browser.spec.ts +213 -0
  149. package/tests/unit/exception/nodejs.spec.ts +144 -0
  150. package/tests/unit/exception/normalize.spec.ts +57 -0
  151. package/tests/unit/identifier/id.spec.ts +71 -0
  152. package/tests/unit/identifier/uuid.spec.ts +85 -0
  153. package/tests/unit/log/log-emitter.spec.ts +33 -0
  154. package/tests/unit/log/log-scheduler.spec.ts +40 -0
  155. package/tests/unit/log/log-type.spec.ts +7 -0
  156. package/tests/unit/log/logger.spec.ts +222 -0
  157. package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
  158. package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
  159. package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
  160. package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
  161. package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
  162. package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
  163. package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
  164. package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
  165. package/tests/unit/random/string.spec.ts +11 -0
  166. package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
  167. package/tests/unit/reactor/preact-signal.spec.ts +1 -2
  168. package/tests/unit/singleton/singleton.spec.ts +49 -0
  169. package/tests/unit/storage/table.spec.ts +620 -0
  170. package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
  171. package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
  172. package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
  173. package/.oxlintrc.json +0 -5
  174. package/src/random/uuid.ts +0 -103
  175. package/tests/unit/random/uuid.spec.ts +0 -37
@@ -0,0 +1,109 @@
1
+ /**
2
+ * 表示可挂入等待队列的节点最小结构。
3
+ *
4
+ * 队列本身不关心节点承载的业务数据,只要求节点能记录自己是否在队列中,以及前后相邻节点。
5
+ */
6
+ export interface InternalWaitQueueEntry<Entry extends InternalWaitQueueEntry<Entry>> {
7
+ isQueued: boolean
8
+ previous: Entry | undefined
9
+ next: Entry | undefined
10
+ }
11
+
12
+ /**
13
+ * 表示面向等待者节点的双向链表队列。
14
+ */
15
+ export class InternalWaitQueue<Entry extends InternalWaitQueueEntry<Entry>> {
16
+ private head: Entry | undefined
17
+ private tail: Entry | undefined
18
+
19
+ private count: number
20
+
21
+ constructor() {
22
+ this.head = undefined
23
+ this.tail = undefined
24
+ this.count = 0
25
+ }
26
+
27
+ /**
28
+ * 查看当前队头节点但不移除。
29
+ */
30
+ peek(): Entry | undefined {
31
+ return this.head
32
+ }
33
+
34
+ /**
35
+ * 读取队列中的节点数量。
36
+ */
37
+ getCount(): number {
38
+ return this.count
39
+ }
40
+
41
+ /**
42
+ * 判断队列是否为空。
43
+ */
44
+ isEmpty(): boolean {
45
+ return this.head === undefined
46
+ }
47
+
48
+ /**
49
+ * 把节点追加到队尾。
50
+ */
51
+ enqueue(entry: Entry): void {
52
+ entry.previous = this.tail
53
+ entry.next = undefined
54
+ entry.isQueued = true
55
+
56
+ if (this.tail === undefined) {
57
+ this.head = entry
58
+ }
59
+ else {
60
+ this.tail.next = entry
61
+ }
62
+
63
+ this.tail = entry
64
+ this.count = this.count + 1
65
+ }
66
+
67
+ /**
68
+ * 从队列中移除指定节点。
69
+ *
70
+ * 该操作不要求节点位于队头,因此适合被 timeout 或 abort 的等待者直接自移除。
71
+ */
72
+ remove(entry: Entry): void {
73
+ if (entry.isQueued === false) {
74
+ return
75
+ }
76
+
77
+ if (entry.previous === undefined) {
78
+ this.head = entry.next
79
+ }
80
+ else {
81
+ entry.previous.next = entry.next
82
+ }
83
+
84
+ if (entry.next === undefined) {
85
+ this.tail = entry.previous
86
+ }
87
+ else {
88
+ entry.next.previous = entry.previous
89
+ }
90
+
91
+ entry.previous = undefined
92
+ entry.next = undefined
93
+ entry.isQueued = false
94
+ this.count = this.count - 1
95
+ }
96
+
97
+ /**
98
+ * 取出并返回队头节点。
99
+ */
100
+ shift(): Entry | undefined {
101
+ const head = this.head
102
+ if (head === undefined) {
103
+ return undefined
104
+ }
105
+
106
+ this.remove(head)
107
+ return head
108
+ }
109
+ }
@@ -0,0 +1,168 @@
1
+ import type { MutexAcquireOptions, MutexOptions, MutexPermit } from "./mutex.ts"
2
+ import type { KeyedPermitDetails } from "./permit.ts"
3
+
4
+ import { Mutex } from "./mutex.ts"
5
+ import { Permit } from "./permit.ts"
6
+
7
+ /**
8
+ * 表示 `KeyedLock` 成功获取后的释放句柄。
9
+ *
10
+ * 句柄中会保留当前命中的 key,便于调试或上层记录持锁对象。
11
+ */
12
+ export type KeyedLockPermit<Key> = Permit<KeyedPermitDetails<"keyed-lock", Key>>
13
+
14
+ /**
15
+ * 表示单个 key 对应的内部状态。
16
+ *
17
+ * 每个 key 都维护一个独立的 `Mutex`,并通过引用计数判断是否可以从状态表中清理。
18
+ */
19
+ interface InternalKeyedLockState<Key> {
20
+ key: Key
21
+ mutex: Mutex
22
+ referenceCount: number
23
+ }
24
+
25
+ /**
26
+ * 表示构造 `KeyedLock` 时支持的配置。
27
+ *
28
+ * 当前直接复用 `Mutex` 的配置项,以保证每个 key 下的锁行为一致。
29
+ */
30
+ export interface KeyedLockOptions extends MutexOptions {
31
+ }
32
+
33
+ /**
34
+ * 表示按 key 维度拆分互斥域的锁。
35
+ *
36
+ * 相同 key 的任务会串行执行,不同 key 之间则互不影响,适合做资源 ID、租户 ID、文件路径等粒度的独占控制。
37
+ */
38
+ export class KeyedLock<Key> {
39
+ private readonly stateMap: Map<Key, InternalKeyedLockState<Key>>
40
+
41
+ private readonly mutexOptions: MutexOptions
42
+
43
+ /**
44
+ * 创建一个按 key 分桶的互斥锁管理器。
45
+ */
46
+ constructor(options: KeyedLockOptions = {}) {
47
+ this.stateMap = new Map<Key, InternalKeyedLockState<Key>>()
48
+ this.mutexOptions = options
49
+ }
50
+
51
+ /**
52
+ * 判断当前是否已经为指定 key 建立内部状态。
53
+ */
54
+ hasKey(key: Key): boolean {
55
+ return this.stateMap.has(key)
56
+ }
57
+
58
+ /**
59
+ * 读取当前仍被追踪的 key 数量。
60
+ */
61
+ getKeyCount(): number {
62
+ return this.stateMap.size
63
+ }
64
+
65
+ /**
66
+ * 取得指定 key 的状态;若不存在则惰性创建。
67
+ */
68
+ private getOrCreateState(key: Key): InternalKeyedLockState<Key> {
69
+ const existingState = this.stateMap.get(key)
70
+ if (existingState !== undefined) {
71
+ return existingState
72
+ }
73
+
74
+ const state: InternalKeyedLockState<Key> = {
75
+ key,
76
+ mutex: new Mutex(this.mutexOptions),
77
+ referenceCount: 0,
78
+ }
79
+ this.stateMap.set(key, state)
80
+ return state
81
+ }
82
+
83
+ /**
84
+ * 在某个 key 不再被持有且没有等待者时回收对应状态。
85
+ */
86
+ private cleanupState(state: InternalKeyedLockState<Key>): void {
87
+ if (state.referenceCount !== 0) {
88
+ return
89
+ }
90
+
91
+ if (state.mutex.isLocked() === true || state.mutex.getPendingCount() !== 0) {
92
+ return
93
+ }
94
+
95
+ const currentState = this.stateMap.get(state.key)
96
+ if (currentState === state) {
97
+ this.stateMap.delete(state.key)
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 基于底层 `Mutex` permit 构造带 key 元信息的释放句柄。
103
+ */
104
+ private buildPermit(state: InternalKeyedLockState<Key>, permit: MutexPermit): KeyedLockPermit<Key> {
105
+ return new Permit<KeyedPermitDetails<"keyed-lock", Key>>({
106
+ details: {
107
+ coordination: "keyed-lock",
108
+ key: state.key,
109
+ },
110
+ duplicateReleaseMessage: "KeyedLock permit release was called more than once.",
111
+ onDuplicateRelease: this.mutexOptions.onDuplicateRelease,
112
+ onRelease: (): void => {
113
+ permit.release()
114
+ state.referenceCount = state.referenceCount - 1
115
+ this.cleanupState(state)
116
+ },
117
+ })
118
+ }
119
+
120
+ /**
121
+ * 尝试立即获取指定 key 的独占锁。
122
+ */
123
+ tryAcquire(key: Key): KeyedLockPermit<Key> | undefined {
124
+ const state = this.getOrCreateState(key)
125
+ const permit = state.mutex.tryAcquire()
126
+
127
+ if (permit === undefined) {
128
+ this.cleanupState(state)
129
+ return undefined
130
+ }
131
+
132
+ state.referenceCount = state.referenceCount + 1
133
+ return this.buildPermit(state, permit)
134
+ }
135
+
136
+ /**
137
+ * 等待直到成功获取指定 key 的独占锁。
138
+ */
139
+ async acquire(key: Key, options: MutexAcquireOptions = {}): Promise<KeyedLockPermit<Key>> {
140
+ const state = this.getOrCreateState(key)
141
+ state.referenceCount = state.referenceCount + 1
142
+
143
+ try {
144
+ const permit = await state.mutex.acquire(options)
145
+ return this.buildPermit(state, permit)
146
+ }
147
+ catch (error) {
148
+ state.referenceCount = state.referenceCount - 1
149
+ this.cleanupState(state)
150
+ throw error
151
+ }
152
+ }
153
+
154
+ /**
155
+ * 在指定 key 的独占锁保护下执行回调。
156
+ */
157
+ async runExclusive<T>(key: Key, callback: () => Promise<T>, options?: MutexAcquireOptions): Promise<T>
158
+ async runExclusive<T>(key: Key, callback: () => T, options?: MutexAcquireOptions): Promise<T>
159
+ async runExclusive<T>(key: Key, callback: () => Promise<T> | T, options: MutexAcquireOptions = {}): Promise<T> {
160
+ const permit = await this.acquire(key, options)
161
+ try {
162
+ return await callback()
163
+ }
164
+ finally {
165
+ permit.release()
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,257 @@
1
+ import type { CoordinationPermitDetails } from "./permit.ts"
2
+ import type { InternalWaitQueueEntry } from "./internal/wait-queue.ts"
3
+ import type { InternalWaitConstraintsEntry, InternalWaitConstraintsOptions } from "./internal/wait-constraints.ts"
4
+
5
+ import { InternalWaitQueue } from "./internal/wait-queue.ts"
6
+ import { Permit } from "./permit.ts"
7
+ import {
8
+ internalAssertTimeoutOption,
9
+ internalBindWaitConstraints,
10
+ internalCleanupWaitConstraints,
11
+ internalThrowIfAborted,
12
+ } from "./internal/wait-constraints.ts"
13
+
14
+ /**
15
+ * 链表形式的等待节点。
16
+ *
17
+ * 之所以不使用数组,是因为等待中的任务可能会被 timeout 或 abort 主动取消。
18
+ * 使用双向链表后,任意节点都可以在 O(1) 时间内从等待队列中移除,不需要做
19
+ * `indexOf + splice` 这样的线性扫描。
20
+ */
21
+ interface InternalMutexQueueEntry extends InternalWaitQueueEntry<InternalMutexQueueEntry>, InternalWaitConstraintsEntry {
22
+ resolve: (permit: MutexPermit) => void
23
+ reject: (reason: unknown) => void
24
+ isSettled: boolean
25
+ }
26
+
27
+ /**
28
+ * 表示获取互斥锁时可选的等待控制参数。
29
+ */
30
+ export interface MutexAcquireOptions extends InternalWaitConstraintsOptions {
31
+ /**
32
+ * 等待锁的超时时间,单位为毫秒。
33
+ */
34
+ timeout?: number | undefined
35
+
36
+ /**
37
+ * 等待期间使用的中止信号;一旦中止,会将本次等待从队列中移除并拒绝。
38
+ */
39
+ abortSignal?: AbortSignal | undefined
40
+ }
41
+
42
+ /**
43
+ * 表示 `Mutex` 成功获取后的占用句柄。
44
+ */
45
+ export type MutexPermit = Permit<CoordinationPermitDetails<"mutex">>
46
+
47
+ const INTERNAL_MUTEX_DUPLICATE_RELEASE_MESSAGE = "Mutex permit release was called more than once."
48
+ /**
49
+ * 表示 `Mutex` 在检测到重复 `release()` 误用时使用的处理函数。
50
+ */
51
+ export type MutexDuplicateReleaseHandler = (message: string) => void
52
+ /**
53
+ * 表示构造 `Mutex` 时可选的运行时配置。
54
+ */
55
+ export interface MutexOptions {
56
+ /**
57
+ * 重复调用同一个 `release()` 时的可选处理器。
58
+ *
59
+ * 未提供时,`Mutex` 会保持静默;提供后,每个 `Mutex` 实例最多调用一次。
60
+ */
61
+ onDuplicateRelease?: MutexDuplicateReleaseHandler | undefined
62
+ }
63
+
64
+ /**
65
+ * 表示用于串行化异步临界区的互斥锁。
66
+ *
67
+ * @example
68
+ * ```
69
+ * const mutex = new Mutex()
70
+ * let counter = 0
71
+ *
72
+ * await Promise.all([
73
+ * mutex.runExclusive(async () => {
74
+ * const current = counter
75
+ * await Promise.resolve()
76
+ * counter = current + 1
77
+ * }),
78
+ * mutex.runExclusive(async () => {
79
+ * const current = counter
80
+ * await Promise.resolve()
81
+ * counter = current + 1
82
+ * }),
83
+ * ])
84
+ *
85
+ * // Expect: 2
86
+ * const example1 = counter
87
+ * ```
88
+ */
89
+ export class Mutex {
90
+ /**
91
+ * 等待队列中的每个节点都代表一个尚未拿到锁的等待者。
92
+ */
93
+ private readonly queue: InternalWaitQueue<InternalMutexQueueEntry>
94
+
95
+ /**
96
+ * 当前是否已有持锁者进入临界区。
97
+ */
98
+ private internalIsLocked: boolean
99
+
100
+ /**
101
+ * 重复 release 误用的可选处理器。
102
+ */
103
+ private readonly onDuplicateRelease: MutexDuplicateReleaseHandler | undefined
104
+
105
+ constructor(options: MutexOptions = {}) {
106
+ this.queue = new InternalWaitQueue<InternalMutexQueueEntry>()
107
+ this.internalIsLocked = false
108
+ this.onDuplicateRelease = options.onDuplicateRelease
109
+ }
110
+
111
+ /**
112
+ * 读取当前锁是否已经被持有。
113
+ */
114
+ isLocked(): boolean {
115
+ return this.internalIsLocked
116
+ }
117
+
118
+ /**
119
+ * 读取当前等待队列中的任务数量。
120
+ */
121
+ getPendingCount(): number {
122
+ return this.queue.getCount()
123
+ }
124
+
125
+ /**
126
+ * 尝试把锁交给下一个可运行的等待者。
127
+ *
128
+ * 这里始终从队头取任务,保持 FIFO;若遇到已失效节点,则跳过直到找到下一个有效等待者。
129
+ */
130
+ private dispatch(): void {
131
+ if (this.internalIsLocked === true) {
132
+ return
133
+ }
134
+
135
+ while (this.queue.isEmpty() === false) {
136
+ const nextEntry = this.queue.shift()
137
+ if (nextEntry === undefined) {
138
+ return
139
+ }
140
+
141
+ if (nextEntry.isSettled === true) {
142
+ continue
143
+ }
144
+
145
+ this.internalIsLocked = true
146
+ nextEntry.isSettled = true
147
+ internalCleanupWaitConstraints(nextEntry)
148
+ nextEntry.resolve(this.buildPermit())
149
+ return
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 为当前持锁者构造一个 permit。
155
+ */
156
+ private buildPermit(): MutexPermit {
157
+ return new Permit<CoordinationPermitDetails<"mutex">>({
158
+ details: { coordination: "mutex" },
159
+ duplicateReleaseMessage: INTERNAL_MUTEX_DUPLICATE_RELEASE_MESSAGE,
160
+ onDuplicateRelease: this.onDuplicateRelease,
161
+ onRelease: (): void => {
162
+ this.internalIsLocked = false
163
+ this.dispatch()
164
+ },
165
+ })
166
+ }
167
+
168
+ /**
169
+ * 以失败状态结束一个等待节点。
170
+ *
171
+ * 该逻辑会统一处理:标记结束、清理约束、从队列摘除、拒绝 Promise,最后尝试继续调度后继等待者。
172
+ */
173
+ private rejectEntry(entry: InternalMutexQueueEntry, reason: unknown): void {
174
+ if (entry.isSettled === true) {
175
+ return
176
+ }
177
+
178
+ entry.isSettled = true
179
+ internalCleanupWaitConstraints(entry)
180
+ this.queue.remove(entry)
181
+ entry.reject(reason)
182
+ this.dispatch()
183
+ }
184
+
185
+ /**
186
+ * 尝试立即获取锁;若当前不可用则返回 `undefined`。
187
+ */
188
+ tryAcquire(): MutexPermit | undefined {
189
+ if (this.internalIsLocked === true || this.queue.isEmpty() === false) {
190
+ return undefined
191
+ }
192
+
193
+ this.internalIsLocked = true
194
+ return this.buildPermit()
195
+ }
196
+
197
+ /**
198
+ * 等待直到成功获取锁。
199
+ *
200
+ * 当锁暂时不可用时,调用方会进入 FIFO 等待队列;一旦前面的持锁者释放,队头等待者会被唤醒。
201
+ */
202
+ async acquire(options: MutexAcquireOptions = {}): Promise<MutexPermit> {
203
+ internalAssertTimeoutOption("Mutex acquire", options.timeout)
204
+
205
+ internalThrowIfAborted("Mutex acquire", options.abortSignal)
206
+
207
+ const immediatePermit = this.tryAcquire()
208
+ if (immediatePermit !== undefined) {
209
+ return immediatePermit
210
+ }
211
+
212
+ const permit = await new Promise<MutexPermit>((resolve, reject) => {
213
+ const entry: InternalMutexQueueEntry = {
214
+ resolve,
215
+ reject,
216
+ isSettled: false,
217
+ isQueued: false,
218
+ previous: undefined,
219
+ next: undefined,
220
+ timeoutId: undefined,
221
+ abortSignal: undefined,
222
+ abortListener: undefined,
223
+ }
224
+
225
+ internalBindWaitConstraints(entry, options, {
226
+ abortOperation: "Mutex acquire",
227
+ timeoutOperation: "Mutex acquire",
228
+ onAbort: error => {
229
+ this.rejectEntry(entry, error)
230
+ },
231
+ onTimeout: error => {
232
+ this.rejectEntry(entry, error)
233
+ },
234
+ })
235
+ this.queue.enqueue(entry)
236
+ this.dispatch()
237
+ })
238
+ return permit
239
+ }
240
+
241
+ /**
242
+ * 在独占锁保护下执行回调。
243
+ *
244
+ * 无论回调成功还是抛错,permit 都会在 `finally` 中释放,避免把锁永久占住。
245
+ */
246
+ async runExclusive<T>(callback: () => Promise<T>, options?: MutexAcquireOptions): Promise<T>
247
+ async runExclusive<T>(callback: () => T, options?: MutexAcquireOptions): Promise<T>
248
+ async runExclusive<T>(callback: () => Promise<T> | T, options: MutexAcquireOptions = {}): Promise<T> {
249
+ const permit = await this.acquire(options)
250
+ try {
251
+ return await callback()
252
+ }
253
+ finally {
254
+ permit.release()
255
+ }
256
+ }
257
+ }
@@ -0,0 +1,127 @@
1
+ const INTERNAL_DEFAULT_DUPLICATE_RELEASE_MESSAGE = "Permit release was called more than once."
2
+
3
+ /**
4
+ * 表示 `Permit` 在检测到重复 `release()` 误用时使用的处理函数。
5
+ */
6
+ export type PermitDuplicateReleaseHandler = (message: string) => void
7
+
8
+ /**
9
+ * 表示构造 `Permit` 时的可选元信息。
10
+ */
11
+ export interface PermitOptions<Details> {
12
+ /**
13
+ * `Permit` 被正常释放时的回调。
14
+ */
15
+ onRelease: () => void
16
+
17
+ /**
18
+ * 释放句柄附带的只读细节。
19
+ */
20
+ details?: Details | undefined
21
+
22
+ /**
23
+ * 重复释放时的可选处理器。
24
+ */
25
+ onDuplicateRelease?: PermitDuplicateReleaseHandler | undefined
26
+
27
+ /**
28
+ * 重复释放时的提示信息。
29
+ */
30
+ duplicateReleaseMessage?: string | undefined
31
+ }
32
+
33
+ /**
34
+ * 表示与某个协调原语关联的 permit 细节。
35
+ */
36
+ export interface CoordinationPermitDetails<Coordination extends string = string> {
37
+ coordination: Coordination
38
+ }
39
+
40
+ /**
41
+ * 表示带有模式信息的 permit 细节。
42
+ */
43
+ export interface ModePermitDetails<Coordination extends string, Mode extends string>
44
+ extends CoordinationPermitDetails<Coordination> {
45
+ mode: Mode
46
+ }
47
+
48
+ /**
49
+ * 表示带有 key 信息的 permit 细节。
50
+ */
51
+ export interface KeyedPermitDetails<Coordination extends string, Key>
52
+ extends CoordinationPermitDetails<Coordination> {
53
+ key: Key
54
+ }
55
+
56
+ /**
57
+ * 表示一次已成功获取的协调资源占用句柄。
58
+ *
59
+ * `Permit` 本身不负责排队或调度,它只是对“当前持有一次资源”的显式表达,
60
+ * 并把释放动作和调试元信息封装在一起。
61
+ */
62
+ export class Permit<Details = undefined> implements Disposable {
63
+ /**
64
+ * 创建该 permit 时附带的只读元信息。
65
+ */
66
+ readonly details: Details | undefined
67
+
68
+ private isReleasedValue: boolean
69
+
70
+ private readonly onRelease: () => void
71
+
72
+ private readonly onDuplicateRelease: PermitDuplicateReleaseHandler | undefined
73
+
74
+ private readonly duplicateReleaseMessage: string
75
+
76
+ /**
77
+ * 创建一个 permit,并绑定释放时需要执行的回调。
78
+ */
79
+ constructor(options: PermitOptions<Details>) {
80
+ this.details = options.details
81
+ this.isReleasedValue = false
82
+ this.onRelease = options.onRelease
83
+ this.onDuplicateRelease = options.onDuplicateRelease
84
+ this.duplicateReleaseMessage = options.duplicateReleaseMessage ?? INTERNAL_DEFAULT_DUPLICATE_RELEASE_MESSAGE
85
+ }
86
+
87
+ /**
88
+ * 读取当前 `Permit` 是否已经释放。
89
+ */
90
+ isReleased(): boolean {
91
+ return this.isReleasedValue
92
+ }
93
+
94
+ /**
95
+ * 读取当前 `Permit` 是否仍然有效。
96
+ */
97
+ isActive(): boolean {
98
+ return this.isReleasedValue === false
99
+ }
100
+
101
+ /**
102
+ * 主动释放本次占用。
103
+ */
104
+ release(): void {
105
+ if (this.isReleasedValue === true) {
106
+ this.onDuplicateRelease?.(this.duplicateReleaseMessage)
107
+ return
108
+ }
109
+
110
+ this.isReleasedValue = true
111
+ this.onRelease()
112
+ }
113
+
114
+ /**
115
+ * 与其他模块保持一致的显式释放别名。
116
+ */
117
+ dispose(): void {
118
+ this.release()
119
+ }
120
+
121
+ /**
122
+ * 支持 `using` 语法自动释放。
123
+ */
124
+ [Symbol.dispose](): void {
125
+ this.release()
126
+ }
127
+ }