@evolu/common 8.7.0 → 8.8.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 (300) 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 +478 -141
  4. package/dist/src/Assert.d.ts.map +1 -1
  5. package/dist/src/Assert.js +644 -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.js +4 -1
  40. package/dist/src/Random.d.ts +1 -1
  41. package/dist/src/Result.d.ts +49 -54
  42. package/dist/src/Result.d.ts.map +1 -1
  43. package/dist/src/Result.js +17 -22
  44. package/dist/src/Set.d.ts +6 -6
  45. package/dist/src/Set.js +2 -2
  46. package/dist/src/Sqlite.js +1 -1
  47. package/dist/src/String.js +3 -2
  48. package/dist/src/Task.d.ts +32 -32
  49. package/dist/src/Task.d.ts.map +1 -1
  50. package/dist/src/Task.js +20 -22
  51. package/dist/src/Test.d.ts +31 -3
  52. package/dist/src/Test.d.ts.map +1 -1
  53. package/dist/src/Test.js +46 -3
  54. package/dist/src/Time.d.ts +3 -11
  55. package/dist/src/Time.d.ts.map +1 -1
  56. package/dist/src/Time.js +10 -18
  57. package/dist/src/Type.d.ts +87 -85
  58. package/dist/src/Type.d.ts.map +1 -1
  59. package/dist/src/Type.js +70 -63
  60. package/dist/src/Types.d.ts +12 -12
  61. package/dist/src/Types.js +1 -1
  62. package/dist/src/WebSocket.d.ts +4 -7
  63. package/dist/src/WebSocket.d.ts.map +1 -1
  64. package/dist/src/Worker.d.ts +1 -1
  65. package/dist/src/Worker.js +7 -7
  66. package/dist/src/index.d.ts +1 -1
  67. package/dist/src/index.js +1 -1
  68. package/dist/src/intl/_en.d.ts.map +1 -1
  69. package/dist/src/intl/_en.js +1 -1
  70. package/dist/src/intl/ar.d.ts.map +1 -1
  71. package/dist/src/intl/ar.js +1 -1
  72. package/dist/src/intl/bn.d.ts.map +1 -1
  73. package/dist/src/intl/bn.js +1 -1
  74. package/dist/src/intl/ca.d.ts.map +1 -1
  75. package/dist/src/intl/ca.js +1 -1
  76. package/dist/src/intl/cs.d.ts.map +1 -1
  77. package/dist/src/intl/cs.js +1 -1
  78. package/dist/src/intl/da.d.ts.map +1 -1
  79. package/dist/src/intl/da.js +1 -1
  80. package/dist/src/intl/de.d.ts.map +1 -1
  81. package/dist/src/intl/de.js +1 -1
  82. package/dist/src/intl/el.d.ts.map +1 -1
  83. package/dist/src/intl/el.js +1 -1
  84. package/dist/src/intl/es.d.ts.map +1 -1
  85. package/dist/src/intl/es.js +1 -1
  86. package/dist/src/intl/fa.d.ts.map +1 -1
  87. package/dist/src/intl/fa.js +1 -1
  88. package/dist/src/intl/fi.d.ts.map +1 -1
  89. package/dist/src/intl/fi.js +1 -1
  90. package/dist/src/intl/fil.d.ts.map +1 -1
  91. package/dist/src/intl/fil.js +1 -1
  92. package/dist/src/intl/fr.d.ts.map +1 -1
  93. package/dist/src/intl/fr.js +1 -1
  94. package/dist/src/intl/he.d.ts.map +1 -1
  95. package/dist/src/intl/he.js +1 -1
  96. package/dist/src/intl/hi.d.ts.map +1 -1
  97. package/dist/src/intl/hi.js +1 -1
  98. package/dist/src/intl/hr.d.ts.map +1 -1
  99. package/dist/src/intl/hr.js +1 -1
  100. package/dist/src/intl/hu.d.ts.map +1 -1
  101. package/dist/src/intl/hu.js +1 -1
  102. package/dist/src/intl/id.d.ts.map +1 -1
  103. package/dist/src/intl/id.js +1 -1
  104. package/dist/src/intl/it.d.ts.map +1 -1
  105. package/dist/src/intl/it.js +1 -1
  106. package/dist/src/intl/ja.d.ts.map +1 -1
  107. package/dist/src/intl/ja.js +1 -1
  108. package/dist/src/intl/ko.d.ts.map +1 -1
  109. package/dist/src/intl/ko.js +1 -1
  110. package/dist/src/intl/ml.d.ts.map +1 -1
  111. package/dist/src/intl/ml.js +1 -1
  112. package/dist/src/intl/mr.d.ts.map +1 -1
  113. package/dist/src/intl/mr.js +1 -1
  114. package/dist/src/intl/ms.d.ts.map +1 -1
  115. package/dist/src/intl/ms.js +1 -1
  116. package/dist/src/intl/nb.d.ts.map +1 -1
  117. package/dist/src/intl/nb.js +1 -1
  118. package/dist/src/intl/nl.d.ts.map +1 -1
  119. package/dist/src/intl/nl.js +1 -1
  120. package/dist/src/intl/pa.d.ts.map +1 -1
  121. package/dist/src/intl/pa.js +1 -1
  122. package/dist/src/intl/pl.d.ts.map +1 -1
  123. package/dist/src/intl/pl.js +1 -1
  124. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  125. package/dist/src/intl/pt-BR.js +1 -1
  126. package/dist/src/intl/pt.d.ts.map +1 -1
  127. package/dist/src/intl/pt.js +1 -1
  128. package/dist/src/intl/ro.d.ts.map +1 -1
  129. package/dist/src/intl/ro.js +1 -1
  130. package/dist/src/intl/sk.d.ts.map +1 -1
  131. package/dist/src/intl/sk.js +1 -1
  132. package/dist/src/intl/sl.d.ts.map +1 -1
  133. package/dist/src/intl/sl.js +1 -1
  134. package/dist/src/intl/sv.d.ts.map +1 -1
  135. package/dist/src/intl/sv.js +1 -1
  136. package/dist/src/intl/sw.d.ts.map +1 -1
  137. package/dist/src/intl/sw.js +1 -1
  138. package/dist/src/intl/ta.d.ts.map +1 -1
  139. package/dist/src/intl/ta.js +1 -1
  140. package/dist/src/intl/te.d.ts.map +1 -1
  141. package/dist/src/intl/te.js +1 -1
  142. package/dist/src/intl/th.d.ts.map +1 -1
  143. package/dist/src/intl/th.js +1 -1
  144. package/dist/src/intl/tr.d.ts.map +1 -1
  145. package/dist/src/intl/tr.js +1 -1
  146. package/dist/src/intl/uk.d.ts.map +1 -1
  147. package/dist/src/intl/uk.js +1 -1
  148. package/dist/src/intl/ur.d.ts.map +1 -1
  149. package/dist/src/intl/ur.js +1 -1
  150. package/dist/src/intl/vi.d.ts.map +1 -1
  151. package/dist/src/intl/vi.js +1 -1
  152. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  153. package/dist/src/intl/zh-CN.js +1 -1
  154. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  155. package/dist/src/intl/zh-TW.js +1 -1
  156. package/dist/src/local-first/Evolu.d.ts +12 -12
  157. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  158. package/dist/src/local-first/Evolu.js +2 -2
  159. package/dist/src/local-first/Protocol.d.ts +14 -76
  160. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  161. package/dist/src/local-first/Protocol.js +29 -206
  162. package/dist/src/local-first/Query.d.ts +11 -11
  163. package/dist/src/local-first/Query.js +6 -6
  164. package/dist/src/local-first/Relay.d.ts +2 -2
  165. package/dist/src/local-first/Schema.d.ts +3 -5
  166. package/dist/src/local-first/Schema.d.ts.map +1 -1
  167. package/dist/src/local-first/Schema.js +2 -4
  168. package/dist/src/local-first/Shared.d.ts.map +1 -1
  169. package/dist/src/local-first/Shared.js +5 -5
  170. package/dist/src/local-first/Storage.js +4 -4
  171. package/dist/src/local-first/Timestamp.d.ts +9 -0
  172. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  173. package/dist/src/local-first/Timestamp.js +13 -10
  174. package/package.json +7 -4
  175. package/src/Array.test.ts +809 -0
  176. package/src/Array.ts +21 -21
  177. package/src/Assert.test.ts +1181 -0
  178. package/src/Assert.ts +971 -227
  179. package/src/BigInt.test.ts +28 -0
  180. package/src/Binary.test.ts +1128 -0
  181. package/src/{Buffer.ts → Binary.ts} +273 -46
  182. package/src/Brand.test.ts +90 -0
  183. package/src/Cache.test.ts +183 -0
  184. package/src/Callbacks.test.ts +66 -0
  185. package/src/Console.test.ts +809 -0
  186. package/src/Console.ts +9 -17
  187. package/src/Crypto.test.ts +138 -0
  188. package/src/Crypto.ts +5 -5
  189. package/src/Eq.test.ts +333 -0
  190. package/src/Eq.ts +110 -176
  191. package/src/Error.test.ts +118 -0
  192. package/src/Function.test.ts +204 -0
  193. package/src/Function.ts +10 -15
  194. package/src/Http.test.ts +816 -0
  195. package/src/Http.ts +6 -7
  196. package/src/LeakDetector.test.ts +210 -0
  197. package/src/LeakDetector.ts +1 -0
  198. package/src/LockManager.ts +2 -2
  199. package/src/Lookup.test.ts +418 -0
  200. package/src/Lookup.ts +4 -3
  201. package/src/Microtask.test.ts +140 -0
  202. package/src/Number.test.ts +218 -0
  203. package/src/Number.ts +2 -2
  204. package/src/Object.test.ts +293 -0
  205. package/src/Object.ts +5 -4
  206. package/src/Option.test.ts +104 -0
  207. package/src/Option.ts +1 -2
  208. package/src/Order.test.ts +56 -0
  209. package/src/Platform.ts +2 -0
  210. package/src/Polyfills.ts +3 -4
  211. package/src/Random.test.ts +89 -0
  212. package/src/Random.ts +1 -1
  213. package/src/Ref.test.ts +139 -0
  214. package/src/RefCount.test.ts +184 -0
  215. package/src/Relation.test.ts +505 -0
  216. package/src/Resource.test.ts +4623 -0
  217. package/src/Result.test.ts +1829 -0
  218. package/src/Result.ts +49 -54
  219. package/src/Schedule.test.ts +1554 -0
  220. package/src/Set.test.ts +279 -0
  221. package/src/Set.ts +6 -6
  222. package/src/Sqlite.ts +1 -1
  223. package/src/Store.test.ts +288 -0
  224. package/src/String.test.ts +54 -0
  225. package/src/String.ts +3 -3
  226. package/src/Task.test.ts +10820 -0
  227. package/src/Task.ts +38 -43
  228. package/src/Test.test.ts +88 -0
  229. package/src/Test.ts +49 -3
  230. package/src/Time.test.ts +926 -0
  231. package/src/Time.ts +13 -21
  232. package/src/Type.base64.test.ts +88 -0
  233. package/src/Type.test.ts +20465 -0
  234. package/src/Type.ts +114 -111
  235. package/src/Types.test.ts +324 -0
  236. package/src/Types.ts +13 -13
  237. package/src/WebSocket.ts +4 -7
  238. package/src/Worker.test.ts +755 -0
  239. package/src/Worker.ts +13 -18
  240. package/src/index.ts +1 -1
  241. package/src/intl/_en.ts +1 -1
  242. package/src/intl/ar.ts +1 -1
  243. package/src/intl/bn.ts +1 -1
  244. package/src/intl/ca.ts +1 -1
  245. package/src/intl/cs.ts +1 -1
  246. package/src/intl/da.ts +1 -1
  247. package/src/intl/de.ts +1 -1
  248. package/src/intl/el.ts +1 -1
  249. package/src/intl/es.ts +1 -1
  250. package/src/intl/fa.ts +1 -1
  251. package/src/intl/fi.ts +1 -1
  252. package/src/intl/fil.ts +1 -1
  253. package/src/intl/fr.ts +1 -1
  254. package/src/intl/he.ts +1 -1
  255. package/src/intl/hi.ts +1 -1
  256. package/src/intl/hr.ts +1 -1
  257. package/src/intl/hu.ts +1 -1
  258. package/src/intl/id.ts +1 -1
  259. package/src/intl/intl.test.ts +3106 -0
  260. package/src/intl/it.ts +1 -1
  261. package/src/intl/ja.ts +1 -1
  262. package/src/intl/ko.ts +1 -1
  263. package/src/intl/ml.ts +1 -1
  264. package/src/intl/mr.ts +1 -1
  265. package/src/intl/ms.ts +1 -1
  266. package/src/intl/nb.ts +1 -1
  267. package/src/intl/nl.ts +1 -1
  268. package/src/intl/pa.ts +1 -1
  269. package/src/intl/pl.ts +1 -1
  270. package/src/intl/pt-BR.ts +1 -1
  271. package/src/intl/pt.ts +1 -1
  272. package/src/intl/ro.ts +1 -1
  273. package/src/intl/sk.ts +1 -1
  274. package/src/intl/sl.ts +1 -1
  275. package/src/intl/sv.ts +1 -1
  276. package/src/intl/sw.ts +1 -1
  277. package/src/intl/ta.ts +1 -1
  278. package/src/intl/te.ts +1 -1
  279. package/src/intl/th.ts +1 -1
  280. package/src/intl/tr.ts +1 -1
  281. package/src/intl/uk.ts +1 -1
  282. package/src/intl/ur.ts +1 -1
  283. package/src/intl/vi.ts +1 -1
  284. package/src/intl/zh-CN.ts +1 -1
  285. package/src/intl/zh-TW.ts +1 -1
  286. package/src/local-first/Evolu.test.ts +1921 -0
  287. package/src/local-first/Evolu.ts +18 -14
  288. package/src/local-first/Owner.test.ts +83 -0
  289. package/src/local-first/Protocol.test.ts +1037 -0
  290. package/src/local-first/Protocol.ts +58 -261
  291. package/src/local-first/Query.test.ts +349 -0
  292. package/src/local-first/Query.ts +11 -11
  293. package/src/local-first/Relay.ts +2 -2
  294. package/src/local-first/Schema.ts +3 -5
  295. package/src/local-first/Shared.test.ts +1828 -0
  296. package/src/local-first/Shared.ts +9 -8
  297. package/src/local-first/Storage.ts +4 -4
  298. package/src/local-first/Timestamp.test.ts +374 -0
  299. package/src/local-first/Timestamp.ts +22 -10
  300. package/dist/src/Buffer.d.ts.map +0 -1
