@evolu/common 8.7.0 → 8.9.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 (302) hide show
  1. package/dist/src/Array.d.ts +21 -21
  2. package/dist/src/Array.js +5 -5
  3. package/dist/src/Assert.d.ts +498 -141
  4. package/dist/src/Assert.d.ts.map +1 -1
  5. package/dist/src/Assert.js +648 -99
  6. package/dist/src/{Buffer.d.ts → Binary.d.ts} +85 -12
  7. package/dist/src/Binary.d.ts.map +1 -0
  8. package/dist/src/{Buffer.js → Binary.js} +206 -24
  9. package/dist/src/Console.d.ts +2 -10
  10. package/dist/src/Console.d.ts.map +1 -1
  11. package/dist/src/Console.js +6 -12
  12. package/dist/src/Crypto.d.ts +5 -5
  13. package/dist/src/Crypto.js +1 -1
  14. package/dist/src/Eq.d.ts +35 -40
  15. package/dist/src/Eq.d.ts.map +1 -1
  16. package/dist/src/Eq.js +51 -87
  17. package/dist/src/Function.d.ts +9 -14
  18. package/dist/src/Function.d.ts.map +1 -1
  19. package/dist/src/Function.js +7 -9
  20. package/dist/src/Http.d.ts +6 -7
  21. package/dist/src/Http.d.ts.map +1 -1
  22. package/dist/src/LeakDetector.d.ts.map +1 -1
  23. package/dist/src/LeakDetector.js +1 -0
  24. package/dist/src/LockManager.d.ts +1 -1
  25. package/dist/src/LockManager.js +1 -1
  26. package/dist/src/Lookup.d.ts +3 -2
  27. package/dist/src/Lookup.d.ts.map +1 -1
  28. package/dist/src/Lookup.js +4 -3
  29. package/dist/src/Number.d.ts +1 -1
  30. package/dist/src/Number.d.ts.map +1 -1
  31. package/dist/src/Number.js +1 -1
  32. package/dist/src/Object.d.ts +4 -4
  33. package/dist/src/Object.d.ts.map +1 -1
  34. package/dist/src/Object.js +5 -4
  35. package/dist/src/Option.d.ts +1 -2
  36. package/dist/src/Option.d.ts.map +1 -1
  37. package/dist/src/Option.js +1 -2
  38. package/dist/src/Platform.js +2 -0
  39. package/dist/src/Polyfills.d.ts +6 -0
  40. package/dist/src/Polyfills.d.ts.map +1 -1
  41. package/dist/src/Polyfills.js +10 -1
  42. package/dist/src/Random.d.ts +1 -1
  43. package/dist/src/Result.d.ts +49 -54
  44. package/dist/src/Result.d.ts.map +1 -1
  45. package/dist/src/Result.js +17 -22
  46. package/dist/src/Set.d.ts +6 -6
  47. package/dist/src/Set.js +2 -2
  48. package/dist/src/Sqlite.js +1 -1
  49. package/dist/src/String.js +3 -2
  50. package/dist/src/Task.d.ts +32 -32
  51. package/dist/src/Task.d.ts.map +1 -1
  52. package/dist/src/Task.js +20 -22
  53. package/dist/src/Test.d.ts +31 -3
  54. package/dist/src/Test.d.ts.map +1 -1
  55. package/dist/src/Test.js +46 -3
  56. package/dist/src/Time.d.ts +3 -11
  57. package/dist/src/Time.d.ts.map +1 -1
  58. package/dist/src/Time.js +10 -18
  59. package/dist/src/Type.d.ts +87 -85
  60. package/dist/src/Type.d.ts.map +1 -1
  61. package/dist/src/Type.js +70 -63
  62. package/dist/src/Types.d.ts +12 -12
  63. package/dist/src/Types.js +1 -1
  64. package/dist/src/WebSocket.d.ts +4 -7
  65. package/dist/src/WebSocket.d.ts.map +1 -1
  66. package/dist/src/Worker.d.ts +1 -1
  67. package/dist/src/Worker.js +7 -7
  68. package/dist/src/index.d.ts +1 -1
  69. package/dist/src/index.js +1 -1
  70. package/dist/src/intl/_en.d.ts.map +1 -1
  71. package/dist/src/intl/_en.js +1 -1
  72. package/dist/src/intl/ar.d.ts.map +1 -1
  73. package/dist/src/intl/ar.js +1 -1
  74. package/dist/src/intl/bn.d.ts.map +1 -1
  75. package/dist/src/intl/bn.js +1 -1
  76. package/dist/src/intl/ca.d.ts.map +1 -1
  77. package/dist/src/intl/ca.js +1 -1
  78. package/dist/src/intl/cs.d.ts.map +1 -1
  79. package/dist/src/intl/cs.js +1 -1
  80. package/dist/src/intl/da.d.ts.map +1 -1
  81. package/dist/src/intl/da.js +1 -1
  82. package/dist/src/intl/de.d.ts.map +1 -1
  83. package/dist/src/intl/de.js +1 -1
  84. package/dist/src/intl/el.d.ts.map +1 -1
  85. package/dist/src/intl/el.js +1 -1
  86. package/dist/src/intl/es.d.ts.map +1 -1
  87. package/dist/src/intl/es.js +1 -1
  88. package/dist/src/intl/fa.d.ts.map +1 -1
  89. package/dist/src/intl/fa.js +1 -1
  90. package/dist/src/intl/fi.d.ts.map +1 -1
  91. package/dist/src/intl/fi.js +1 -1
  92. package/dist/src/intl/fil.d.ts.map +1 -1
  93. package/dist/src/intl/fil.js +1 -1
  94. package/dist/src/intl/fr.d.ts.map +1 -1
  95. package/dist/src/intl/fr.js +1 -1
  96. package/dist/src/intl/he.d.ts.map +1 -1
  97. package/dist/src/intl/he.js +1 -1
  98. package/dist/src/intl/hi.d.ts.map +1 -1
  99. package/dist/src/intl/hi.js +1 -1
  100. package/dist/src/intl/hr.d.ts.map +1 -1
  101. package/dist/src/intl/hr.js +1 -1
  102. package/dist/src/intl/hu.d.ts.map +1 -1
  103. package/dist/src/intl/hu.js +1 -1
  104. package/dist/src/intl/id.d.ts.map +1 -1
  105. package/dist/src/intl/id.js +1 -1
  106. package/dist/src/intl/it.d.ts.map +1 -1
  107. package/dist/src/intl/it.js +1 -1
  108. package/dist/src/intl/ja.d.ts.map +1 -1
  109. package/dist/src/intl/ja.js +1 -1
  110. package/dist/src/intl/ko.d.ts.map +1 -1
  111. package/dist/src/intl/ko.js +1 -1
  112. package/dist/src/intl/ml.d.ts.map +1 -1
  113. package/dist/src/intl/ml.js +1 -1
  114. package/dist/src/intl/mr.d.ts.map +1 -1
  115. package/dist/src/intl/mr.js +1 -1
  116. package/dist/src/intl/ms.d.ts.map +1 -1
  117. package/dist/src/intl/ms.js +1 -1
  118. package/dist/src/intl/nb.d.ts.map +1 -1
  119. package/dist/src/intl/nb.js +1 -1
  120. package/dist/src/intl/nl.d.ts.map +1 -1
  121. package/dist/src/intl/nl.js +1 -1
  122. package/dist/src/intl/pa.d.ts.map +1 -1
  123. package/dist/src/intl/pa.js +1 -1
  124. package/dist/src/intl/pl.d.ts.map +1 -1
  125. package/dist/src/intl/pl.js +1 -1
  126. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  127. package/dist/src/intl/pt-BR.js +1 -1
  128. package/dist/src/intl/pt.d.ts.map +1 -1
  129. package/dist/src/intl/pt.js +1 -1
  130. package/dist/src/intl/ro.d.ts.map +1 -1
  131. package/dist/src/intl/ro.js +1 -1
  132. package/dist/src/intl/sk.d.ts.map +1 -1
  133. package/dist/src/intl/sk.js +1 -1
  134. package/dist/src/intl/sl.d.ts.map +1 -1
  135. package/dist/src/intl/sl.js +1 -1
  136. package/dist/src/intl/sv.d.ts.map +1 -1
  137. package/dist/src/intl/sv.js +1 -1
  138. package/dist/src/intl/sw.d.ts.map +1 -1
  139. package/dist/src/intl/sw.js +1 -1
  140. package/dist/src/intl/ta.d.ts.map +1 -1
  141. package/dist/src/intl/ta.js +1 -1
  142. package/dist/src/intl/te.d.ts.map +1 -1
  143. package/dist/src/intl/te.js +1 -1
  144. package/dist/src/intl/th.d.ts.map +1 -1
  145. package/dist/src/intl/th.js +1 -1
  146. package/dist/src/intl/tr.d.ts.map +1 -1
  147. package/dist/src/intl/tr.js +1 -1
  148. package/dist/src/intl/uk.d.ts.map +1 -1
  149. package/dist/src/intl/uk.js +1 -1
  150. package/dist/src/intl/ur.d.ts.map +1 -1
  151. package/dist/src/intl/ur.js +1 -1
  152. package/dist/src/intl/vi.d.ts.map +1 -1
  153. package/dist/src/intl/vi.js +1 -1
  154. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  155. package/dist/src/intl/zh-CN.js +1 -1
  156. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  157. package/dist/src/intl/zh-TW.js +1 -1
  158. package/dist/src/local-first/Evolu.d.ts +12 -12
  159. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  160. package/dist/src/local-first/Evolu.js +2 -2
  161. package/dist/src/local-first/Protocol.d.ts +14 -76
  162. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  163. package/dist/src/local-first/Protocol.js +29 -206
  164. package/dist/src/local-first/Query.d.ts +11 -11
  165. package/dist/src/local-first/Query.js +6 -6
  166. package/dist/src/local-first/Relay.d.ts +2 -2
  167. package/dist/src/local-first/Schema.d.ts +3 -5
  168. package/dist/src/local-first/Schema.d.ts.map +1 -1
  169. package/dist/src/local-first/Schema.js +2 -4
  170. package/dist/src/local-first/Shared.d.ts.map +1 -1
  171. package/dist/src/local-first/Shared.js +5 -5
  172. package/dist/src/local-first/Storage.js +4 -4
  173. package/dist/src/local-first/Timestamp.d.ts +9 -0
  174. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  175. package/dist/src/local-first/Timestamp.js +13 -10
  176. package/package.json +7 -4
  177. package/src/Array.test.ts +809 -0
  178. package/src/Array.ts +21 -21
  179. package/src/Assert.test.ts +1232 -0
  180. package/src/Assert.ts +1023 -227
  181. package/src/BigInt.test.ts +28 -0
  182. package/src/Binary.test.ts +1128 -0
  183. package/src/{Buffer.ts → Binary.ts} +273 -46
  184. package/src/Brand.test.ts +90 -0
  185. package/src/Cache.test.ts +183 -0
  186. package/src/Callbacks.test.ts +66 -0
  187. package/src/Console.test.ts +809 -0
  188. package/src/Console.ts +9 -17
  189. package/src/Crypto.test.ts +138 -0
  190. package/src/Crypto.ts +5 -5
  191. package/src/Eq.test.ts +333 -0
  192. package/src/Eq.ts +110 -176
  193. package/src/Error.test.ts +118 -0
  194. package/src/Function.test.ts +204 -0
  195. package/src/Function.ts +10 -15
  196. package/src/Http.test.ts +816 -0
  197. package/src/Http.ts +6 -7
  198. package/src/LeakDetector.test.ts +210 -0
  199. package/src/LeakDetector.ts +1 -0
  200. package/src/LockManager.ts +2 -2
  201. package/src/Lookup.test.ts +418 -0
  202. package/src/Lookup.ts +4 -3
  203. package/src/Microtask.test.ts +140 -0
  204. package/src/Number.test.ts +218 -0
  205. package/src/Number.ts +2 -2
  206. package/src/Object.test.ts +293 -0
  207. package/src/Object.ts +5 -4
  208. package/src/Option.test.ts +104 -0
  209. package/src/Option.ts +1 -2
  210. package/src/Order.test.ts +56 -0
  211. package/src/Platform.ts +2 -0
  212. package/src/Polyfills.ts +9 -4
  213. package/src/Random.test.ts +89 -0
  214. package/src/Random.ts +1 -1
  215. package/src/Ref.test.ts +139 -0
  216. package/src/RefCount.test.ts +184 -0
  217. package/src/Relation.test.ts +505 -0
  218. package/src/Resource.test.ts +4623 -0
  219. package/src/Result.test.ts +1829 -0
  220. package/src/Result.ts +49 -54
  221. package/src/Schedule.test.ts +1554 -0
  222. package/src/Set.test.ts +279 -0
  223. package/src/Set.ts +6 -6
  224. package/src/Sqlite.ts +1 -1
  225. package/src/Store.test.ts +288 -0
  226. package/src/String.test.ts +54 -0
  227. package/src/String.ts +3 -3
  228. package/src/Task.test.ts +10820 -0
  229. package/src/Task.ts +38 -43
  230. package/src/Test.test.ts +88 -0
  231. package/src/Test.ts +49 -3
  232. package/src/Time.test.ts +926 -0
  233. package/src/Time.ts +13 -21
  234. package/src/Type.base64.test.ts +88 -0
  235. package/src/Type.test.ts +20465 -0
  236. package/src/Type.ts +114 -111
  237. package/src/Types.test.ts +324 -0
  238. package/src/Types.ts +13 -13
  239. package/src/WebSocket.ts +4 -7
  240. package/src/Worker.test.ts +755 -0
  241. package/src/Worker.ts +13 -18
  242. package/src/index.ts +1 -1
  243. package/src/intl/_en.ts +1 -1
  244. package/src/intl/ar.ts +1 -1
  245. package/src/intl/bn.ts +1 -1
  246. package/src/intl/ca.ts +1 -1
  247. package/src/intl/cs.ts +1 -1
  248. package/src/intl/da.ts +1 -1
  249. package/src/intl/de.ts +1 -1
  250. package/src/intl/el.ts +1 -1
  251. package/src/intl/es.ts +1 -1
  252. package/src/intl/fa.ts +1 -1
  253. package/src/intl/fi.ts +1 -1
  254. package/src/intl/fil.ts +1 -1
  255. package/src/intl/fr.ts +1 -1
  256. package/src/intl/he.ts +1 -1
  257. package/src/intl/hi.ts +1 -1
  258. package/src/intl/hr.ts +1 -1
  259. package/src/intl/hu.ts +1 -1
  260. package/src/intl/id.ts +1 -1
  261. package/src/intl/intl.test.ts +3106 -0
  262. package/src/intl/it.ts +1 -1
  263. package/src/intl/ja.ts +1 -1
  264. package/src/intl/ko.ts +1 -1
  265. package/src/intl/ml.ts +1 -1
  266. package/src/intl/mr.ts +1 -1
  267. package/src/intl/ms.ts +1 -1
  268. package/src/intl/nb.ts +1 -1
  269. package/src/intl/nl.ts +1 -1
  270. package/src/intl/pa.ts +1 -1
  271. package/src/intl/pl.ts +1 -1
  272. package/src/intl/pt-BR.ts +1 -1
  273. package/src/intl/pt.ts +1 -1
  274. package/src/intl/ro.ts +1 -1
  275. package/src/intl/sk.ts +1 -1
  276. package/src/intl/sl.ts +1 -1
  277. package/src/intl/sv.ts +1 -1
  278. package/src/intl/sw.ts +1 -1
  279. package/src/intl/ta.ts +1 -1
  280. package/src/intl/te.ts +1 -1
  281. package/src/intl/th.ts +1 -1
  282. package/src/intl/tr.ts +1 -1
  283. package/src/intl/uk.ts +1 -1
  284. package/src/intl/ur.ts +1 -1
  285. package/src/intl/vi.ts +1 -1
  286. package/src/intl/zh-CN.ts +1 -1
  287. package/src/intl/zh-TW.ts +1 -1
  288. package/src/local-first/Evolu.test.ts +1921 -0
  289. package/src/local-first/Evolu.ts +18 -14
  290. package/src/local-first/Owner.test.ts +83 -0
  291. package/src/local-first/Protocol.test.ts +1037 -0
  292. package/src/local-first/Protocol.ts +58 -261
  293. package/src/local-first/Query.test.ts +349 -0
  294. package/src/local-first/Query.ts +11 -11
  295. package/src/local-first/Relay.ts +2 -2
  296. package/src/local-first/Schema.ts +3 -5
  297. package/src/local-first/Shared.test.ts +1828 -0
  298. package/src/local-first/Shared.ts +9 -8
  299. package/src/local-first/Storage.ts +4 -4
  300. package/src/local-first/Timestamp.test.ts +374 -0
  301. package/src/local-first/Timestamp.ts +22 -10
  302. package/dist/src/Buffer.d.ts.map +0 -1
