@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,154 @@
1
+ import { afterEach, expect, test, vi } from "vitest"
2
+
3
+ import { CoordinationAbortError, CoordinationTimeoutError, ReadWriteLock } from "#Source/orchestration/index.ts"
4
+
5
+ afterEach(() => {
6
+ vi.clearAllTimers()
7
+ vi.useRealTimers()
8
+ vi.restoreAllMocks()
9
+ })
10
+
11
+ test("ReadWriteLock tracks reader and writer state across immediate acquisitions", () => {
12
+ const readWriteLock = new ReadWriteLock()
13
+
14
+ expect(readWriteLock.isLocked()).toBe(false)
15
+ expect(readWriteLock.isReadLocked()).toBe(false)
16
+ expect(readWriteLock.isWriteLocked()).toBe(false)
17
+
18
+ const firstReader = readWriteLock.tryAcquireRead()
19
+ const secondReader = readWriteLock.tryAcquireRead()
20
+
21
+ expect(firstReader?.details).toEqual({ coordination: "read-write-lock", mode: "read" })
22
+ expect(secondReader?.details).toEqual({ coordination: "read-write-lock", mode: "read" })
23
+ expect(readWriteLock.getActiveReaderCount()).toBe(2)
24
+ expect(readWriteLock.isReadLocked()).toBe(true)
25
+ expect(readWriteLock.tryAcquireWrite()).toBeUndefined()
26
+
27
+ firstReader?.release()
28
+ secondReader?.release()
29
+
30
+ const writer = readWriteLock.tryAcquireWrite()
31
+
32
+ expect(writer?.details).toEqual({ coordination: "read-write-lock", mode: "write" })
33
+ expect(readWriteLock.isWriteLocked()).toBe(true)
34
+ expect(readWriteLock.isLocked()).toBe(true)
35
+
36
+ writer?.release()
37
+
38
+ expect(readWriteLock.isLocked()).toBe(false)
39
+ })
40
+
41
+ test("ReadWriteLock keeps queued writers ahead of later readers", async () => {
42
+ const readWriteLock = new ReadWriteLock()
43
+ const activeReader = readWriteLock.tryAcquireRead()
44
+
45
+ expect(activeReader).toBeDefined()
46
+
47
+ const writerPromise = readWriteLock.acquireWrite()
48
+ const lateReaderPromise = readWriteLock.acquireRead()
49
+
50
+ expect(readWriteLock.getPendingWriterCount()).toBe(1)
51
+ expect(readWriteLock.getPendingReaderCount()).toBe(1)
52
+ expect(readWriteLock.getPendingCount()).toBe(2)
53
+
54
+ activeReader?.release()
55
+
56
+ const writer = await writerPromise
57
+ let lateReaderResolved = false
58
+ void lateReaderPromise.then(() => {
59
+ lateReaderResolved = true
60
+ })
61
+
62
+ await Promise.resolve()
63
+
64
+ expect(readWriteLock.isWriteLocked()).toBe(true)
65
+ expect(lateReaderResolved).toBe(false)
66
+
67
+ writer.release()
68
+
69
+ const lateReader = await lateReaderPromise
70
+
71
+ expect(readWriteLock.getPendingCount()).toBe(0)
72
+ expect(readWriteLock.getActiveReaderCount()).toBe(1)
73
+
74
+ lateReader.release()
75
+
76
+ expect(readWriteLock.isLocked()).toBe(false)
77
+ })
78
+
79
+ test("ReadWriteLock acquire methods reject on timeout and abort without leaking counts", async () => {
80
+ vi.useFakeTimers()
81
+
82
+ const readWriteLock = new ReadWriteLock()
83
+ const writer = readWriteLock.tryAcquireWrite()
84
+
85
+ expect(writer).toBeDefined()
86
+
87
+ const timeoutPromise = readWriteLock.acquireRead({ timeout: 20 })
88
+
89
+ expect(readWriteLock.getPendingReaderCount()).toBe(1)
90
+
91
+ await vi.advanceTimersByTimeAsync(20)
92
+
93
+ await expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError)
94
+ expect(readWriteLock.getPendingReaderCount()).toBe(0)
95
+
96
+ const abortController = new AbortController()
97
+ const abortPromise = readWriteLock.acquireWrite({ abortSignal: abortController.signal })
98
+
99
+ expect(readWriteLock.getPendingWriterCount()).toBe(1)
100
+
101
+ abortController.abort("cancelled")
102
+
103
+ await expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError)
104
+ expect(readWriteLock.getPendingWriterCount()).toBe(0)
105
+
106
+ writer?.release()
107
+ })
108
+
109
+ test("ReadWriteLock runExclusive methods release permits after callback completion", async () => {
110
+ const readWriteLock = new ReadWriteLock()
111
+
112
+ await expect(readWriteLock.runExclusiveRead(() => "read")).resolves.toBe("read")
113
+ expect(readWriteLock.isLocked()).toBe(false)
114
+
115
+ const error = new Error("write failed")
116
+
117
+ await expect(readWriteLock.runExclusiveWrite(() => {
118
+ throw error
119
+ })).rejects.toThrow(error)
120
+
121
+ expect(readWriteLock.isLocked()).toBe(false)
122
+ })
123
+
124
+ test("ReadWriteLock duplicate release stays silent by default and uses custom handler when provided", () => {
125
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
126
+ // no-op to silence warnings during test
127
+ })
128
+ const silentReadWriteLock = new ReadWriteLock()
129
+ const silentPermit = silentReadWriteLock.tryAcquireWrite()
130
+
131
+ expect(silentPermit).toBeDefined()
132
+
133
+ silentPermit?.release()
134
+ silentPermit?.release()
135
+ silentPermit?.release()
136
+
137
+ expect(silentReadWriteLock.isLocked()).toBe(false)
138
+ expect(warnSpy).not.toHaveBeenCalled()
139
+
140
+ const onDuplicateRelease = vi.fn()
141
+ const customReadWriteLock = new ReadWriteLock({ onDuplicateRelease })
142
+ const customPermit = customReadWriteLock.tryAcquireRead()
143
+
144
+ expect(customPermit).toBeDefined()
145
+
146
+ customPermit?.release()
147
+ customPermit?.release()
148
+ customPermit?.release()
149
+
150
+ expect(customReadWriteLock.isLocked()).toBe(false)
151
+ expect(onDuplicateRelease).toHaveBeenCalledTimes(2)
152
+ expect(onDuplicateRelease).toHaveBeenCalledWith("ReadWriteLock permit release was called more than once.")
153
+ expect(warnSpy).not.toHaveBeenCalled()
154
+ })
@@ -0,0 +1,135 @@
1
+ import { afterEach, expect, test, vi } from "vitest"
2
+
3
+ import { CoordinationAbortError, CoordinationTimeoutError, Semaphore } from "#Source/orchestration/index.ts"
4
+
5
+ afterEach(() => {
6
+ vi.clearAllTimers()
7
+ vi.useRealTimers()
8
+ vi.restoreAllMocks()
9
+ })
10
+
11
+ test("Semaphore tracks capacity and grants permits in FIFO order", async () => {
12
+ const semaphore = new Semaphore(2)
13
+
14
+ expect(semaphore.getMaxConcurrency()).toBe(2)
15
+ expect(semaphore.getActiveCount()).toBe(0)
16
+ expect(semaphore.getAvailableCount()).toBe(2)
17
+ expect(semaphore.isSaturated()).toBe(false)
18
+
19
+ const firstPermit = semaphore.tryAcquire()
20
+ const secondPermit = semaphore.tryAcquire()
21
+
22
+ expect(firstPermit).toBeDefined()
23
+ expect(secondPermit).toBeDefined()
24
+ expect(semaphore.isSaturated()).toBe(true)
25
+ expect(semaphore.getActiveCount()).toBe(2)
26
+ expect(semaphore.getAvailableCount()).toBe(0)
27
+
28
+ const thirdPermitPromise = semaphore.acquire()
29
+ const fourthPermitPromise = semaphore.acquire()
30
+
31
+ expect(semaphore.getPendingCount()).toBe(2)
32
+ expect(semaphore.tryAcquire()).toBeUndefined()
33
+
34
+ firstPermit?.release()
35
+
36
+ const thirdPermit = await thirdPermitPromise
37
+ let fourthResolved = false
38
+ void fourthPermitPromise.then(() => {
39
+ fourthResolved = true
40
+ })
41
+
42
+ await Promise.resolve()
43
+
44
+ expect(semaphore.getPendingCount()).toBe(1)
45
+ expect(fourthResolved).toBe(false)
46
+
47
+ secondPermit?.release()
48
+ const fourthPermit = await fourthPermitPromise
49
+
50
+ expect(semaphore.getPendingCount()).toBe(0)
51
+
52
+ thirdPermit.release()
53
+ fourthPermit.release()
54
+
55
+ expect(semaphore.getActiveCount()).toBe(0)
56
+ expect(semaphore.getAvailableCount()).toBe(2)
57
+ expect(semaphore.isSaturated()).toBe(false)
58
+ })
59
+
60
+ test("Semaphore acquire rejects on timeout and abort without leaking queue entries", async () => {
61
+ vi.useFakeTimers()
62
+
63
+ const semaphore = new Semaphore(1)
64
+ const blockingPermit = semaphore.tryAcquire()
65
+
66
+ expect(blockingPermit).toBeDefined()
67
+
68
+ const timeoutPromise = semaphore.acquire({ timeout: 15 })
69
+
70
+ expect(semaphore.getPendingCount()).toBe(1)
71
+
72
+ await vi.advanceTimersByTimeAsync(15)
73
+
74
+ await expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError)
75
+ expect(semaphore.getPendingCount()).toBe(0)
76
+
77
+ const abortController = new AbortController()
78
+ const abortPromise = semaphore.acquire({ abortSignal: abortController.signal })
79
+
80
+ expect(semaphore.getPendingCount()).toBe(1)
81
+
82
+ abortController.abort("stopped")
83
+
84
+ await expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError)
85
+ expect(semaphore.getPendingCount()).toBe(0)
86
+
87
+ blockingPermit?.release()
88
+ })
89
+
90
+ test("Semaphore runExclusive releases capacity after callback failure", async () => {
91
+ const semaphore = new Semaphore(1)
92
+ const error = new Error("boom")
93
+
94
+ await expect(semaphore.runExclusive(() => 1)).resolves.toBe(1)
95
+ expect(semaphore.getActiveCount()).toBe(0)
96
+
97
+ await expect(semaphore.runExclusive(() => {
98
+ throw error
99
+ })).rejects.toThrow(error)
100
+
101
+ expect(semaphore.getActiveCount()).toBe(0)
102
+ expect(semaphore.isSaturated()).toBe(false)
103
+ })
104
+
105
+ test("Semaphore duplicate release stays silent by default and uses custom handler when provided", () => {
106
+ const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
107
+ // no-op to silence warnings during test
108
+ })
109
+ const silentSemaphore = new Semaphore(1)
110
+ const silentPermit = silentSemaphore.tryAcquire()
111
+
112
+ expect(silentPermit).toBeDefined()
113
+
114
+ silentPermit?.release()
115
+ silentPermit?.release()
116
+ silentPermit?.release()
117
+
118
+ expect(silentSemaphore.getActiveCount()).toBe(0)
119
+ expect(warnSpy).not.toHaveBeenCalled()
120
+
121
+ const onDuplicateRelease = vi.fn()
122
+ const customSemaphore = new Semaphore(1, { onDuplicateRelease })
123
+ const customPermit = customSemaphore.tryAcquire()
124
+
125
+ expect(customPermit).toBeDefined()
126
+
127
+ customPermit?.release()
128
+ customPermit?.release()
129
+ customPermit?.release()
130
+
131
+ expect(customSemaphore.getActiveCount()).toBe(0)
132
+ expect(onDuplicateRelease).toHaveBeenCalledTimes(2)
133
+ expect(onDuplicateRelease).toHaveBeenCalledWith("Semaphore permit release was called more than once.")
134
+ expect(warnSpy).not.toHaveBeenCalled()
135
+ })
@@ -0,0 +1,11 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import { randomString } from "#Source/random/index.ts"
4
+
5
+ test("randomString returns expected output", () => {
6
+ const value = randomString(12)
7
+ expect(value).toHaveLength(12)
8
+
9
+ const constrained = randomString(10, "ab")
10
+ expect(constrained.split("").every(char => char === "a" || char === "b")).toBe(true)
11
+ })
@@ -49,16 +49,17 @@ test('should not run untracked inner effect', () => {
49
49
  let outerEffectTruthyBranchRunTimes = 0;
50
50
  const a = signal(() => 3);
51
51
  const b = computed(() => a.get() > 0);
52
- const _outerEffect = effect(() => {
52
+ // outer effect
53
+ effect(() => {
53
54
  if (b.get() === true) {
54
55
  outerEffectTruthyBranchRunTimes = outerEffectTruthyBranchRunTimes + 1;
55
- const _innerEffect = effect(() => {
56
+ effect(() => {
56
57
  if (a.get() === 0) {
57
58
  throw new Error("bad");
58
59
  }
59
- });
60
+ }, { name: "inner effect" });
60
61
  }
61
- });
62
+ }, { name: "outer effect" });
62
63
 
63
64
  expect(outerEffectTruthyBranchRunTimes).toBe(1);
64
65
  a.set(2);
@@ -226,7 +227,7 @@ test('should duplicate subscribers do not affect the notify order', () => {
226
227
  const srcB = signal(() => 0, { name: "srcB" });
227
228
  const order: string[] = [];
228
229
 
229
- const _effectA = effect(() => {
230
+ effect(() => {
230
231
  order.push('a');
231
232
  reactiveSystem.setNoActiveNodeAsSub();
232
233
  const isOne = srcB.get() === 1;
@@ -237,7 +238,7 @@ test('should duplicate subscribers do not affect the notify order', () => {
237
238
  srcB.get();
238
239
  srcA.get();
239
240
  }, { name: "effect-a" });
240
- const _effectB = effect(() => {
241
+ effect(() => {
241
242
  order.push('b');
242
243
  srcA.get();
243
244
  }, { name: "effect-b" });
@@ -357,20 +358,20 @@ test('should not execute skipped effects from previous failed flush when updatin
357
358
 
358
359
  let effect3Executed = false;
359
360
 
360
- const _effect1 = effect(() => {
361
+ effect(() => {
361
362
  a.get();
362
363
  }, { name: "effect1" });
363
- const _effect2 = effect(() => {
364
+ effect(() => {
364
365
  if (a.get() === 2) {
365
366
  throw new Error('Error in effect 2');
366
367
  }
367
368
  }, { name: "effect2" });
368
- const _effect3 = effect(() => {
369
+ effect(() => {
369
370
  a.get();
370
371
  d.get();
371
372
  effect3Executed = true;
372
373
  }, { name: "effect3" });
373
- const _effect4 = effect(() => {
374
+ effect(() => {
374
375
  b.get();
375
376
  }, { name: "effect4" });
376
377
 
@@ -1,7 +1,6 @@
1
1
  import { describe, it, vi, expect } from "vitest";
2
2
 
3
- import type { Effect } from '#Source/index.ts';
4
- import { Signal, computed, effect, effectScope, endBatch, reactiveSystem, signal, startBatch } from '#Source/reactor/index.ts';
3
+ import { Signal, effect, signal } from "#Source/reactor/index.ts"
5
4
 
6
5
  /**
7
6
  * 这个测试文件应该与 https://github.com/preactjs/signals/blob/main/packages/core/test/signal.test.tsx 保持同步。
@@ -0,0 +1,49 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import type { SingletonItem } from "#Source/singleton/index.ts"
4
+ import {
5
+ getSingletonFactory,
6
+ singletonCollection,
7
+ singletonItem,
8
+ } from "#Source/singleton/index.ts"
9
+
10
+ test("getSingletonFactory caches first produced value", () => {
11
+ let makeCalls = 0
12
+ const createSingleton = getSingletonFactory(() => {
13
+ makeCalls = makeCalls + 1
14
+ return { value: "singleton" }
15
+ })
16
+
17
+ const first = createSingleton()
18
+ const second = createSingleton()
19
+
20
+ expect(first).toBe(second)
21
+ expect(first.value).toBe("singleton")
22
+ expect(makeCalls).toBe(1)
23
+ })
24
+
25
+ test("singletonItem creates named lazy singleton item", () => {
26
+ let valueCalls = 0
27
+ const item = singletonItem("config", () => {
28
+ valueCalls = valueCalls + 1
29
+ return { enabled: true }
30
+ })
31
+
32
+ const first = item.getValue()
33
+ const second = item.getValue()
34
+
35
+ expect(item.getName()).toBe("config")
36
+ expect(first).toBe(second)
37
+ expect(first.enabled).toBe(true)
38
+ expect(valueCalls).toBe(1)
39
+ })
40
+
41
+ test("singletonCollection resolves named items and throws for missing names", () => {
42
+ const portItem: SingletonItem<string, number> = singletonItem("port", () => 3_000)
43
+ const modeItem: SingletonItem<string, string> = singletonItem("mode", () => "test")
44
+ const collection = singletonCollection([portItem, modeItem])
45
+
46
+ expect(collection.getItem("port")).toBe(3_000)
47
+ expect(collection.getItem("mode")).toBe("test")
48
+ expect(() => collection.getItem("missing")).toThrow("GlobalService: item missing not found")
49
+ })