@@ -1,143 +1,435 @@
1
1
  /**
2
- * Runtime assertions for invariants and value checks.
2
+ * Platform-agnostic assertions for invariants, examples, and tests.
3
+ *
4
+ * An assertion documents a condition required for the program to be correct.
5
+ * Ideally, the type system should enforce it so incorrect code cannot compile,
6
+ * but that is not always possible (not even in Rust). A runtime assertion
7
+ * failure therefore indicates a bug that must be fixed. Throwing at the point
8
+ * of violation prevents invalid state from propagating and makes the defect
9
+ * easier to diagnose.
10
+ *
11
+ * Evolu provides its own assertions so the same concise API works for
12
+ * production invariants, executable examples, and tests on every platform. The
13
+ * general {@link assert} requires a message explaining the invariant, and
14
+ * specialized assertions provide focused diagnostics and narrowing. The API
15
+ * deliberately favors strict and predictable contracts: one {@link assertEqual}
16
+ * covers primitives and structural Data comparisons, {@link assertEqualBytes}
17
+ * compares bytes across array representations, and {@link assertSame} is
18
+ * reserved for SameValue or reference identity. Generic partial structural
19
+ * assertions such as `assertMatches` are omitted so unmentioned state cannot
20
+ * hide regressions. If a complete expected value is too large to keep inline,
21
+ * use a fixture or a focused domain helper that keeps the contract explicit.
22
+ * {@link assertOk} and {@link assertErr} provide convenient Result narrowing,
23
+ * while {@link assertType} provides runtime Type assertions and compile-time
24
+ * type equality similar to Vitest's `expectTypeOf` without requiring a test
25
+ * runner. {@link assertThrows} and {@link assertRejects} verify thrown and
26
+ * rejected values without matcher semantics. Together, these assertions keep
27
+ * documentation examples concise and directly copyable.
28
+ *
29
+ * In Node.js, failures use the native `AssertionError` for structured
30
+ * diagnostics and diffs. Other platforms use a compatible fallback. You should
31
+ * not need to import Node.js assertions unless Evolu does not provide an
32
+ * equivalent or a test requires exact Node.js semantics.
33
+ *
34
+ * Do not use assertions to validate external input. Use a {@link Type}
35
+ * declaration's `fromUnknown` so invalid input is represented by a typed
36
+ * {@link Result}.
37
+ *
38
+ * TODO(next major): Add a separate production build that replaces full
39
+ * assertion messages with numeric error codes and a decoder, following React's
40
+ * approach, to reduce the core bundle size since Evolu has many assertions.
3
41
  *
4
42
  * @module
5
43
  */
