@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,364 @@
1
+ import { expect, test, vi } from "vitest"
2
+ import {
3
+ getRuntime,
4
+ getRuntimeContextBrowser,
5
+ getRuntimeContextBun,
6
+ getRuntimeContextDeno,
7
+ getRuntimeContextNodejs,
8
+ getRuntimeContextUnknown,
9
+ getRuntimeContextWebWorker,
10
+ getRuntimeFlags,
11
+ getRuntimeRegistryItem,
12
+ getServiceWorkerContext,
13
+ isBrowser,
14
+ isBun,
15
+ isDeno,
16
+ isNodejs,
17
+ isRuntimeRegistered,
18
+ isSatisfiesRuntime,
19
+ isSatisfiesRuntimes,
20
+ isServiceWorker,
21
+ isUnknown,
22
+ isWebWorker,
23
+ listRuntimes,
24
+ registerRuntime,
25
+ unregisterRuntime,
26
+ useBrowser,
27
+ useBun,
28
+ useDeno,
29
+ useNodejs,
30
+ useRuntimes,
31
+ useServiceWorker,
32
+ useUnknown,
33
+ useWebWorker,
34
+ } from "#Source/environment/runtime.ts"
35
+
36
+ test("isRuntimeRegistered detects built-in and missing runtime", () => {
37
+ expect(isRuntimeRegistered("nodejs")).toBe(true)
38
+ expect(isRuntimeRegistered("custom-runtime")).toBe(false)
39
+ })
40
+
41
+ test("registerRuntime stores custom runtime", () => {
42
+ registerRuntime({
43
+ runtime: "custom-register",
44
+ detect: () => false,
45
+ getGlobalContext: () => ({ ok: true }),
46
+ use: (_use, otherwise) => otherwise(),
47
+ })
48
+
49
+ expect(isRuntimeRegistered("custom-register")).toBe(true)
50
+ unregisterRuntime("custom-register")
51
+ })
52
+
53
+ test("unregisterRuntime removes custom runtime", () => {
54
+ registerRuntime({
55
+ runtime: "custom-remove",
56
+ detect: () => false,
57
+ getGlobalContext: () => ({ ok: true }),
58
+ use: (_use, otherwise) => otherwise(),
59
+ })
60
+ unregisterRuntime("custom-remove")
61
+
62
+ expect(isRuntimeRegistered("custom-remove")).toBe(false)
63
+ })
64
+
65
+ test("listRuntimes returns runtime keys", () => {
66
+ const runtimes = listRuntimes()
67
+
68
+ expect(runtimes).toContain("nodejs")
69
+ expect(runtimes).toContain("unknown")
70
+ })
71
+
72
+ test("isBrowser detects browser context", () => {
73
+ const browserWindow = { document: { title: "mock" } }
74
+ vi.stubGlobal("window", browserWindow)
75
+ vi.stubGlobal("self", browserWindow)
76
+
77
+ expect(isBrowser()).toBe(true)
78
+ vi.unstubAllGlobals()
79
+ })
80
+
81
+ test("getRuntimeContextBrowser returns browser globals", () => {
82
+ const browserWindow = { document: { title: "mock" } }
83
+ vi.stubGlobal("window", browserWindow)
84
+ vi.stubGlobal("self", browserWindow)
85
+
86
+ const context = getRuntimeContextBrowser()
87
+
88
+ expect(context.window).toBe(browserWindow)
89
+ expect(context.self).toBe(browserWindow)
90
+ vi.unstubAllGlobals()
91
+ })
92
+
93
+ test("useBrowser executes matched handler", { skip: false }, () => {
94
+ const browserWindow = { document: { title: "mock" } }
95
+ vi.stubGlobal("window", browserWindow)
96
+ vi.stubGlobal("self", browserWindow)
97
+
98
+ const browserResult = useBrowser(
99
+ (context) => context.window.document.title,
100
+ () => "fallback",
101
+ )
102
+
103
+ expect(browserResult).toBe("mock")
104
+ vi.unstubAllGlobals()
105
+ })
106
+
107
+ test("isNodejs detects Node.js runtime", () => {
108
+ expect(isNodejs()).toBe(true)
109
+ })
110
+
111
+ test("getRuntimeContextNodejs returns node globals", () => {
112
+ const context = getRuntimeContextNodejs()
113
+
114
+ expect(context.global).toBe(globalThis)
115
+ expect(context.globalThis).toBe(globalThis)
116
+ })
117
+
118
+ test("useNodejs executes node handler", () => {
119
+ const nodeResult = useNodejs(
120
+ (context) => context.globalThis === globalThis,
121
+ () => false,
122
+ )
123
+
124
+ expect(nodeResult).toBe(true)
125
+ })
126
+
127
+ test("isDeno detects deno runtime", () => {
128
+ vi.stubGlobal("Deno", { version: { deno: "2.0.0" } })
129
+
130
+ expect(isDeno()).toBe(true)
131
+ vi.unstubAllGlobals()
132
+ })
133
+
134
+ test("getRuntimeContextDeno returns deno context", () => {
135
+ vi.stubGlobal("Deno", { version: { deno: "2.0.0" } })
136
+ const context = getRuntimeContextDeno()
137
+
138
+ expect(context.global).toBe(globalThis)
139
+ vi.unstubAllGlobals()
140
+ })
141
+
142
+ test("useDeno executes deno handler", () => {
143
+ vi.stubGlobal("Deno", { version: { deno: "2.0.0" } })
144
+ const denoResult = useDeno(
145
+ (context) => context.globalThis === globalThis,
146
+ () => false,
147
+ )
148
+
149
+ expect(denoResult).toBe(true)
150
+ vi.unstubAllGlobals()
151
+ })
152
+
153
+ test("isBun detects bun runtime", () => {
154
+ vi.stubGlobal("Bun", { version: "1.0.0" })
155
+
156
+ expect(isBun()).toBe(true)
157
+ vi.unstubAllGlobals()
158
+ })
159
+
160
+ test("getRuntimeContextBun returns bun context", () => {
161
+ const bunGlobal = { version: "1.0.0" }
162
+ vi.stubGlobal("Bun", bunGlobal)
163
+ const context = getRuntimeContextBun()
164
+
165
+ expect(context.Bun).toBe(bunGlobal)
166
+ vi.unstubAllGlobals()
167
+ })
168
+
169
+ test("useBun executes bun handler", () => {
170
+ vi.stubGlobal("Bun", { version: "1.0.0" })
171
+ const bunResult = useBun(
172
+ (context) => typeof context.Bun.version,
173
+ () => "fallback",
174
+ )
175
+
176
+ expect(bunResult).toBe("string")
177
+ vi.unstubAllGlobals()
178
+ })
179
+
180
+ test("isWebWorker detects web worker runtime", () => {
181
+ class WorkerGlobalScopeMock {
182
+ readonly internalType = "worker"
183
+ }
184
+ class ServiceWorkerGlobalScopeMock extends WorkerGlobalScopeMock {
185
+ readonly internalTypeService = "service-worker"
186
+ }
187
+
188
+ vi.stubGlobal("WorkerGlobalScope", WorkerGlobalScopeMock)
189
+ vi.stubGlobal("ServiceWorkerGlobalScope", ServiceWorkerGlobalScopeMock)
190
+ vi.stubGlobal("self", new WorkerGlobalScopeMock())
191
+ vi.stubGlobal("process", undefined)
192
+ vi.stubGlobal("window", undefined)
193
+
194
+ expect(isWebWorker()).toBe(true)
195
+ vi.unstubAllGlobals()
196
+ })
197
+
198
+ test("getRuntimeContextWebWorker returns worker context", () => {
199
+ class WorkerGlobalScopeMock {
200
+ readonly internalType = "worker"
201
+ }
202
+
203
+ const selfWorker = new WorkerGlobalScopeMock()
204
+ vi.stubGlobal("WorkerGlobalScope", WorkerGlobalScopeMock)
205
+ vi.stubGlobal("ServiceWorkerGlobalScope", undefined)
206
+ vi.stubGlobal("self", selfWorker)
207
+
208
+ const context = getRuntimeContextWebWorker()
209
+
210
+ expect(context.self).toBe(selfWorker)
211
+ vi.unstubAllGlobals()
212
+ })
213
+
214
+ test("useWebWorker executes worker handler", () => {
215
+ class WorkerGlobalScopeMock {
216
+ readonly internalType = "worker"
217
+ }
218
+
219
+ const selfWorker = new WorkerGlobalScopeMock()
220
+ vi.stubGlobal("WorkerGlobalScope", WorkerGlobalScopeMock)
221
+ vi.stubGlobal("ServiceWorkerGlobalScope", undefined)
222
+ vi.stubGlobal("self", selfWorker)
223
+ vi.stubGlobal("process", undefined)
224
+ vi.stubGlobal("window", undefined)
225
+
226
+ const workerResult = useWebWorker(
227
+ (context) => typeof context.self === "object",
228
+ () => false,
229
+ )
230
+
231
+ expect(workerResult).toBe(true)
232
+ vi.unstubAllGlobals()
233
+ })
234
+
235
+ test("isServiceWorker detects service worker runtime", () => {
236
+ class WorkerGlobalScopeMock {
237
+ readonly internalType = "worker"
238
+ }
239
+ class ServiceWorkerGlobalScopeMock extends WorkerGlobalScopeMock {
240
+ readonly internalTypeService = "service-worker"
241
+ }
242
+
243
+ vi.stubGlobal("WorkerGlobalScope", WorkerGlobalScopeMock)
244
+ vi.stubGlobal("ServiceWorkerGlobalScope", ServiceWorkerGlobalScopeMock)
245
+ vi.stubGlobal("self", new ServiceWorkerGlobalScopeMock())
246
+ vi.stubGlobal("process", undefined)
247
+ vi.stubGlobal("window", undefined)
248
+
249
+ expect(isServiceWorker()).toBe(true)
250
+ vi.unstubAllGlobals()
251
+ })
252
+
253
+ test("getServiceWorkerContext returns service worker context", () => {
254
+ class WorkerGlobalScopeMock {
255
+ readonly internalType = "worker"
256
+ }
257
+ class ServiceWorkerGlobalScopeMock extends WorkerGlobalScopeMock {
258
+ readonly internalTypeService = "service-worker"
259
+ }
260
+
261
+ const selfServiceWorker = new ServiceWorkerGlobalScopeMock()
262
+ vi.stubGlobal("WorkerGlobalScope", WorkerGlobalScopeMock)
263
+ vi.stubGlobal("ServiceWorkerGlobalScope", ServiceWorkerGlobalScopeMock)
264
+ vi.stubGlobal("self", selfServiceWorker)
265
+
266
+ const context = getServiceWorkerContext()
267
+
268
+ expect(context.self).toBe(selfServiceWorker)
269
+ vi.unstubAllGlobals()
270
+ })
271
+
272
+ test("useServiceWorker executes service worker handler", () => {
273
+ class WorkerGlobalScopeMock {
274
+ readonly internalType = "worker"
275
+ }
276
+ class ServiceWorkerGlobalScopeMock extends WorkerGlobalScopeMock {
277
+ readonly internalTypeService = "service-worker"
278
+ }
279
+
280
+ const selfServiceWorker = new ServiceWorkerGlobalScopeMock()
281
+ vi.stubGlobal("WorkerGlobalScope", WorkerGlobalScopeMock)
282
+ vi.stubGlobal("ServiceWorkerGlobalScope", ServiceWorkerGlobalScopeMock)
283
+ vi.stubGlobal("self", selfServiceWorker)
284
+ vi.stubGlobal("process", undefined)
285
+ vi.stubGlobal("window", undefined)
286
+
287
+ const serviceWorkerResult = useServiceWorker(
288
+ (context) => typeof context.self === "object",
289
+ () => false,
290
+ )
291
+
292
+ expect(serviceWorkerResult).toBe(true)
293
+ vi.unstubAllGlobals()
294
+ })
295
+
296
+ test("isUnknown detects non-unknown runtime in current process", () => {
297
+ expect(isUnknown()).toBe(false)
298
+ })
299
+
300
+ test("getRuntimeContextUnknown returns unknown context", () => {
301
+ const context = getRuntimeContextUnknown()
302
+
303
+ expect(context.global).toBeUndefined()
304
+ })
305
+
306
+ test("useUnknown executes fallback in current process", () => {
307
+ const fallbackResult = useUnknown(
308
+ (context) => context.global,
309
+ () => "fallback",
310
+ )
311
+
312
+ expect(fallbackResult).toBe("fallback")
313
+ })
314
+
315
+ test("getRuntimeRegistryItem returns matched runtime registry item", () => {
316
+ const item = getRuntimeRegistryItem()
317
+
318
+ expect(item.runtime).toBe("nodejs")
319
+ expect(typeof item.detect).toBe("function")
320
+ })
321
+
322
+ test("getRuntime resolves current runtime key", () => {
323
+ expect(getRuntime()).toBe("nodejs")
324
+ })
325
+
326
+ test("getRuntimeFlags resolves runtime detector outputs", () => {
327
+ const flags = getRuntimeFlags()
328
+
329
+ expect(flags.browser).toBe(false)
330
+ expect(flags.nodejs).toBe(true)
331
+ expect(flags.unknown).toBe(false)
332
+ })
333
+
334
+ test("isSatisfiesRuntime checks runtime and throws for missing", () => {
335
+ expect(isSatisfiesRuntime("nodejs")).toBe(true)
336
+ expect(() => isSatisfiesRuntime("runtime-not-found")).toThrow(
337
+ "is not registered",
338
+ )
339
+ })
340
+
341
+ test("isSatisfiesRuntimes checks runtime conditions and throws for missing", () => {
342
+ expect(isSatisfiesRuntimes({ nodejs: true })).toBe(true)
343
+ expect(isSatisfiesRuntimes({ nodejs: false })).toBe(false)
344
+ expect(() => isSatisfiesRuntimes({ "runtime-not-found": true })).toThrow(
345
+ "is not registered",
346
+ )
347
+ })
348
+
349
+ test("useRuntimes executes runtime specific handler, default handler, and throws without handlers", () => {
350
+ const nodeResult = useRuntimes({
351
+ nodejs: () => "node",
352
+ default: () => "default",
353
+ })
354
+ const defaultResult = useRuntimes({
355
+ browser: () => "browser",
356
+ default: () => "default",
357
+ })
358
+
359
+ expect(nodeResult).toBe("node")
360
+ expect(defaultResult).toBe("default")
361
+ expect(() => useRuntimes({ browser: () => "browser" })).toThrow(
362
+ "Neither runtime-specific nor default handler provided",
363
+ )
364
+ })
@@ -0,0 +1,4 @@
1
+ import { test } from "vitest"
2
+
3
+ test.todo("getSnapshot does not need to be tested in detail")
4
+ test.todo("printSnapshot does not need to be tested in detail")
@@ -0,0 +1,190 @@
1
+ import { expect, test } from "vitest"
2
+
3
+ import type { StandardSchemaV1 } from "@standard-schema/spec"
4
+ import {
5
+ VariableManager,
6
+ getVariable,
7
+ getVariableHost,
8
+ getVariableHosted,
9
+ loadVariable,
10
+ parseVariable,
11
+ verifyVariable,
12
+ } from "#Source/environment/variable.ts"
13
+
14
+ interface TestVariable {
15
+ APP_REQUIRED: string
16
+ APP_OPTIONAL?: string | undefined
17
+ }
18
+ const createSyncSchema = (): StandardSchemaV1<Record<string, string>, TestVariable> => {
19
+ const isRecord = (value: unknown): value is Record<string, unknown> => {
20
+ return typeof value === "object" && value !== null
21
+ }
22
+
23
+ return {
24
+ "~standard": {
25
+ version: 1 as const,
26
+ vendor: "mobius-variable-spec",
27
+ validate: (value: unknown): StandardSchemaV1.Result<TestVariable> => {
28
+ if (isRecord(value) === false) {
29
+ return {
30
+ issues: [{ message: "APP_REQUIRED is required" }],
31
+ }
32
+ }
33
+
34
+ const appRequired = value["APP_REQUIRED"]
35
+ const appOptional = value["APP_OPTIONAL"]
36
+
37
+ if (typeof appRequired !== "string") {
38
+ return {
39
+ issues: [{ message: "APP_REQUIRED is required" }],
40
+ }
41
+ }
42
+
43
+ const variable: TestVariable = {
44
+ APP_REQUIRED: appRequired,
45
+ }
46
+
47
+ if (typeof appOptional === "string") {
48
+ variable.APP_OPTIONAL = appOptional
49
+ }
50
+
51
+ return {
52
+ value: variable,
53
+ }
54
+ },
55
+ },
56
+ }
57
+ }
58
+ const createAsyncSchema = (): StandardSchemaV1<Record<string, string>, TestVariable> => {
59
+ return {
60
+ "~standard": {
61
+ version: 1 as const,
62
+ vendor: "mobius-variable-spec-async",
63
+ validate: async (_value: unknown): Promise<StandardSchemaV1.Result<TestVariable>> => {
64
+ await Promise.resolve()
65
+ return {
66
+ value: {
67
+ APP_REQUIRED: "ok",
68
+ },
69
+ }
70
+ },
71
+ },
72
+ }
73
+ }
74
+
75
+ test("parseVariable parses dotenv style content", () => {
76
+ const result = parseVariable({
77
+ variableContent: "APP_REQUIRED=enabled\nAPP_OPTIONAL=maybe\n",
78
+ })
79
+
80
+ expect(result["APP_REQUIRED"]).toBe("enabled")
81
+ expect(result["APP_OPTIONAL"]).toBe("maybe")
82
+ })
83
+
84
+ test("getVariableHost resolves runtime variable host", () => {
85
+ const variableHost = getVariableHost()
86
+
87
+ expect(variableHost).toBeTypeOf("object")
88
+ })
89
+
90
+ test("loadVariable loads prefixed variables and keeps inline host priority", () => {
91
+ const variableHost = {
92
+ APP_REQUIRED: "inline",
93
+ APP_OLD: "legacy",
94
+ OTHER_KEY: "ignore",
95
+ }
96
+ const result = loadVariable({
97
+ variable: {
98
+ APP_REQUIRED: "from-file",
99
+ APP_OPTIONAL: "file-only",
100
+ OTHER_KEY: "other",
101
+ },
102
+ variablePrefixs: ["APP_"],
103
+ variableHost,
104
+ })
105
+
106
+ expect(result.variableLoaded["APP_REQUIRED"]).toBe("from-file")
107
+ expect(result.variableLoaded["APP_OPTIONAL"]).toBe("file-only")
108
+ expect(result.variableLoaded["APP_OLD"]).toBe("legacy")
109
+ expect(result.variableLoaded["OTHER_KEY"]).toBeUndefined()
110
+ })
111
+
112
+ test("verifyVariable validates sync schema and throws on async or invalid results", () => {
113
+ const validSchema = createSyncSchema()
114
+ const validResult = verifyVariable({
115
+ variable: { APP_REQUIRED: "ok" },
116
+ variableSchema: validSchema,
117
+ })
118
+ expect(validResult.APP_REQUIRED).toBe("ok")
119
+
120
+ const invalidSchema = createSyncSchema()
121
+ expect(() =>
122
+ verifyVariable({
123
+ variable: { APP_OPTIONAL: "missing-required" },
124
+ variableSchema: invalidSchema,
125
+ }),
126
+ ).toThrow("Variable validation failed")
127
+
128
+ const asyncSchema = createAsyncSchema()
129
+ expect(() =>
130
+ verifyVariable({
131
+ variable: { APP_REQUIRED: "ok" },
132
+ variableSchema: asyncSchema,
133
+ }),
134
+ ).toThrow("expected synchronous validation")
135
+ })
136
+
137
+ test("getVariableHosted returns host variables as key-value map", () => {
138
+ const hosted = getVariableHosted()
139
+
140
+ expect(hosted).toBeTypeOf("object")
141
+ })
142
+
143
+ test("getVariable resolves from content path and host path", () => {
144
+ const schema = createSyncSchema()
145
+ const fromContent = getVariable({
146
+ variableContent: "APP_REQUIRED=file\nAPP_OPTIONAL=extra\n",
147
+ variablePrefixs: ["APP_"],
148
+ variableHost: {},
149
+ variableSchema: schema,
150
+ })
151
+ const fromHost = getVariable({
152
+ variableHost: { APP_REQUIRED: "host" },
153
+ variableSchema: schema,
154
+ })
155
+
156
+ expect(fromContent.APP_REQUIRED).toBe("file")
157
+ expect(fromHost.APP_REQUIRED).toBe("host")
158
+ })
159
+
160
+ test("VariableManager.getFreshVariable refreshes current variables", () => {
161
+ const schema = createSyncSchema()
162
+ const manager = new VariableManager({
163
+ variableHost: { APP_REQUIRED: "fresh" },
164
+ variableSchema: schema,
165
+ })
166
+ const freshVariable = manager.getFreshVariable()
167
+
168
+ expect(freshVariable.APP_REQUIRED).toBe("fresh")
169
+ })
170
+
171
+ test("VariableManager.getSchema returns schema instance", () => {
172
+ const schema = createSyncSchema()
173
+ const manager = new VariableManager({
174
+ variableHost: { APP_REQUIRED: "schema" },
175
+ variableSchema: schema,
176
+ })
177
+
178
+ expect(manager.getSchema()).toBe(schema)
179
+ })
180
+
181
+ test("VariableManager.getVariable returns cached variable snapshot", () => {
182
+ const schema = createSyncSchema()
183
+ const manager = new VariableManager({
184
+ variableHost: { APP_REQUIRED: "cached" },
185
+ variableSchema: schema,
186
+ })
187
+ const cachedVariable = manager.getVariable()
188
+
189
+ expect(cachedVariable.APP_REQUIRED).toBe("cached")
190
+ })