@@ -0,0 +1,809 @@
1
+ import { describe, it, mock } from "node:test";
2
+ import {
3
+ assertEqual,
4
+ assertFalse,
5
+ assertLength,
6
+ assertSame,
7
+ assertTrue,
8
+ } from "./Assert.ts";
9
+
10
+ import {
11
+ createConsole,
12
+ createConsoleArrayOutput,
13
+ createConsoleFormatter,
14
+ createConsoleStoreOutput,
15
+ createMultiOutput,
16
+ createNativeConsoleOutput,
17
+ testCreateConsole,
18
+ type ConsoleEntry,
19
+ type ConsoleOutput,
20
+ } from "./Console.ts";
21
+ import { testCreateTime, type Millis } from "./Time.ts";
22
+ import { assertType, String } from "./Type.ts";
23
+
24
+ const createTimeDep = (startAt?: Millis) => ({
25
+ time: testCreateTime(startAt != null ? { startAt } : undefined),
26
+ });
27
+
28
+ const createTestOutput = (): ConsoleOutput & {
29
+ entries: Array<{
30
+ entry: ConsoleEntry;
31
+ formattedArgs: ReadonlyArray<unknown>;
32
+ }>;
33
+ } => {
34
+ const entries: Array<{
35
+ entry: ConsoleEntry;
36
+ formattedArgs: ReadonlyArray<unknown>;
37
+ }> = [];
38
+ return {
39
+ entries,
40
+ write: (entry, formatter) => {
41
+ entries.push({
42
+ entry,
43
+ formattedArgs: formatter ? formatter(entry) : entry.args,
44
+ });
45
+ },
46
+ };
47
+ };
48
+
49
+ describe("createConsole", () => {
50
+ it("logs at default level (log)", () => {
51
+ const output = createTestOutput();
52
+ const console = createConsole({ output });
53
+
54
+ console.trace("trace");
55
+ console.debug("debug");
56
+ console.log("log");
57
+ console.info("info");
58
+ console.warn("warn");
59
+ console.error("error");
60
+
61
+ assertEqual(
62
+ output.entries.map((e) => e.entry.method),
63
+ ["log", "info", "warn", "error"],
64
+ );
65
+ });
66
+
67
+ it("respects level filtering", () => {
68
+ const output = createTestOutput();
69
+ const console = createConsole({
70
+ output,
71
+ level: "warn",
72
+ });
73
+
74
+ console.debug("debug");
75
+ console.log("log");
76
+ console.info("info");
77
+ console.warn("warn");
78
+ console.error("error");
79
+
80
+ assertEqual(
81
+ output.entries.map((e) => e.entry.method),
82
+ ["warn", "error"],
83
+ );
84
+ });
85
+
86
+ it("silent level disables all logging", () => {
87
+ const output = createTestOutput();
88
+ const console = createConsole({
89
+ output,
90
+ level: "silent",
91
+ });
92
+
93
+ console.trace("trace");
94
+ console.debug("debug");
95
+ console.log("log");
96
+ console.info("info");
97
+ console.warn("warn");
98
+ console.error("error");
99
+
100
+ assertLength(output.entries, 0);
101
+ });
102
+
103
+ it("level can be changed at runtime", () => {
104
+ const output = createTestOutput();
105
+ const console = createConsole({ output });
106
+
107
+ console.debug("before");
108
+ console.setLevel("debug");
109
+ console.debug("after");
110
+
111
+ assertEqual(
112
+ output.entries.map((e) => e.entry.args[0]),
113
+ ["after"],
114
+ );
115
+ });
116
+
117
+ it("child inherits level at creation (static)", () => {
118
+ const output = createTestOutput();
119
+ const console = createConsole({ output, level: "info" });
120
+ const child = console.child("relay");
121
+
122
+ // Child inherits "info" level
123
+ child.debug("ignored");
124
+ child.info("logged");
125
+
126
+ // Parent change doesn't affect child (static inheritance)
127
+ console.setLevel("debug");
128
+ child.debug("still ignored");
129
+
130
+ assertEqual(
131
+ output.entries.map((e) => e.entry.args[0]),
132
+ ["logged"],
133
+ );
134
+ });
135
+
136
+ it("child can override level independently", () => {
137
+ const output = createTestOutput();
138
+ const console = createConsole({ output, level: "info" });
139
+ const child = console.child("relay");
140
+
141
+ child.setLevel("debug");
142
+ child.debug("logged");
143
+ // parent still at "info"
144
+ console.debug("ignored");
145
+
146
+ assertEqual(
147
+ output.entries.map((e) => e.entry.args[0]),
148
+ ["logged"],
149
+ );
150
+ });
151
+
152
+ it("setLevel(null) reverts to inherited level", () => {
153
+ const output = createTestOutput();
154
+ const console = createConsole({ output, level: "info" });
155
+ const child = console.child("relay");
156
+
157
+ child.setLevel("debug");
158
+ assertTrue(child.hasOwnLevel());
159
+
160
+ child.setLevel(null);
161
+ assertFalse(child.hasOwnLevel());
162
+ assertEqual(child.getLevel(), "info");
163
+ });
164
+
165
+ it("child adds path", () => {
166
+ const output = createTestOutput();
167
+ const console = createConsole({ output });
168
+ const child = console.child("relay").child("db");
169
+
170
+ child.info("message");
171
+
172
+ assertEqual(output.entries[0].entry.path, ["relay", "db"]);
173
+ });
174
+
175
+ it("child inherits formatter", () => {
176
+ const output = createTestOutput();
177
+ const formatter = (entry: ConsoleEntry) => ["prefix", ...entry.args];
178
+ const console = createConsole({
179
+ output,
180
+ formatter,
181
+ });
182
+ const child = console.child("relay");
183
+
184
+ child.info("message");
185
+
186
+ assertEqual(output.entries[0].formattedArgs, ["prefix", "message"]);
187
+ });
188
+
189
+ it("debug-level methods use debug level", () => {
190
+ const output = createTestOutput();
191
+ const console = createConsole({
192
+ output,
193
+ level: "log",
194
+ });
195
+
196
+ console.time("timer");
197
+ console.timeLog("timer");
198
+ console.timeEnd("timer");
199
+ console.dir({ foo: 1 });
200
+ console.table([1, 2, 3]);
201
+ console.count("counter");
202
+ console.countReset("counter");
203
+
204
+ assertLength(output.entries, 0);
205
+
206
+ console.setLevel("debug");
207
+
208
+ console.time("timer");
209
+ console.dir({ foo: 1 });
210
+
211
+ assertEqual(
212
+ output.entries.map((e) => e.entry.method),
213
+ ["time", "dir"],
214
+ );
215
+ });
216
+
217
+ it("debug-level methods skip formatter", () => {
218
+ const output = createTestOutput();
219
+ const formatter = mock.fn((entry: ConsoleEntry) => [
220
+ "formatted",
221
+ ...entry.args,
222
+ ]);
223
+ const console = createConsole({
224
+ output,
225
+ level: "debug",
226
+ formatter,
227
+ });
228
+
229
+ console.info("info message");
230
+ console.dir({ foo: 1 });
231
+
232
+ assertEqual(output.entries[0].formattedArgs, ["formatted", "info message"]);
233
+ assertEqual(output.entries[1].formattedArgs, [{ foo: 1 }]);
234
+ });
235
+
236
+ it("children tracking", () => {
237
+ const console = createConsole();
238
+ const child1 = console.child("a");
239
+ const child2 = console.child("b");
240
+ const grandchild = child1.child("c");
241
+
242
+ assertEqual(console.children.size, 2);
243
+ assertTrue(console.children.has(child1));
244
+ assertTrue(console.children.has(child2));
245
+ assertEqual(child1.children.size, 1);
246
+ assertTrue(child1.children.has(grandchild));
247
+ });
248
+
249
+ it("name property", () => {
250
+ const console = createConsole({ name: "root" });
251
+ const child = console.child("relay");
252
+
253
+ assertEqual(console.name, "root");
254
+ assertEqual(child.name, "relay");
255
+ });
256
+
257
+ it("write bypasses level filtering", () => {
258
+ const output = createTestOutput();
259
+ const console = createConsole({ output, level: "silent" });
260
+
261
+ const entry: ConsoleEntry = {
262
+ method: "debug",
263
+ path: ["worker"],
264
+ args: ["replayed"],
265
+ };
266
+ console.write(entry);
267
+
268
+ assertLength(output.entries, 1);
269
+ assertEqual(output.entries[0].entry, entry);
270
+ });
271
+
272
+ it("write passes formatter to output", () => {
273
+ const output = createTestOutput();
274
+ const formatter = (entry: ConsoleEntry) => ["fmt", ...entry.args];
275
+ const console = createConsole({ output, formatter });
276
+
277
+ const entry: ConsoleEntry = {
278
+ method: "info",
279
+ path: [],
280
+ args: ["msg"],
281
+ };
282
+ console.write(entry);
283
+
284
+ assertEqual(output.entries[0].formattedArgs, ["fmt", "msg"]);
285
+ });
286
+ });
287
+
288
+ describe("createNativeConsoleOutput", () => {
289
+ // oxlint-disable-next-line evolu/no-unnecessary-global-this -- These tests explicitly observe the global object console used by the native output.
290
+ const nativeConsole = globalThis.console;
291
+
292
+ it("calls the native console method with its console receiver", (t) => {
293
+ const logSpy = t.mock.method(nativeConsole, "info", () => undefined);
294
+ const output = createNativeConsoleOutput();
295
+
296
+ output.write({
297
+ method: "info",
298
+ path: [],
299
+ args: ["hello", "world"],
300
+ });
301
+
302
+ assertEqual(logSpy.mock.calls[0].arguments, ["hello", "world"]);
303
+ assertSame(logSpy.mock.calls[0].this, nativeConsole);
304
+ });
305
+
306
+ it("applies formatter", (t) => {
307
+ const logSpy = t.mock.method(nativeConsole, "info", () => undefined);
308
+ const output = createNativeConsoleOutput();
309
+ const formatter = (entry: ConsoleEntry) => ["prefix", ...entry.args];
310
+
311
+ output.write(
312
+ {
313
+ method: "info",
314
+ path: [],
315
+ args: ["message"],
316
+ },
317
+ formatter,
318
+ );
319
+
320
+ assertEqual(logSpy.mock.calls[0].arguments, ["prefix", "message"]);
321
+ });
322
+ });
323
+
324
+ describe("createConsoleFormatter", () => {
325
+ it("uses default time dep when not provided", () => {
326
+ const formatter = createConsoleFormatter()({
327
+ timestampFormat: "relative",
328
+ });
329
+ const entry: ConsoleEntry = {
330
+ method: "info",
331
+ path: [],
332
+ args: ["message"],
333
+ };
334
+
335
+ const result = formatter(entry);
336
+
337
+ // Should have a relative timestamp prefix
338
+ assertLength(result, 2);
339
+ assertType(String, result[0]);
340
+ assertTrue(/^\+\d+\.\d{3}s$/u.test(result[0]));
341
+ assertEqual(result[1], "message");
342
+ });
343
+
344
+ it("formats path", () => {
345
+ const formatter = createConsoleFormatter(createTimeDep())();
346
+ const entry: ConsoleEntry = {
347
+ method: "info",
348
+ path: ["relay", "db"],
349
+ args: ["message"],
350
+ };
351
+
352
+ const result = formatter(entry);
353
+
354
+ assertEqual(result, ["[relay] [db]", "message"]);
355
+ });
356
+
357
+ it("with no path returns args unchanged", () => {
358
+ const formatter = createConsoleFormatter(createTimeDep())();
359
+ const entry: ConsoleEntry = {
360
+ method: "info",
361
+ path: [],
362
+ args: ["message", 123],
363
+ };
364
+
365
+ const result = formatter(entry);
366
+
367
+ assertEqual(result, ["message", 123]);
368
+ });
369
+
370
+ it("relative timestamp", () => {
371
+ const time = testCreateTime({ startAt: 1000 as Millis });
372
+ const formatter = createConsoleFormatter({ time })({
373
+ timestampFormat: "relative",
374
+ });
375
+
376
+ const entry: ConsoleEntry = {
377
+ method: "info",
378
+ path: [],
379
+ args: ["first"],
380
+ };
381
+
382
+ const result1 = formatter(entry);
383
+ time.advance("1.5s");
384
+ const result2 = formatter({ ...entry, args: ["second"] });
385
+
386
+ assertEqual(result1, ["+0.000s", "first"]);
387
+ assertEqual(result2, ["+1.500s", "second"]);
388
+ });
389
+
390
+ it("relative timestamp with custom start time", () => {
391
+ const time = testCreateTime({ startAt: 1500 as Millis });
392
+ const formatter = createConsoleFormatter({ time })({
393
+ timestampFormat: "relative",
394
+ startTime: 500 as Millis,
395
+ });
396
+
397
+ const entry: ConsoleEntry = {
398
+ method: "info",
399
+ path: [],
400
+ args: ["message"],
401
+ };
402
+
403
+ const result = formatter(entry);
404
+
405
+ assertEqual(result, ["+1.000s", "message"]);
406
+ });
407
+
408
+ it("iso timestamp", () => {
409
+ const time = testCreateTime({
410
+ startAt: Date.UTC(2026, 0, 28, 14, 30, 0, 123) as Millis,
411
+ });
412
+ const formatter = createConsoleFormatter({ time })({
413
+ timestampFormat: "iso",
414
+ });
415
+
416
+ const entry: ConsoleEntry = {
417
+ method: "info",
418
+ path: [],
419
+ args: ["message"],
420
+ };
421
+
422
+ const result = formatter(entry);
423
+
424
+ assertEqual(result, ["2026-01-28T14:30:00.123Z", "message"]);
425
+ });
426
+
427
+ it("absolute timestamp", () => {
428
+ const time = testCreateTime({
429
+ startAt: Date.UTC(2026, 0, 28, 14, 30, 15, 123) as Millis,
430
+ });
431
+ const formatter = createConsoleFormatter({ time })({
432
+ timestampFormat: "absolute",
433
+ });
434
+
435
+ const entry: ConsoleEntry = {
436
+ method: "info",
437
+ path: [],
438
+ args: ["message"],
439
+ };
440
+
441
+ const result = formatter(entry);
442
+
443
+ // Result includes local time formatted as HH:MM:SS.mmm
444
+ assertLength(result, 2);
445
+ assertType(String, result[0]);
446
+ assertTrue(/^\d{2}:\d{2}:\d{2}\.\d{3}$/u.test(result[0]));
447
+ assertEqual(result[1], "message");
448
+ });
449
+
450
+ it("combines timestamp and path", () => {
451
+ const formatter = createConsoleFormatter(createTimeDep())({
452
+ timestampFormat: "relative",
453
+ });
454
+
455
+ const entry: ConsoleEntry = {
456
+ method: "info",
457
+ path: ["relay"],
458
+ args: ["message"],
459
+ };
460
+
461
+ const result = formatter(entry);
462
+
463
+ assertEqual(result, ["+0.000s [relay]", "message"]);
464
+ });
465
+
466
+ it("createConsoleFormatter example", () => {
467
+ const time = testCreateTime({ startAt: 0 as Millis });
468
+ const output = createTestOutput();
469
+
470
+ // Relative timestamps
471
+ const root = createConsole({
472
+ output,
473
+ formatter: createConsoleFormatter({ time })({
474
+ timestampFormat: "relative",
475
+ }),
476
+ });
477
+
478
+ const relay = root.child("relay");
479
+ relay.log("connected");
480
+ time.advance("1.5s");
481
+ relay.log("synced");
482
+
483
+ assertEqual(
484
+ output.entries.map((entry) => entry.formattedArgs),
485
+ [
486
+ ["+0.000s [relay]", "connected"],
487
+ ["+1.500s [relay]", "synced"],
488
+ ],
489
+ );
490
+
491
+ // Nested children
492
+ const db = relay.child("db");
493
+ db.log("opened");
494
+
495
+ assertEqual(output.entries[2].formattedArgs, [
496
+ "+1.500s [relay] [db]",
497
+ "opened",
498
+ ]);
499
+
500
+ // Absolute timestamps (local clock time HH:MM:SS.mmm)
501
+ const absoluteOutput = createTestOutput();
502
+ const absoluteTime = testCreateTime({
503
+ startAt: Date.UTC(2026, 0, 28, 14, 30, 15, 123) as Millis,
504
+ });
505
+ const absoluteRoot = createConsole({
506
+ output: absoluteOutput,
507
+ formatter: createConsoleFormatter({ time: absoluteTime })({
508
+ timestampFormat: "absolute",
509
+ }),
510
+ });
511
+ const absoluteRelay = absoluteRoot.child("relay");
512
+
513
+ absoluteRelay.log("connected");
514
+
515
+ const [timestamp, message] = absoluteOutput.entries[0].formattedArgs;
516
+ assertType(String, timestamp);
517
+ assertTrue(/^\d{2}:\d{2}:\d{2}\.\d{3} \[relay\]$/u.test(timestamp));
518
+ assertEqual(message, "connected");
519
+ });
520
+ });
521
+
522
+ describe("createConsoleStoreOutput", () => {
523
+ it("entry starts as null", () => {
524
+ const output = createConsoleStoreOutput();
525
+ assertSame(output.entry.get(), null);
526
+ });
527
+
528
+ it("entry updates on write", () => {
529
+ const output = createConsoleStoreOutput();
530
+ const console = createConsole({ output });
531
+ console.info("hello");
532
+ assertEqual(output.entry.get(), {
533
+ method: "info",
534
+ path: [],
535
+ args: ["hello"],
536
+ });
537
+ });
538
+
539
+ it("entry notifies subscribers", () => {
540
+ const output = createConsoleStoreOutput();
541
+ const console = createConsole({ output });
542
+ const received: Array<ConsoleEntry | null> = [];
543
+ output.entry.subscribe(() => {
544
+ received.push(output.entry.get());
545
+ });
546
+
547
+ console.warn("one");
548
+ console.error("two");
549
+
550
+ assertEqual(received, [
551
+ { method: "warn", path: [], args: ["one"] },
552
+ { method: "error", path: [], args: ["two"] },
553
+ ]);
554
+ });
555
+
556
+ it("captures child entries", () => {
557
+ const output = createConsoleStoreOutput();
558
+ const console = createConsole({ output });
559
+ const child = console.child("db");
560
+ const received: Array<ConsoleEntry | null> = [];
561
+ output.entry.subscribe(() => {
562
+ received.push(output.entry.get());
563
+ });
564
+
565
+ child.info("from child");
566
+
567
+ assertEqual(received, [
568
+ { method: "info", path: ["db"], args: ["from child"] },
569
+ ]);
570
+ });
571
+
572
+ it("skips filtered entries", () => {
573
+ const output = createConsoleStoreOutput();
574
+ const console = createConsole({ output, level: "warn" });
575
+ console.debug("ignored");
576
+ assertSame(output.entry.get(), null);
577
+ console.warn("logged");
578
+ assertEqual(output.entry.get()?.method, "warn");
579
+ });
580
+ });
581
+
582
+ describe("createConsoleArrayOutput", () => {
583
+ it("captures entries to array", () => {
584
+ const entries: Array<ConsoleEntry> = [];
585
+ const output = createConsoleArrayOutput(entries);
586
+
587
+ output.write({
588
+ method: "info",
589
+ path: ["relay"],
590
+ args: ["message", 123],
591
+ });
592
+
593
+ assertEqual(entries, [
594
+ {
595
+ method: "info",
596
+ path: ["relay"],
597
+ args: ["message", 123],
598
+ },
599
+ ]);
600
+ });
601
+
602
+ it("works with createConsole", () => {
603
+ const entries: Array<ConsoleEntry> = [];
604
+ const output = createConsoleArrayOutput(entries);
605
+ const console = createConsole({ output });
606
+
607
+ console.info("hello");
608
+ console.warn("world");
609
+
610
+ assertEqual(
611
+ entries.map((e) => e.method),
612
+ ["info", "warn"],
613
+ );
614
+ assertEqual(
615
+ entries.map((e) => e.args[0]),
616
+ ["hello", "world"],
617
+ );
618
+ });
619
+ });
620
+
621
+ describe("createMultiOutput", () => {
622
+ it("writes to all outputs", () => {
623
+ const entries1: Array<ConsoleEntry> = [];
624
+ const entries2: Array<ConsoleEntry> = [];
625
+ const output = createMultiOutput([
626
+ createConsoleArrayOutput(entries1),
627
+ createConsoleArrayOutput(entries2),
628
+ ]);
629
+ const console = createConsole({ output });
630
+
631
+ console.info("hello");
632
+
633
+ assertLength(entries1, 1);
634
+ assertLength(entries2, 1);
635
+ assertEqual(entries1[0], entries2[0]);
636
+ });
637
+
638
+ it("combines native and store outputs", () => {
639
+ const storeOutput = createConsoleStoreOutput();
640
+ const entries: Array<ConsoleEntry> = [];
641
+ const output = createMultiOutput([
642
+ createConsoleArrayOutput(entries),
643
+ storeOutput,
644
+ ]);
645
+ const console = createConsole({ output });
646
+
647
+ console.error("fail");
648
+
649
+ assertLength(entries, 1);
650
+ assertEqual(storeOutput.entry.get()?.args, ["fail"]);
651
+ });
652
+ });
653
+
654
+ describe("testCreateConsole", () => {
655
+ it("captures entries", () => {
656
+ const console = testCreateConsole();
657
+
658
+ console.info("first");
659
+ console.info("second");
660
+
661
+ assertEqual(console.getEntriesSnapshot(), [
662
+ { method: "info", path: [], args: ["first"] },
663
+ { method: "info", path: [], args: ["second"] },
664
+ ]);
665
+ });
666
+
667
+ it("defaults to trace level (logs everything)", () => {
668
+ const console = testCreateConsole();
669
+
670
+ console.trace("trace");
671
+ console.debug("debug");
672
+ console.log("log");
673
+ console.info("info");
674
+ console.warn("warn");
675
+ console.error("error");
676
+
677
+ assertEqual(
678
+ console.getEntriesSnapshot().map((e) => e.method),
679
+ ["trace", "debug", "log", "info", "warn", "error"],
680
+ );
681
+ });
682
+
683
+ it("respects configured level", () => {
684
+ const console = testCreateConsole({ level: "warn" });
685
+
686
+ console.debug("ignored");
687
+ console.info("ignored");
688
+ console.warn("logged");
689
+ console.error("logged");
690
+
691
+ assertEqual(
692
+ console.getEntriesSnapshot().map((e) => e.method),
693
+ ["warn", "error"],
694
+ );
695
+ });
696
+
697
+ it("getEntriesSnapshot clears entries", () => {
698
+ const console = testCreateConsole();
699
+
700
+ console.info("first");
701
+ assertLength(console.getEntriesSnapshot(), 1);
702
+ assertLength(console.getEntriesSnapshot(), 0);
703
+ });
704
+
705
+ it("clearEntries clears without returning", () => {
706
+ const console = testCreateConsole();
707
+
708
+ console.info("message");
709
+ console.clearEntries();
710
+
711
+ assertLength(console.getEntriesSnapshot(), 0);
712
+ });
713
+
714
+ it("child adds path", () => {
715
+ const console = testCreateConsole();
716
+ const child = console.child("relay").child("db");
717
+
718
+ child.info("message");
719
+
720
+ assertEqual(console.getEntriesSnapshot()[0].path, ["relay", "db"]);
721
+ });
722
+
723
+ it("child inherits level at creation (static)", () => {
724
+ const console = testCreateConsole({ level: "info" });
725
+ const child = console.child("relay");
726
+
727
+ // Child inherits "info" level
728
+ child.debug("ignored");
729
+ child.info("logged");
730
+
731
+ // Parent change doesn't affect child
732
+ console.setLevel("debug");
733
+ child.debug("still ignored");
734
+
735
+ assertEqual(
736
+ console.getEntriesSnapshot().map((e) => e.args[0]),
737
+ ["logged"],
738
+ );
739
+ });
740
+
741
+ it("child can override level independently", () => {
742
+ const console = testCreateConsole({ level: "info" });
743
+ const child = console.child("relay");
744
+
745
+ child.setLevel("debug");
746
+ child.debug("logged");
747
+
748
+ assertEqual(
749
+ console.getEntriesSnapshot().map((e) => e.args[0]),
750
+ ["logged"],
751
+ );
752
+ });
753
+
754
+ it("hasOwnLevel tracks level override", () => {
755
+ const console = testCreateConsole({ level: "info" });
756
+
757
+ assertFalse(console.hasOwnLevel());
758
+
759
+ console.setLevel("debug");
760
+ assertTrue(console.hasOwnLevel());
761
+
762
+ console.setLevel(null);
763
+ assertFalse(console.hasOwnLevel());
764
+ });
765
+
766
+ it("debug-level methods use debug level", () => {
767
+ const console = testCreateConsole({ level: "log" });
768
+
769
+ console.time("timer");
770
+ console.dir({ foo: 1 });
771
+ console.table([1, 2]);
772
+ console.count("counter");
773
+
774
+ assertLength(console.getEntriesSnapshot(), 0);
775
+
776
+ console.setLevel("debug");
777
+
778
+ console.time("timer");
779
+ console.timeLog("timer", "extra");
780
+ console.timeEnd("timer");
781
+ console.dir({ foo: 1 });
782
+ console.table([1, 2]);
783
+ console.count("counter");
784
+ console.countReset("counter");
785
+
786
+ assertEqual(
787
+ console.getEntriesSnapshot().map((e) => e.method),
788
+ ["time", "timeLog", "timeEnd", "dir", "table", "count", "countReset"],
789
+ );
790
+ });
791
+
792
+ it("children tracking", () => {
793
+ const console = testCreateConsole();
794
+ const child1 = console.child("a");
795
+ const child2 = console.child("b");
796
+
797
+ assertEqual(console.children.size, 2);
798
+ assertTrue(console.children.has(child1));
799
+ assertTrue(console.children.has(child2));
800
+ });
801
+
802
+ it("name property", () => {
803
+ const console = testCreateConsole();
804
+ const child = console.child("relay");
805
+
806
+ assertEqual(console.name, "");
807
+ assertEqual(child.name, "relay");
808
+ });
809
+ });