6
- import { eqData, eqStrict } from "./Eq.js";
44
+ import { eqArrayNumber, eqData, eqSameValue } from "./Eq.js";
7
45
  /**
8
- * Ensures a condition is true, throwing an error with the provided message if
9
- * not.
46
+ * Asserts that a condition is truthy.
10
47
  *
11
- * Prevents invalid states from propagating through the system by halting
12
- * execution when a condition fails, improving reliability and debuggability.
48
+ * Throws an `AssertionError` with the provided message if the condition is
49
+ * falsy, preventing invalid state from propagating and making the failure
50
+ * easier to diagnose.
13
51
  *
14
- * Do not use this instead of {@link Type}. Assertions are intended when a
15
- * condition is logically guaranteed to be true but TypeScript cannot prove it,
16
- * or for catching and signaling developer mistakes eagerly.
52
+ * Use `assert` only when a more specific assertion, such as {@link assertTrue}
53
+ * or {@link assertEqual}, does not apply. For that reason, `assert` requires a
54
+ * custom message explaining the expected condition.
55
+ *
56
+ * Options provide structured diagnostics for custom assertions. Pass an
57
+ * underlying failure as `cause` when it explains why the asserted condition
58
+ * failed, and pass the custom assertion as `stackStartFn` to omit its
59
+ * implementation from the stack trace.
17
60
  *
18
61
  * ### Example
19
62
  *
20
63
  * ```ts
21
- * import { assert, assertEqual, assertErr, trySync } from "@evolu/common";
64
+ * import { assert, assertEqual, assertType } from "@evolu/common";
22
65
  *
23
- * assert(true, "Expected true.");
24
- * const result = trySync(() => assert(false, "Expected true."));
25
- * assertErr(result);
26
- * assert(result.error instanceof Error, "Expected an Error.");
27
- * assertEqual(result.error.message, "Expected true.");
66
+ * const value: unknown = "Evolu";
67
+ * assert(typeof value === "string", "Expected a string.");
68
+ *
69
+ * assertType<typeof value, string>();
70
+ * assertEqual(value, "Evolu");
28
71
  * ```
72
+ *
73
+ * @group Assertions
29
74
  */
30
- export const assert = (condition, message) => {
75
+ export const assert = (condition, message, options = {}) => {
31
76
  // oxlint-disable-next-line typescript/strict-boolean-expressions -- JavaScript truthiness is the contract of assert.
32
- if (!condition) {
33
- throw new Error(message);
77
+ if (condition)
78
+ return;
79
+ const actual = Object.hasOwn(options, "actual") ? options.actual : condition;
80
+ const expected = Object.hasOwn(options, "expected") ? options.expected : true;
81
+ const operator = options.operator ?? "==";
82
+ const stackStartFn = options.stackStartFn ?? assert;
83
+ if (NodeAssert !== undefined) {
84
+ const error = new NodeAssert.AssertionError({
85
+ message,
86
+ actual,
87
+ expected,
88
+ operator: options.diff === "full" ? "deepStrictEqual" : operator,
89
+ diff: options.diff,
90
+ stackStartFn,
91
+ });
92
+ if (options.diff === "full") {
93
+ error.operator = operator;
94
+ }
95
+ if (options.cause !== undefined) {
96
+ Object.defineProperty(error, "cause", {
97
+ configurable: true,
98
+ value: options.cause,
99
+ writable: true,
100
+ });
101
+ }
102
+ throw error;
34
103
  }
104
+ const error = Object.assign(new Error(message, options.cause === undefined ? undefined : { cause: options.cause }), {
105
+ name: "AssertionError",
106
+ code: "ERR_ASSERTION",
107
+ actual,
108
+ expected,
109
+ generatedMessage: false,
110
+ operator,
111
+ });
112
+ Error.captureStackTrace?.(error, stackStartFn);
113
+ throw error;
35
114
  };
115
+ // Node.js generates assertion diffs inside its AssertionError constructor; the
116
+ // test runner does not derive them from actual and expected metadata alone. Use
117
+ // the native constructor when available and the portable fallback elsewhere.
118
+ const NodeAssert = globalThis.process?.getBuiltinModule?.("node:assert/strict");
119
+ export function assertTrue(value) {
120
+ assert(value === true, "Expected true.", {
121
+ actual: value,
122
+ expected: true,
123
+ operator: "strictEqual",
124
+ stackStartFn: assertTrue,
125
+ });
126
+ }
36
127
  /**
37
- * Asserts that two values are the same according to {@link eqStrict}.
128
+ * Asserts that a value is exactly `false` and narrows it to `false`.
38
129
  *
39
- * `eqStrict` uses SameValue equality: it considers `NaN` the same as itself,
40
- * distinguishes `0` from `-0`, and compares objects by reference identity.
130
+ * Unlike {@link assert}, this checks an exact boolean value instead of falsiness
131
+ * and does not require a custom message.
41
132
  *
42
133
  * ### Example
43
134
  *
44
135
  * ```ts
45
- * import { assertSame, assertType } from "@evolu/common";
136
+ * import { assertFalse, assertType } from "@evolu/common";
46
137
  *
47
- * interface User {
48
- * readonly name: string;
49
- * }
138
+ * const value: unknown = false;
139
+ * assertFalse(value);
140
+ * assertType<typeof value, false>();
141
+ * ```
50
142
  *
51
- * const user: User = { name: "Ada" };
52
- * const value: unknown = user;
143
+ * @group Assertions
144
+ */
145
+ export const assertFalse = (value) => {
146
+ assert(value === false, "Expected false.", {
147
+ actual: value,
148
+ expected: false,
149
+ operator: "strictEqual",
150
+ stackStartFn: assertFalse,
151
+ });
152
+ };
153
+ /**
154
+ * Asserts that a condition becomes true after exactly the specified number of
155
+ * microtasks.
53
156
  *
54
- * assertSame(value, user);
55
- * assertType<User, typeof value>();
157
+ * Use this in tests that intentionally specify async scheduling behavior.
158
+ * Application code must not depend on exact microtask counts. Maintainers
159
+ * should review count changes because they indicate that an async pipeline
160
+ * changed.
161
+ *
162
+ * ### Example
163
+ *
164
+ * ```ts
165
+ * import { assertConditionAfterMicrotasks } from "@evolu/common";
166
+ *
167
+ * let ready = false;
168
+ * queueMicrotask(() => {
169
+ * ready = true;
170
+ * });
171
+ *
172
+ * await assertConditionAfterMicrotasks(() => ready, 1);
56
173
  * ```
174
+ *
175
+ * @group Assertions
57
176
  */
58
- export const assertSame = (actual, expected) => {
59
- assert(eqStrict(actual, expected), "Expected values to be the same.");
177
+ export const assertConditionAfterMicrotasks = async (condition, expectedMicrotaskCount) => {
178
+ for (let microtaskCount = 0; microtaskCount < expectedMicrotaskCount; microtaskCount++) {
179
+ const actual = condition();
180
+ assert(!actual, `Expected condition to be false after ${microtaskCount} microtasks.`, {
181
+ actual,
182
+ expected: false,
183
+ operator: "strictEqual",
184
+ stackStartFn: assertConditionAfterMicrotasks,
185
+ });
186
+ await Promise.resolve();
187
+ }
188
+ const actual = condition();
189
+ assert(actual, `Expected condition to be true after exactly ${expectedMicrotaskCount} microtasks.`, {
190
+ actual,
191
+ expected: true,
192
+ operator: "strictEqual",
193
+ stackStartFn: assertConditionAfterMicrotasks,
194
+ });
60
195
  };
196
+ export function assertThrows(run, expectedOrAssert) {
197
+ const thrown = captureThrown(run, assertThrows);
198
+ if (typeof expectedOrAssert === "function") {
199
+ const result = expectedOrAssert(thrown);
200
+ assert(result === undefined, "Expected the thrown value assertion to return undefined.", {
201
+ actual: result,
202
+ expected: undefined,
203
+ operator: "strictEqual",
204
+ stackStartFn: assertThrows,
205
+ });
206
+ return;
207
+ }
208
+ assert(eqUnknown(thrown, expectedOrAssert), "Expected the thrown value to equal the expected value.", {
209
+ actual: thrown,
210
+ expected: expectedOrAssert,
211
+ operator: "eqData",
212
+ diff: "full",
213
+ stackStartFn: assertThrows,
214
+ });
215
+ }
61
216
  /**
62
- * Asserts that a value is exactly `true` and narrows it to `true`.
217
+ * Asserts that a function throws the same value using `Object.is`.
63
218
  *
64
- * Unlike {@link assert}, this checks an exact boolean value instead of
65
- * truthiness and does not require a custom message.
219
+ * Use this to verify that a thrown value was propagated unchanged. For value
220
+ * comparisons, use {@link assertThrows}.
66
221
  *
67
222
  * ### Example
68
223
  *
69
224
  * ```ts
70
- * import { assertTrue, assertType } from "@evolu/common";
225
+ * import { assertThrowsSame } from "@evolu/common";
71
226
  *
72
- * const value: unknown = true;
73
- * assertTrue(value);
74
- * assertType<true, typeof value>();
227
+ * const expected = new Error("Unavailable.");
228
+ * assertThrowsSame(() => {
229
+ * throw expected;
230
+ * }, expected);
75
231
  * ```
232
+ *
233
+ * @group Assertions
76
234
  */
77
- export const assertTrue = (value) => {
78
- assert(value === true, "Expected true.");
235
+ export const assertThrowsSame = (run, expected) => {
236
+ const thrown = captureThrown(run, assertThrowsSame);
237
+ assert(eqSameValue(thrown, expected), "Expected the thrown value to be the same as the expected value.", {
238
+ actual: thrown,
239
+ expected,
240
+ operator: "strictEqual",
241
+ stackStartFn: assertThrowsSame,
242
+ });
79
243
  };
80
244
  /**
81
- * Asserts that a value is exactly `false` and narrows it to `false`.
245
+ * Asserts that a function throws an instance of a constructor.
82
246
  *
83
- * Unlike {@link assert}, this checks an exact boolean value instead of falsiness
84
- * and does not require a custom message.
247
+ * Returns the narrowed instance so additional properties can be asserted
248
+ * without running the function again.
85
249
  *
86
250
  * ### Example
87
251
  *
88
252
  * ```ts
89
- * import { assertFalse, assertType } from "@evolu/common";
253
+ * import { assertEqual, assertThrowsInstanceOf } from "@evolu/common";
90
254
  *
91
- * const value: unknown = false;
92
- * assertFalse(value);
93
- * assertType<false, typeof value>();
255
+ * const error = assertThrowsInstanceOf(() => {
256
+ * throw new TypeError("Expected a string.");
257
+ * }, TypeError);
258
+ * assertEqual(error.message, "Expected a string.");
94
259
  * ```
260
+ *
261
+ * @group Assertions
95
262
  */
96
- export const assertFalse = (value) => {
97
- assert(value === false, "Expected false.");
263
+ export const assertThrowsInstanceOf = (run, constructor) => {
264
+ const thrown = captureThrown(run, assertThrowsInstanceOf);
265
+ assert(thrown instanceof constructor, constructor.name === ""
266
+ ? "Expected the thrown value to be an instance of the provided constructor."
267
+ : `Expected the thrown value to be an instance of ${constructor.name}.`, {
268
+ actual: thrown,
269
+ expected: constructor,
270
+ operator: "instanceof",
271
+ stackStartFn: assertThrowsInstanceOf,
272
+ });
273
+ return thrown;
98
274
  };
99
- export function assertEqual(actual, expected) {
100
- assert(eqData(actual, expected), "Expected values to be equal.");
101
- }
102
- export function assertOk(result, ...comparison) {
103
- assert(result.ok, "Expected an Ok result.");
104
- if (comparison.length === 0)
105
- return;
106
- const expectedValue = comparison[0];
107
- const eq = comparison[1] ?? eqData;
108
- assert(eq(result.value, expectedValue), "Expected the value to equal the expected value.");
109
- }
110
- export function assertErr(result, ...comparison) {
111
- assert(!result.ok, "Expected an Err result.");
112
- if (comparison.length === 0)
275
+ const captureThrown = (run, stackStartFn) => {
276
+ let value;
277
+ try {
278
+ value = run();
279
+ }
280
+ catch (error) {
281
+ return error;
282
+ }
283
+ assert(false, "Expected function to throw.", {
284
+ actual: value,
285
+ expected: "throw",
286
+ operator: "throws",
287
+ stackStartFn,
288
+ });
289
+ };
290
+ export async function assertRejects(promise, expectedOrAssert) {
291
+ const rejected = await captureRejected(promise, assertRejects);
292
+ if (typeof expectedOrAssert === "function") {
293
+ const result = expectedOrAssert(rejected);
294
+ assert(result === undefined, "Expected the rejection assertion to return undefined.", {
295
+ actual: result,
296
+ expected: undefined,
297
+ operator: "strictEqual",
298
+ stackStartFn: assertRejects,
299
+ });
113
300
  return;
114
- const expectedError = comparison[0];
115
- const eq = comparison[1] ?? eqData;
116
- assert(eq(result.error, expectedError), "Expected the error to equal the expected error.");
301
+ }
302
+ assert(eqUnknown(rejected, expectedOrAssert), "Expected the rejection reason to equal the expected value.", {
303
+ actual: rejected,
304
+ expected: expectedOrAssert,
305
+ operator: "eqData",
306
+ diff: "full",
307
+ stackStartFn: assertRejects,
308
+ });
117
309
  }
310
+ /**
311
+ * Asserts that a promise rejects with the same value using `Object.is`.
312
+ *
313
+ * Use this to verify that a rejection reason was propagated unchanged. For
314
+ * value comparisons, use {@link assertRejects}.
315
+ *
316
+ * ### Example
317
+ *
318
+ * ```ts
319
+ * import { assertRejectsSame } from "@evolu/common";
320
+ *
321
+ * const expected = new Error("Unavailable.");
322
+ * await assertRejectsSame(Promise.reject(expected), expected);
323
+ * ```
324
+ *
325
+ * @group Assertions
326
+ */
327
+ export const assertRejectsSame = async (promise, expected) => {
328
+ const rejected = await captureRejected(promise, assertRejectsSame);
329
+ assert(eqSameValue(rejected, expected), "Expected the rejection reason to be the same as the expected value.", {
330
+ actual: rejected,
331
+ expected,
332
+ operator: "strictEqual",
333
+ stackStartFn: assertRejectsSame,
334
+ });
335
+ };
336
+ /**
337
+ * Asserts that a promise rejects with an instance of a constructor.
338
+ *
339
+ * Returns the narrowed instance so additional properties can be asserted.
340
+ *
341
+ * ### Example
342
+ *
343
+ * ```ts
344
+ * import { assertEqual, assertRejectsInstanceOf } from "@evolu/common";
345
+ *
346
+ * const error = await assertRejectsInstanceOf(
347
+ * Promise.reject(new TypeError("Unavailable.")),
348
+ * TypeError,
349
+ * );
350
+ * assertEqual(error.message, "Unavailable.");
351
+ * ```
352
+ *
353
+ * @group Assertions
354
+ */
355
+ export const assertRejectsInstanceOf = async (promise, constructor) => {
356
+ const rejected = await captureRejected(promise, assertRejectsInstanceOf);
357
+ assert(rejected instanceof constructor, constructor.name === ""
358
+ ? "Expected the rejection reason to be an instance of the provided constructor."
359
+ : `Expected the rejection reason to be an instance of ${constructor.name}.`, {
360
+ actual: rejected,
361
+ expected: constructor,
362
+ operator: "instanceof",
363
+ stackStartFn: assertRejectsInstanceOf,
364
+ });
365
+ return rejected;
366
+ };
367
+ const captureRejected = async (promise, stackStartFn) => {
368
+ let value;
369
+ try {
370
+ value = await promise;
371
+ }
372
+ catch (error) {
373
+ return error;
374
+ }
375
+ assert(false, "Expected promise to reject.", {
376
+ actual: value,
377
+ expected: "rejection",
378
+ operator: "rejects",
379
+ stackStartFn,
380
+ });
381
+ };
382
+ /**
383
+ * Asserts that a value is an instance of a constructor and narrows it.
384
+ *
385
+ * ### Example
386
+ *
387
+ * ```ts
388
+ * import { assertInstanceOf, assertType } from "@evolu/common";
389
+ *
390
+ * const value: unknown = new TypeError("Invalid value.");
391
+ * assertInstanceOf(value, TypeError);
392
+ * assertType<typeof value, TypeError>();
393
+ * ```
394
+ *
395
+ * @group Assertions
396
+ */
397
+ export const assertInstanceOf = (value, constructor) => {
398
+ assert(value instanceof constructor, constructor.name === ""
399
+ ? "Expected an instance of the provided constructor."
400
+ : `Expected an instance of ${constructor.name}.`, {
401
+ actual: value,
402
+ expected: constructor,
403
+ operator: "instanceof",
404
+ stackStartFn: assertInstanceOf,
405
+ });
406
+ };
118
407
  /**
119
408
  * Asserts that a value is non-nullable.
120
409
  *
121
410
  * Following TypeScript's {@link NonNullable}, non-nullable here means neither
122
- * null nor undefined. Use this when a value is logically guaranteed to be
123
- * non-nullable but TypeScript cannot prove it.
411
+ * null nor undefined.
412
+ *
413
+ * @group Assertions
124
414
  */
125
415
  export const assertNonNullable = (value, message = "Expected value to be non-nullable.") => {
126
- assert(value != null, message);
416
+ assert(value != null, message, {
417
+ actual: value,
418
+ expected: null,
419
+ operator: "!=",
420
+ stackStartFn: assertNonNullable,
421
+ });
127
422
  };
128
423
  /**
129
424
  * Asserts that a value is not null while preserving undefined.
130
425
  *
131
- * Use this when a value is logically guaranteed not to be null but TypeScript
132
- * cannot prove it.
133
- *
134
426
  * ### Example
135
427
  *
136
428
  * ```ts
137
429
  * import {
138
- * assert,
139
430
  * assertEqual,
140
431
  * assertErr,
432
+ * assertInstanceOf,
141
433
  * assertNotNull,
142
434
  * assertType,
143
435
  * trySync,
@@ -145,30 +437,36 @@ export const assertNonNullable = (value, message = "Expected value to be non-nul
145
437
  *
146
438
  * const value = undefined as string | null | undefined;
147
439
  * assertNotNull(value);
148
- * assertType<string | undefined, typeof value>();
440
+ *
441
+ * assertType<typeof value, string | undefined>();
149
442
  * assertEqual(value, undefined);
443
+ *
150
444
  * const result = trySync(() => assertNotNull(null));
151
445
  * assertErr(result);
152
- * assert(result.error instanceof Error, "Expected an Error.");
446
+ * assertInstanceOf(result.error, Error);
153
447
  * assertEqual(result.error.message, "Expected value not to be null.");
154
448
  * ```
449
+ *
450
+ * @group Assertions
155
451
  */
156
452
  export const assertNotNull = (value, message = "Expected value not to be null.") => {
157
- assert(value !== null, message);
453
+ assert(value !== null, message, {
454
+ actual: value,
455
+ expected: null,
456
+ operator: "notStrictEqual",
457
+ stackStartFn: assertNotNull,
458
+ });
158
459
  };
159
460
  /**
160
461
  * Asserts that a value is not undefined while preserving null.
161
462
  *
162
- * Use this when a value is logically guaranteed not to be undefined but
163
- * TypeScript cannot prove it.
164
- *
165
463
  * ### Example
166
464
  *
167
465
  * ```ts
168
466
  * import {
169
- * assert,
170
467
  * assertEqual,
171
468
  * assertErr,
469
+ * assertInstanceOf,
172
470
  * assertNotUndefined,
173
471
  * assertType,
174
472
  * trySync,
@@ -176,22 +474,49 @@ export const assertNotNull = (value, message = "Expected value not to be null.")
176
474
  *
177
475
  * const value = null as string | null | undefined;
178
476
  * assertNotUndefined(value);
179
- * assertType<string | null, typeof value>();
477
+ * assertType<typeof value, string | null>();
180
478
  * assertEqual(value, null);
181
479
  * const result = trySync(() => assertNotUndefined(undefined));
182
480
  * assertErr(result);
183
- * assert(result.error instanceof Error, "Expected an Error.");
481
+ * assertInstanceOf(result.error, Error);
184
482
  * assertEqual(result.error.message, "Expected value not to be undefined.");
185
483
  * ```
484
+ *
485
+ * @group Assertions
186
486
  */
187
487
  export const assertNotUndefined = (value, message = "Expected value not to be undefined.") => {
188
- assert(value !== undefined, message);
488
+ assert(value !== undefined, message, {
489
+ actual: value,
490
+ expected: undefined,
491
+ operator: "notStrictEqual",
492
+ stackStartFn: assertNotUndefined,
493
+ });
189
494
  };
190
495
  /**
191
- * Asserts that an array is non-empty.
496
+ * Asserts that a value has the expected length and narrows its length.
497
+ *
498
+ * ### Example
499
+ *
500
+ * ```ts
501
+ * import { assertLength, assertType } from "@evolu/common";
192
502
  *
193
- * Use this when an array is logically guaranteed to be non-empty but TypeScript
194
- * cannot prove it.
503
+ * const values: ReadonlyArray<string> = ["Ada", "Grace"];
504
+ * assertLength(values, 2);
505
+ * assertType<typeof values.length, 2>();
506
+ * ```
507
+ *
508
+ * @group Assertions
509
+ */
510
+ export const assertLength = (value, expectedLength) => {
511
+ assert(eqSameValue(value.length, expectedLength), `Expected value to have length ${expectedLength}.`, {
512
+ actual: value.length,
513
+ expected: expectedLength,
514
+ operator: "strictEqual",
515
+ stackStartFn: assertLength,
516
+ });
517
+ };
518
+ /**
519
+ * Asserts that an array is non-empty.
195
520
  *
196
521
  * ### Example
197
522
  *
@@ -207,21 +532,25 @@ export const assertNotUndefined = (value, message = "Expected value not to be un
207
532
  *
208
533
  * const values = [1, 2, 3];
209
534
  * assertNonEmptyArray(values);
210
- * assertType<NonEmptyArray<number>, typeof values>();
535
+ * assertType<typeof values, NonEmptyArray<number>>();
211
536
  * assertEqual(values[0], 1);
212
537
  * const result = trySync(() => assertNonEmptyArray([]));
213
538
  * assertErr(result);
214
539
  * ```
540
+ *
541
+ * @group Assertions
215
542
  */
216
543
  export const assertNonEmptyArray = (arr, message = "Expected a non-empty array.") => {
217
- assert(arr.length > 0, message);
544
+ assert(arr.length > 0, message, {
545
+ actual: arr.length,
546
+ expected: 0,
547
+ operator: ">",
548
+ stackStartFn: assertNonEmptyArray,
549
+ });
218
550
  };
219
551
  /**
220
552
  * Asserts that a readonly array is non-empty.
221
553
  *
222
- * Use this when a readonly array is logically guaranteed to be non-empty but
223
- * TypeScript cannot prove it.
224
- *
225
554
  * ### Example
226
555
  *
227
556
  * ```ts
@@ -236,14 +565,21 @@ export const assertNonEmptyArray = (arr, message = "Expected a non-empty array."
236
565
  *
237
566
  * const values: ReadonlyArray<number> = [1, 2, 3];
238
567
  * assertNonEmptyReadonlyArray(values);
239
- * assertType<NonEmptyReadonlyArray<number>, typeof values>();
568
+ * assertType<typeof values, NonEmptyReadonlyArray<number>>();
240
569
  * assertEqual(values[0], 1);
241
570
  * const result = trySync(() => assertNonEmptyReadonlyArray([]));
242
571
  * assertErr(result);
243
572
  * ```
573
+ *
574
+ * @group Assertions
244
575
  */
245
576
  export const assertNonEmptyReadonlyArray = (arr, message = "Expected a non-empty readonly array.") => {
246
- assert(arr.length > 0, message);
577
+ assert(arr.length > 0, message, {
578
+ actual: arr.length,
579
+ expected: 0,
580
+ operator: ">",
581
+ stackStartFn: assertNonEmptyReadonlyArray,
582
+ });
247
583
  };
248
584
  /**
249
585
  * Guards synchronous methods on objects that may be called after disposal.
@@ -254,27 +590,236 @@ export const assertNonEmptyReadonlyArray = (arr, message = "Expected a non-empty
254
590
  *
255
591
  * ```ts
256
592
  * import {
257
- * assert,
258
593
  * assertEqual,
259
594
  * assertErr,
595
+ * assertInstanceOf,
260
596
  * assertNotDisposed,
261
597
  * trySync,
262
598
  * } from "@evolu/common";
263
599
  *
264
- * await using disposer = new globalThis.AsyncDisposableStack();
600
+ * await using disposer = new AsyncDisposableStack();
265
601
  * assertNotDisposed(disposer);
266
602
  * await disposer.disposeAsync();
267
603
  * const result = trySync(() => assertNotDisposed(disposer));
268
604
  * assertErr(result);
269
- * assert(result.error instanceof Error, "Expected an Error.");
605
+ * assertInstanceOf(result.error, Error);
270
606
  * assertEqual(result.error.message, "Cannot use a disposed object.");
271
607
  * ```
272
608
  *
273
609
  * This is the JavaScript equivalent of the .NET `ObjectDisposedException`
274
610
  * pattern: once a helper has been disposed, calling its synchronous methods is
275
- * a programmer error and should throw immediately instead of continuing with
276
- * invalid state.
611
+ * a bug and should throw immediately instead of continuing with invalid state.
612
+ *
613
+ * @group Assertions
277
614
  */
278
615
  export const assertNotDisposed = (value) => {
279
- assert(!value.disposed, "Cannot use a disposed object.");
616
+ assert(!value.disposed, "Cannot use a disposed object.", {
617
+ actual: value.disposed,
618
+ expected: false,
619
+ operator: "==",
620
+ stackStartFn: assertNotDisposed,
621
+ });
622
+ };
623
+ /**
624
+ * Asserts that two values are the same using `Object.is`.
625
+ *
626
+ * Use this when exact sameness is the contract, such as asserting reference
627
+ * identity or narrowing the actual value to the expected value's type, as shown
628
+ * below. For value comparisons, use {@link assertEqual}.
629
+ *
630
+ * Uses the same equality semantics as `assert.strictEqual` from
631
+ * `node:assert/strict`, but is platform-agnostic. See {@link eqSameValue} for
632
+ * the equality semantics.
633
+ *
634
+ * ### Example
635
+ *
636
+ * ```ts
637
+ * import { assertSame, assertType } from "@evolu/common";
638
+ *
639
+ * interface User {
640
+ * readonly name: string;
641
+ * }
642
+ *
643
+ * const user: User = { name: "Ada" };
644
+ * const value: unknown = user;
645
+ *
646
+ * assertSame(value, user);
647
+ * // `assertSame` narrows `value` from `unknown` to `User`.
648
+ * assertType<typeof value, User>();
649
+ * ```
650
+ *
651
+ * @group Assertions
652
+ */
653
+ export const assertSame = (actual, expected) => {
654
+ // `Object.is` implements ECMAScript SameValue, whereas strict equality means
655
+ // `===`. Keep the platform-agnostic "same" wording instead of reproducing
656
+ // Node.js's value-dependent messages, which sometimes say "strictly equal".
657
+ assert(eqSameValue(actual, expected), "Expected values to be the same.", {
658
+ actual,
659
+ expected,
660
+ operator: "strictEqual",
661
+ stackStartFn: assertSame,
662
+ });
280
663
  };
664
+ /**
665
+ * Asserts that two values are not the same using `Object.is`.
666
+ *
667
+ * The opposite of {@link assertSame}.
668
+ *
669
+ * ### Example
670
+ *
671
+ * ```ts
672
+ * import { assertNotSame } from "@evolu/common";
673
+ *
674
+ * const first = { name: "Ada" };
675
+ * const second = { name: "Ada" };
676
+ *
677
+ * assertNotSame(first, second);
678
+ * assertNotSame(0, -0);
679
+ * ```
680
+ *
681
+ * @group Assertions
682
+ */
683
+ export const assertNotSame = (actual, expected) => {
684
+ // `Object.is` implements ECMAScript SameValue, whereas strict equality means
685
+ // `===`. Keep the platform-agnostic "same" wording instead of reproducing
686
+ // Node.js's value-dependent messages, which sometimes say "strictly equal".
687
+ assert(!eqSameValue(actual, expected), "Expected values not to be the same.", {
688
+ actual,
689
+ expected,
690
+ operator: "notStrictEqual",
691
+ stackStartFn: assertNotSame,
692
+ });
693
+ };
694
+ // oxlint-disable eslint/func-style -- Function declarations make TypeDoc categorize these APIs as functions.
695
+ /**
696
+ * Asserts that two values are equal.
697
+ *
698
+ * Use this for value comparisons of both primitive and deeply structured
699
+ * values. Use {@link assertSame} when exact sameness or narrowing is required.
700
+ *
701
+ * Like `assert.deepEqual` from `node:assert/strict`, it performs deep
702
+ * structural comparison, but uses deliberately smaller, platform-agnostic
703
+ * equality logic based on {@link eqData}. More complex comparison logic is
704
+ * difficult to reason about and rarely needed. Other values are opaque and
705
+ * compare equal only by identity. If broader comparison semantics are needed in
706
+ * Node.js, use the native assertion instead.
707
+ *
708
+ * ### Example
709
+ *
710
+ * ```ts
711
+ * import { assertEqual } from "@evolu/common";
712
+ *
713
+ * assertEqual(21 * 2, 42);
714
+ *
715
+ * const actual: unknown = new Map([
716
+ * ["roles", new Set(["admin", "author"])],
717
+ * ]);
718
+ * const expected = new Map([["roles", new Set(["author", "admin"])]]);
719
+ *
720
+ * assertEqual(actual, expected);
721
+ * ```
722
+ *
723
+ * @group Assertions
724
+ */
725
+ export function assertEqual(actual, expected) {
726
+ assert(eqUnknown(actual, expected), "Expected values to be equal.", {
727
+ actual,
728
+ expected,
729
+ operator: "eqData",
730
+ diff: "full",
731
+ stackStartFn: assertEqual,
732
+ });
733
+ }
734
+ /**
735
+ * Asserts that a `Uint8Array` contains the expected bytes.
736
+ *
737
+ * Uses {@link eqArrayNumber}, so the runtime representations may differ. For
738
+ * example, a `Uint8Array` can be compared directly with a regular array.
739
+ *
740
+ * ### Example
741
+ *
742
+ * ```ts
743
+ * import { assertEqualBytes } from "@evolu/common";
744
+ *
745
+ * const bytes = new Uint8Array([1, 5, 39, 254]);
746
+ *
747
+ * assertEqualBytes(bytes, [1, 5, 39, 254]);
748
+ * ```
749
+ *
750
+ * @group Assertions
751
+ */
752
+ export const assertEqualBytes = (actual, expected) => {
753
+ assert(eqArrayNumber(actual, expected), "Expected bytes to be equal.", {
754
+ actual,
755
+ expected,
756
+ operator: "eqArrayNumber",
757
+ diff: "full",
758
+ stackStartFn: assertEqualBytes,
759
+ });
760
+ };
761
+ /**
762
+ * Asserts that two values are not equal.
763
+ *
764
+ * The opposite of {@link assertEqual}.
765
+ *
766
+ * ### Example
767
+ *
768
+ * ```ts
769
+ * import { assertNotEqual } from "@evolu/common";
770
+ *
771
+ * assertNotEqual({ name: "Ada" }, { name: "Grace" });
772
+ * assertNotEqual([1, 2], [2, 1]);
773
+ * ```
774
+ *
775
+ * @group Assertions
776
+ */
777
+ export function assertNotEqual(actual, expected) {
778
+ assert(!eqUnknown(actual, expected), "Expected values not to be equal.", {
779
+ actual,
780
+ expected,
781
+ operator: "notEqData",
782
+ stackStartFn: assertNotEqual,
783
+ });
784
+ }
785
+ // oxlint-enable eslint/func-style
786
+ // Assertion equality extends eqData to unknown values. The comparator's
787
+ // SameValue fast path treats identical opaque values as equal, while its
788
+ // unsupported branches make distinct opaque values unequal.
789
+ const eqUnknown = eqData;
790
+ export function assertOk(result, ...comparison) {
791
+ assert(result.ok, "Expected an Ok result.", {
792
+ actual: result.ok,
793
+ expected: true,
794
+ operator: "strictEqual",
795
+ stackStartFn: assertOk,
796
+ });
797
+ if (comparison.length === 0)
798
+ return;
799
+ const expectedValue = comparison[0];
800
+ assert((comparison[1] ?? eqUnknown)(result.value, expectedValue), "Expected the value to equal the expected value.", {
801
+ actual: result.value,
802
+ expected: expectedValue,
803
+ operator: comparison[1] === undefined ? "eqData" : "Eq",
804
+ diff: comparison[1] === undefined ? "full" : undefined,
805
+ stackStartFn: assertOk,
806
+ });
807
+ }
808
+ export function assertErr(result, ...comparison) {
809
+ assert(!result.ok, "Expected an Err result.", {
810
+ actual: result.ok,
811
+ expected: false,
812
+ operator: "strictEqual",
813
+ stackStartFn: assertErr,
814
+ });
815
+ if (comparison.length === 0)
816
+ return;
817
+ const expectedError = comparison[0];
818
+ assert((comparison[1] ?? eqUnknown)(result.error, expectedError), "Expected the error to equal the expected error.", {
819
+ actual: result.error,
820
+ expected: expectedError,
821
+ operator: comparison[1] === undefined ? "eqData" : "Eq",
822
+ diff: comparison[1] === undefined ? "full" : undefined,
823
+ stackStartFn: assertErr,
824
+ });
825
+ }