@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
package/src/Assert.ts CHANGED
@@ -1,11 +1,49 @@
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
44
 
7
45
  import type { NonEmptyArray, NonEmptyReadonlyArray } from "./Array.ts";
8
- import { eqData, eqStrict, type Eq } from "./Eq.ts";
46
+ import { eqArrayNumber, eqData, eqSameValue, type Eq } from "./Eq.ts";
9
47
  import type {
10
48
  AnyResult,
11
49
  Err,
@@ -14,306 +52,668 @@ import type {
14
52
  Ok,
15
53
  Result,
16
54
  } from "./Result.ts";
17
- import type { Data, IsData, Type } from "./Type.ts";
18
- import type { CompileTimeError } from "./Types.ts";
55
+ import type { assertType, Type } from "./Type.ts";
56
+ import type { ValueWithLength } from "./Types.ts";
19
57
 
20
58
  /**
21
- * Ensures a condition is true, throwing an error with the provided message if
22
- * not.
59
+ * Asserts that a condition is truthy.
60
+ *
61
+ * Throws an `AssertionError` with the provided message if the condition is
62
+ * falsy, preventing invalid state from propagating and making the failure
63
+ * easier to diagnose.
23
64
  *
24
- * Prevents invalid states from propagating through the system by halting
25
- * execution when a condition fails, improving reliability and debuggability.
65
+ * Use `assert` only when a more specific assertion, such as {@link assertTrue}
66
+ * or {@link assertEqual}, does not apply. For that reason, `assert` requires a
67
+ * custom message explaining the expected condition.
26
68
  *
27
- * Do not use this instead of {@link Type}. Assertions are intended when a
28
- * condition is logically guaranteed to be true but TypeScript cannot prove it,
29
- * or for catching and signaling developer mistakes eagerly.
69
+ * Options provide structured diagnostics for custom assertions. Pass an
70
+ * underlying failure as `cause` when it explains why the asserted condition
71
+ * failed, and pass the custom assertion as `stackStartFn` to omit its
72
+ * implementation from the stack trace.
30
73
  *
31
74
  * ### Example
32
75
  *
33
76
  * ```ts
34
- * import { assert, assertEqual, assertErr, trySync } from "@evolu/common";
77
+ * import { assert, assertEqual, assertType } from "@evolu/common";
35
78
  *
36
- * assert(true, "Expected true.");
37
- * const result = trySync(() => assert(false, "Expected true."));
38
- * assertErr(result);
39
- * assert(result.error instanceof Error, "Expected an Error.");
40
- * assertEqual(result.error.message, "Expected true.");
79
+ * const value: unknown = "Evolu";
80
+ * assert(typeof value === "string", "Expected a string.");
81
+ *
82
+ * assertType<typeof value, string>();
83
+ * assertEqual(value, "Evolu");
41
84
  * ```
85
+ *
86
+ * @group Assertions
42
87
  */
43
88
  export const assert: (
44
89
  condition: unknown,
45
90
  message: string,
46
- ) => asserts condition = (condition, message) => {
91
+ options?: {
92
+ readonly actual?: unknown;
93
+ readonly expected?: unknown;
94
+ readonly operator?: string;
95
+ readonly diff?: "full" | undefined;
96
+ readonly cause?: unknown;
97
+ readonly stackStartFn?: (...args: Array<never>) => unknown;
98
+ },
99
+ ) => asserts condition = (condition, message, options = {}) => {
47
100
  // oxlint-disable-next-line typescript/strict-boolean-expressions -- JavaScript truthiness is the contract of assert.
48
- if (!condition) {
49
- throw new Error(message);
101
+ if (condition) return;
102
+
103
+ const actual = Object.hasOwn(options, "actual") ? options.actual : condition;
104
+ const expected = Object.hasOwn(options, "expected") ? options.expected : true;
105
+ const operator = options.operator ?? "==";
106
+ const stackStartFn = options.stackStartFn ?? assert;
107
+ if (NodeAssert !== undefined) {
108
+ const error = new NodeAssert.AssertionError({
109
+ message,
110
+ actual,
111
+ expected,
112
+ operator: options.diff === "full" ? "deepStrictEqual" : operator,
113
+ diff: options.diff,
114
+ stackStartFn,
115
+ });
116
+
117
+ if (options.diff === "full") {
118
+ error.operator = operator;
119
+ }
120
+
121
+ if (options.cause !== undefined) {
122
+ Object.defineProperty(error, "cause", {
123
+ configurable: true,
124
+ value: options.cause,
125
+ writable: true,
126
+ });
127
+ }
128
+
129
+ throw error;
50
130
  }
131
+
132
+ const error = Object.assign(
133
+ new Error(
134
+ message,
135
+ options.cause === undefined ? undefined : { cause: options.cause },
136
+ ),
137
+ {
138
+ name: "AssertionError",
139
+ code: "ERR_ASSERTION" as const,
140
+ actual,
141
+ expected,
142
+ generatedMessage: false,
143
+ operator,
144
+ },
145
+ );
146
+
147
+ Error.captureStackTrace?.(error, stackStartFn);
148
+
149
+ throw error;
51
150
  };
52
151
 
152
+ // Node.js generates assertion diffs inside its AssertionError constructor; the
153
+ // test runner does not derive them from actual and expected metadata alone. Use
154
+ // the native constructor when available and the portable fallback elsewhere.
155
+ const NodeAssert = globalThis.process?.getBuiltinModule?.("node:assert/strict");
156
+
53
157
  /**
54
- * Asserts that two values are the same according to {@link eqStrict}.
158
+ * Asserts that a value is exactly `true`.
55
159
  *
56
- * `eqStrict` uses SameValue equality: it considers `NaN` the same as itself,
57
- * distinguishes `0` from `-0`, and compares objects by reference identity.
160
+ * Boolean conditions preserve their control-flow narrowing, including named
161
+ * type guards. Unknown values narrow to the literal `true`. Unlike
162
+ * {@link assert}, this checks an exact boolean value instead of truthiness and
163
+ * does not require a custom message.
58
164
  *
59
165
  * ### Example
60
166
  *
61
167
  * ```ts
62
- * import { assertSame, assertType } from "@evolu/common";
168
+ * import { assertTrue, assertType } from "@evolu/common";
63
169
  *
64
170
  * interface User {
65
171
  * readonly name: string;
66
172
  * }
67
173
  *
68
- * const user: User = { name: "Ada" };
69
- * const value: unknown = user;
174
+ * const isUser = (value: unknown): value is User =>
175
+ * typeof value === "object" &&
176
+ * value !== null &&
177
+ * "name" in value &&
178
+ * typeof value.name === "string";
70
179
  *
71
- * assertSame(value, user);
72
- * assertType<User, typeof value>();
180
+ * const value: unknown = { name: "Ada" };
181
+ * assertTrue(isUser(value));
182
+ * assertType<typeof value, User>();
183
+ *
184
+ * const condition: unknown = true;
185
+ * assertTrue(condition);
186
+ * assertType<typeof condition, true>();
73
187
  * ```
188
+ *
189
+ * @group Assertions
74
190
  */
75
- export const assertSame: <Expected>(
76
- actual: unknown,
77
- expected: Expected,
78
- ) => asserts actual is Expected = (actual, expected) => {
79
- assert(eqStrict(actual, expected), "Expected values to be the same.");
80
- };
191
+ export function assertTrue(condition: boolean): asserts condition;
192
+ export function assertTrue(value: unknown): asserts value is true;
193
+ export function assertTrue(value: unknown): void {
194
+ assert(value === true, "Expected true.", {
195
+ actual: value,
196
+ expected: true,
197
+ operator: "strictEqual",
198
+ stackStartFn: assertTrue,
199
+ });
200
+ }
81
201
 
82
202
  /**
83
- * Asserts that a value is exactly `true` and narrows it to `true`.
203
+ * Asserts that a value is exactly `false` and narrows it to `false`.
84
204
  *
85
- * Unlike {@link assert}, this checks an exact boolean value instead of
86
- * truthiness and does not require a custom message.
205
+ * Unlike {@link assert}, this checks an exact boolean value instead of falsiness
206
+ * and does not require a custom message.
87
207
  *
88
208
  * ### Example
89
209
  *
90
210
  * ```ts
91
- * import { assertTrue, assertType } from "@evolu/common";
211
+ * import { assertFalse, assertType } from "@evolu/common";
92
212
  *
93
- * const value: unknown = true;
94
- * assertTrue(value);
95
- * assertType<true, typeof value>();
213
+ * const value: unknown = false;
214
+ * assertFalse(value);
215
+ * assertType<typeof value, false>();
96
216
  * ```
217
+ *
218
+ * @group Assertions
97
219
  */
98
- export const assertTrue: (value: unknown) => asserts value is true = (
220
+ export const assertFalse: (value: unknown) => asserts value is false = (
99
221
  value,
100
222
  ) => {
101
- assert(value === true, "Expected true.");
223
+ assert(value === false, "Expected false.", {
224
+ actual: value,
225
+ expected: false,
226
+ operator: "strictEqual",
227
+ stackStartFn: assertFalse,
228
+ });
102
229
  };
103
230
 
104
231
  /**
105
- * Asserts that a value is exactly `false` and narrows it to `false`.
232
+ * Asserts that a condition becomes true after exactly the specified number of
233
+ * microtasks.
106
234
  *
107
- * Unlike {@link assert}, this checks an exact boolean value instead of falsiness
108
- * and does not require a custom message.
235
+ * Use this in tests that intentionally specify async scheduling behavior.
236
+ * Application code must not depend on exact microtask counts. Maintainers
237
+ * should review count changes because they indicate that an async pipeline
238
+ * changed.
109
239
  *
110
240
  * ### Example
111
241
  *
112
242
  * ```ts
113
- * import { assertFalse, assertType } from "@evolu/common";
243
+ * import { assertConditionAfterMicrotasks } from "@evolu/common";
114
244
  *
115
- * const value: unknown = false;
116
- * assertFalse(value);
117
- * assertType<false, typeof value>();
245
+ * let ready = false;
246
+ * queueMicrotask(() => {
247
+ * ready = true;
248
+ * });
249
+ *
250
+ * await assertConditionAfterMicrotasks(() => ready, 1);
118
251
  * ```
252
+ *
253
+ * @group Assertions
119
254
  */
120
- export const assertFalse: (value: unknown) => asserts value is false = (
121
- value,
122
- ) => {
123
- assert(value === false, "Expected false.");
124
- };
255
+ export function assertConditionAfterMicrotasks(
256
+ condition: () => boolean,
257
+ expectedMicrotaskCount: number,
258
+ ): Promise<void>;
259
+ export async function assertConditionAfterMicrotasks(
260
+ condition: () => boolean,
261
+ expectedMicrotaskCount: number,
262
+ stackStartFn: (
263
+ ...args: Array<never>
264
+ ) => unknown = assertConditionAfterMicrotasks,
265
+ ): Promise<void> {
266
+ for (
267
+ let microtaskCount = 0;
268
+ microtaskCount < expectedMicrotaskCount;
269
+ microtaskCount++
270
+ ) {
271
+ const actual = condition();
272
+ assert(
273
+ !actual,
274
+ `Expected condition to be false after ${microtaskCount} microtasks.`,
275
+ {
276
+ actual,
277
+ expected: false,
278
+ operator: "strictEqual",
279
+ stackStartFn,
280
+ },
281
+ );
282
+ await Promise.resolve();
283
+ }
284
+
285
+ const actual = condition();
286
+ assert(
287
+ actual,
288
+ `Expected condition to be true after exactly ${expectedMicrotaskCount} microtasks.`,
289
+ {
290
+ actual,
291
+ expected: true,
292
+ operator: "strictEqual",
293
+ stackStartFn,
294
+ },
295
+ );
296
+ }
125
297
 
126
298
  /**
127
- * Asserts that two {@link Data} values are equal according to {@link eqData}.
299
+ * Asserts that a Promise continuation runs after exactly the specified number
300
+ * of microtasks.
128
301
  *
129
- * Use this for concise value checks in platform-independent examples. Use
130
- * {@link assert} with a descriptive message for application invariants.
302
+ * Both fulfillment and rejection count as running the continuation. Use this in
303
+ * tests that intentionally specify async scheduling behavior. Application code
304
+ * must not depend on exact microtask counts. Maintainers should review count
305
+ * changes because they indicate that an async pipeline changed.
131
306
  *
132
307
  * ### Example
133
308
  *
134
309
  * ```ts
135
- * import { assertEqual } from "@evolu/common";
310
+ * import { assertContinuationAfterMicrotasks } from "@evolu/common";
136
311
  *
137
- * const actual = new Map([["roles", new Set(["admin", "author"])]]);
138
- * const expected = new Map([["roles", new Set(["author", "admin"])]]);
312
+ * await assertContinuationAfterMicrotasks(Promise.resolve("ready"), 1);
313
+ * ```
139
314
  *
140
- * assertEqual(actual, expected);
315
+ * @group Assertions
316
+ */
317
+ export const assertContinuationAfterMicrotasks = async (
318
+ promise: Promise<unknown>,
319
+ expectedMicrotaskCount: number,
320
+ ): Promise<void> => {
321
+ let continuationCalled = false;
322
+
323
+ const markContinuationCalled = (): void => {
324
+ continuationCalled = true;
325
+ };
326
+
327
+ void promise.then(markContinuationCalled, markContinuationCalled);
328
+
329
+ await assertConditionAfterMicrotasksWithStackStartFn(
330
+ () => continuationCalled,
331
+ expectedMicrotaskCount,
332
+ assertContinuationAfterMicrotasks,
333
+ );
334
+ };
335
+
336
+ const assertConditionAfterMicrotasksWithStackStartFn =
337
+ assertConditionAfterMicrotasks as (
338
+ condition: () => boolean,
339
+ expectedMicrotaskCount: number,
340
+ stackStartFn: (...args: Array<never>) => unknown,
341
+ ) => Promise<void>;
342
+
343
+ /**
344
+ * Asserts that a function throws the expected value.
345
+ *
346
+ * Expected values use {@link assertEqual} semantics. To perform several or
347
+ * specialized assertions, pass an assertion function that receives the thrown
348
+ * value and returns nothing. Returning a predicate result fails. Use
349
+ * {@link assertThrowsSame} for SameValue or reference identity and
350
+ * {@link assertThrowsInstanceOf} for a runtime type.
351
+ *
352
+ * Because JavaScript permits throwing functions, a function second argument is
353
+ * always treated as an assertion function. Use `assertThrowsSame` to assert
354
+ * that a particular function was thrown.
355
+ *
356
+ * ### Example
357
+ *
358
+ * ```ts
359
+ * import { assertThrows } from "@evolu/common";
360
+ *
361
+ * assertThrows(
362
+ * () => {
363
+ * // oxlint-disable-next-line eslint/no-throw-literal, typescript/only-throw-error -- Exercise arbitrary thrown values.
364
+ * throw { type: "ExpectedFailure", value: 42 };
365
+ * },
366
+ * { type: "ExpectedFailure", value: 42 },
367
+ * );
141
368
  * ```
369
+ *
370
+ * @group Assertions
142
371
  */
143
- export function assertEqual<Actual, Expected>(
144
- actual: Actual,
145
- expected: Expected,
146
- ...dataError: AssertEqualError<Actual | Expected>
372
+ export function assertThrows(
373
+ run: () => unknown,
374
+ assertThrown: (thrown: unknown) => void,
147
375
  ): void;
148
- export function assertEqual(actual: Data, expected: Data): void {
149
- assert(eqData(actual, expected), "Expected values to be equal.");
376
+ export function assertThrows(run: () => unknown, expected: unknown): void;
377
+ export function assertThrows(
378
+ run: () => unknown,
379
+ expectedOrAssert: unknown,
380
+ ): void {
381
+ const thrown = captureThrown(run, assertThrows);
382
+
383
+ if (typeof expectedOrAssert === "function") {
384
+ const result = (expectedOrAssert as (thrown: unknown) => unknown)(thrown);
385
+ assert(
386
+ result === undefined,
387
+ "Expected the thrown value assertion to return undefined.",
388
+ {
389
+ actual: result,
390
+ expected: undefined,
391
+ operator: "strictEqual",
392
+ stackStartFn: assertThrows,
393
+ },
394
+ );
395
+ return;
396
+ }
397
+
398
+ assert(
399
+ eqUnknown(thrown, expectedOrAssert),
400
+ "Expected the thrown value to equal the expected value.",
401
+ {
402
+ actual: thrown,
403
+ expected: expectedOrAssert,
404
+ operator: "eqData",
405
+ diff: "full",
406
+ stackStartFn: assertThrows,
407
+ },
408
+ );
150
409
  }
151
410
 
152
- type AssertEqualError<Value> =
153
- IsData<Value> extends true
154
- ? []
155
- : [
156
- error: CompileTimeError<
157
- "assertEqual",
158
- "Actual and expected values must consist only of Data."
159
- >,
160
- ];
411
+ /**
412
+ * Asserts that a function throws the same value using `Object.is`.
413
+ *
414
+ * Use this to verify that a thrown value was propagated unchanged. For value
415
+ * comparisons, use {@link assertThrows}.
416
+ *
417
+ * ### Example
418
+ *
419
+ * ```ts
420
+ * import { assertThrowsSame } from "@evolu/common";
421
+ *
422
+ * const expected = new Error("Unavailable.");
423
+ * assertThrowsSame(() => {
424
+ * throw expected;
425
+ * }, expected);
426
+ * ```
427
+ *
428
+ * @group Assertions
429
+ */
430
+ export const assertThrowsSame = (
431
+ run: () => unknown,
432
+ expected: unknown,
433
+ ): void => {
434
+ const thrown = captureThrown(run, assertThrowsSame);
435
+ assert(
436
+ eqSameValue(thrown, expected),
437
+ "Expected the thrown value to be the same as the expected value.",
438
+ {
439
+ actual: thrown,
440
+ expected,
441
+ operator: "strictEqual",
442
+ stackStartFn: assertThrowsSame,
443
+ },
444
+ );
445
+ };
161
446
 
162
447
  /**
163
- * Asserts that a {@link Result} is an {@link Ok}, optionally compares its value,
164
- * and narrows the Result.
448
+ * Asserts that a function throws an instance of a constructor.
165
449
  *
166
- * With only a Result, this checks the variant without inspecting the value.
167
- * When an expected value is provided, it uses {@link eqData}; its Data type is
168
- * inferred independently, so an unbranded literal can compare a branded
169
- * primitive or collection. Pass a custom {@link Eq} when either value is outside
170
- * {@link Data} or needs domain-specific equality.
450
+ * Returns the narrowed instance so additional properties can be asserted
451
+ * without running the function again.
171
452
  *
172
453
  * ### Example
173
454
  *
174
455
  * ```ts
175
- * import {
176
- * assertEqual,
177
- * assertOk,
178
- * assertType,
179
- * ok,
180
- * type Ok,
181
- * type Result,
182
- * type Typed,
183
- * } from "@evolu/common";
456
+ * import { assertEqual, assertThrowsInstanceOf } from "@evolu/common";
184
457
  *
185
- * interface User {
186
- * readonly id: string;
187
- * }
458
+ * const error = assertThrowsInstanceOf(() => {
459
+ * throw new TypeError("Expected a string.");
460
+ * }, TypeError);
461
+ * assertEqual(error.message, "Expected a string.");
462
+ * ```
188
463
  *
189
- * interface UserNotFoundError extends Typed<"UserNotFound"> {}
464
+ * @group Assertions
465
+ */
466
+ export const assertThrowsInstanceOf = <
467
+ T extends {
468
+ readonly name: string;
469
+ } & (abstract new (...args: Array<never>) => unknown),
470
+ >(
471
+ run: () => unknown,
472
+ constructor: T,
473
+ ): InstanceType<T> => {
474
+ const thrown = captureThrown(run, assertThrowsInstanceOf);
475
+ assert(
476
+ thrown instanceof constructor,
477
+ constructor.name === ""
478
+ ? "Expected the thrown value to be an instance of the provided constructor."
479
+ : `Expected the thrown value to be an instance of ${constructor.name}.`,
480
+ {
481
+ actual: thrown,
482
+ expected: constructor,
483
+ operator: "instanceof",
484
+ stackStartFn: assertThrowsInstanceOf,
485
+ },
486
+ );
487
+ return thrown as InstanceType<T>;
488
+ };
489
+
490
+ const captureThrown = (
491
+ run: () => unknown,
492
+ stackStartFn: (...args: Array<never>) => unknown,
493
+ ): unknown => {
494
+ let value: unknown;
495
+
496
+ try {
497
+ value = run();
498
+ } catch (error) {
499
+ return error;
500
+ }
501
+
502
+ assert(false, "Expected function to throw.", {
503
+ actual: value,
504
+ expected: "throw",
505
+ operator: "throws",
506
+ stackStartFn,
507
+ });
508
+ };
509
+
510
+ /**
511
+ * Asserts that a promise rejects with the expected value.
190
512
  *
191
- * const result: Result<User, UserNotFoundError> = ok({ id: "user-1" });
513
+ * Expected values use {@link assertEqual} semantics. To perform several or
514
+ * specialized assertions, pass an assertion function that receives the
515
+ * rejection reason and returns nothing. Returning a predicate result fails. Use
516
+ * {@link assertRejectsSame} for SameValue or reference identity and
517
+ * {@link assertRejectsInstanceOf} for a runtime type.
518
+ *
519
+ * Because JavaScript permits rejecting with functions, a function second
520
+ * argument is always treated as an assertion function. Use `assertRejectsSame`
521
+ * to assert that a particular function was rejected.
192
522
  *
193
- * assertOk(result);
194
- * assertType<Ok<User>, typeof result>();
195
- * assertEqual(result.value.id, "user-1");
523
+ * ### Example
524
+ *
525
+ * ```ts
526
+ * import { assertRejects } from "@evolu/common";
527
+ *
528
+ * const expected = new Error("Unavailable.");
529
+ * await assertRejects(Promise.reject(expected), expected);
196
530
  * ```
531
+ *
532
+ * @group Assertions
197
533
  */
198
- export function assertOk<R extends Result<unknown, unknown>>(
199
- result: R,
200
- ): asserts result is Extract<R, Ok<unknown>>;
201
- export function assertOk<R extends Result<unknown, unknown>>(
202
- result: R,
203
- expectedValue: InferOk<R>,
204
- eq: Eq<InferOk<R>>,
205
- ): asserts result is Extract<R, Ok<unknown>>;
206
- // Constrain the argument tuple so any cannot absorb the CompileTimeError.
207
- export function assertOk<R extends Result<unknown, unknown>, ExpectedValue>(
208
- result: R,
209
- ...comparison: [expectedValue: ExpectedValue] &
210
- (IsData<InferOk<R> | ExpectedValue> extends true
211
- ? unknown
212
- : {
213
- readonly [
214
- Error in CompileTimeError<
215
- "assertOk",
216
- "Result value and expected value must consist only of Data when no custom Eq is provided."
217
- >
218
- ]: never;
219
- })
220
- ): asserts result is Extract<R, Ok<unknown>>;
221
- export function assertOk(
222
- result: AnyResult,
223
- ...comparison: [] | [expectedValue: unknown, eq?: Eq<any>]
224
- ): asserts result is Ok<unknown> {
225
- assert(result.ok, "Expected an Ok result.");
226
- if (comparison.length === 0) return;
534
+ export function assertRejects(
535
+ promise: PromiseLike<unknown>,
536
+ assertRejected: (reason: unknown) => void,
537
+ ): Promise<void>;
538
+ export function assertRejects(
539
+ promise: PromiseLike<unknown>,
540
+ expected: unknown,
541
+ ): Promise<void>;
542
+ export async function assertRejects(
543
+ promise: PromiseLike<unknown>,
544
+ expectedOrAssert: unknown,
545
+ ): Promise<void> {
546
+ const rejected = await captureRejected(promise, assertRejects);
547
+
548
+ if (typeof expectedOrAssert === "function") {
549
+ const result = (expectedOrAssert as (reason: unknown) => unknown)(rejected);
550
+ assert(
551
+ result === undefined,
552
+ "Expected the rejection assertion to return undefined.",
553
+ {
554
+ actual: result,
555
+ expected: undefined,
556
+ operator: "strictEqual",
557
+ stackStartFn: assertRejects,
558
+ },
559
+ );
560
+ return;
561
+ }
227
562
 
228
- const expectedValue = comparison[0];
229
- const eq: Eq<any> = comparison[1] ?? eqData;
230
563
  assert(
231
- eq(result.value, expectedValue),
232
- "Expected the value to equal the expected value.",
564
+ eqUnknown(rejected, expectedOrAssert),
565
+ "Expected the rejection reason to equal the expected value.",
566
+ {
567
+ actual: rejected,
568
+ expected: expectedOrAssert,
569
+ operator: "eqData",
570
+ diff: "full",
571
+ stackStartFn: assertRejects,
572
+ },
233
573
  );
234
574
  }
235
575
 
236
576
  /**
237
- * Asserts that a {@link Result} is an {@link Err}, optionally compares its error,
238
- * and narrows the Result.
577
+ * Asserts that a promise rejects with the same value using `Object.is`.
239
578
  *
240
- * With only a Result, this checks the variant without inspecting the error.
241
- * When an expected error is provided, it uses {@link eqData}; its Data type is
242
- * inferred independently, so an unbranded literal can compare a branded
243
- * primitive or collection. Pass a custom {@link Eq} when either value is outside
244
- * {@link Data} or needs domain-specific equality.
579
+ * Use this to verify that a rejection reason was propagated unchanged. For
580
+ * value comparisons, use {@link assertRejects}.
245
581
  *
246
582
  * ### Example
247
583
  *
248
584
  * ```ts
249
- * import {
250
- * assertEqual,
251
- * assertErr,
252
- * assertType,
253
- * err,
254
- * type Err,
255
- * type Result,
256
- * type Typed,
257
- * } from "@evolu/common";
585
+ * import { assertRejectsSame } from "@evolu/common";
258
586
  *
259
- * interface UserNotFoundError extends Typed<"UserNotFound"> {
260
- * readonly id: string;
261
- * }
587
+ * const expected = new Error("Unavailable.");
588
+ * await assertRejectsSame(Promise.reject(expected), expected);
589
+ * ```
262
590
  *
263
- * const result: Result<string, UserNotFoundError> = err({
264
- * type: "UserNotFound",
265
- * id: "user-1",
266
- * });
591
+ * @group Assertions
592
+ */
593
+ export const assertRejectsSame = async (
594
+ promise: PromiseLike<unknown>,
595
+ expected: unknown,
596
+ ): Promise<void> => {
597
+ const rejected = await captureRejected(promise, assertRejectsSame);
598
+ assert(
599
+ eqSameValue(rejected, expected),
600
+ "Expected the rejection reason to be the same as the expected value.",
601
+ {
602
+ actual: rejected,
603
+ expected,
604
+ operator: "strictEqual",
605
+ stackStartFn: assertRejectsSame,
606
+ },
607
+ );
608
+ };
609
+
610
+ /**
611
+ * Asserts that a promise rejects with an instance of a constructor.
267
612
  *
268
- * assertErr(result);
269
- * assertType<Err<UserNotFoundError>, typeof result>();
270
- * assertEqual(result.error.id, "user-1");
613
+ * Returns the narrowed instance so additional properties can be asserted.
614
+ *
615
+ * ### Example
616
+ *
617
+ * ```ts
618
+ * import { assertEqual, assertRejectsInstanceOf } from "@evolu/common";
619
+ *
620
+ * const error = await assertRejectsInstanceOf(
621
+ * Promise.reject(new TypeError("Unavailable.")),
622
+ * TypeError,
623
+ * );
624
+ * assertEqual(error.message, "Unavailable.");
271
625
  * ```
626
+ *
627
+ * @group Assertions
272
628
  */
273
- export function assertErr<R extends Result<unknown, unknown>>(
274
- result: R,
275
- ): asserts result is Extract<R, Err<unknown>>;
276
- export function assertErr<R extends Result<unknown, unknown>>(
277
- result: R,
278
- expectedError: InferErr<R>,
279
- eq: Eq<InferErr<R>>,
280
- ): asserts result is Extract<R, Err<unknown>>;
281
- // Constrain the argument tuple so any cannot absorb the CompileTimeError.
282
- export function assertErr<R extends Result<unknown, unknown>, ExpectedError>(
283
- result: R,
284
- ...comparison: [expectedError: ExpectedError] &
285
- (IsData<InferErr<R> | ExpectedError> extends true
286
- ? unknown
287
- : {
288
- readonly [
289
- Error in CompileTimeError<
290
- "assertErr",
291
- "Result error and expected error must consist only of Data when no custom Eq is provided."
292
- >
293
- ]: never;
294
- })
295
- ): asserts result is Extract<R, Err<unknown>>;
296
- export function assertErr(
297
- result: AnyResult,
298
- ...comparison: [] | [expectedError: unknown, eq?: Eq<any>]
299
- ): asserts result is Err<unknown> {
300
- assert(!result.ok, "Expected an Err result.");
301
- if (comparison.length === 0) return;
629
+ export const assertRejectsInstanceOf = async <
630
+ T extends {
631
+ readonly name: string;
632
+ } & (abstract new (...args: Array<never>) => unknown),
633
+ >(
634
+ promise: PromiseLike<unknown>,
635
+ constructor: T,
636
+ ): Promise<InstanceType<T>> => {
637
+ const rejected = await captureRejected(promise, assertRejectsInstanceOf);
638
+ assert(
639
+ rejected instanceof constructor,
640
+ constructor.name === ""
641
+ ? "Expected the rejection reason to be an instance of the provided constructor."
642
+ : `Expected the rejection reason to be an instance of ${constructor.name}.`,
643
+ {
644
+ actual: rejected,
645
+ expected: constructor,
646
+ operator: "instanceof",
647
+ stackStartFn: assertRejectsInstanceOf,
648
+ },
649
+ );
650
+ return rejected as InstanceType<T>;
651
+ };
302
652
 
303
- const expectedError = comparison[0];
304
- const eq: Eq<any> = comparison[1] ?? eqData;
653
+ const captureRejected = async (
654
+ promise: PromiseLike<unknown>,
655
+ stackStartFn: (...args: Array<never>) => unknown,
656
+ ): Promise<unknown> => {
657
+ let value: unknown;
658
+
659
+ try {
660
+ value = await promise;
661
+ } catch (error) {
662
+ return error;
663
+ }
664
+
665
+ assert(false, "Expected promise to reject.", {
666
+ actual: value,
667
+ expected: "rejection",
668
+ operator: "rejects",
669
+ stackStartFn,
670
+ });
671
+ };
672
+
673
+ /**
674
+ * Asserts that a value is an instance of a constructor and narrows it.
675
+ *
676
+ * ### Example
677
+ *
678
+ * ```ts
679
+ * import { assertInstanceOf, assertType } from "@evolu/common";
680
+ *
681
+ * const value: unknown = new TypeError("Invalid value.");
682
+ * assertInstanceOf(value, TypeError);
683
+ * assertType<typeof value, TypeError>();
684
+ * ```
685
+ *
686
+ * @group Assertions
687
+ */
688
+ export const assertInstanceOf: <
689
+ Constructor extends {
690
+ readonly name: string;
691
+ } & (abstract new (...args: Array<never>) => unknown),
692
+ >(
693
+ value: unknown,
694
+ constructor: Constructor,
695
+ ) => asserts value is InstanceType<Constructor> = (value, constructor) => {
305
696
  assert(
306
- eq(result.error, expectedError),
307
- "Expected the error to equal the expected error.",
697
+ value instanceof constructor,
698
+ constructor.name === ""
699
+ ? "Expected an instance of the provided constructor."
700
+ : `Expected an instance of ${constructor.name}.`,
701
+ {
702
+ actual: value,
703
+ expected: constructor,
704
+ operator: "instanceof",
705
+ stackStartFn: assertInstanceOf,
706
+ },
308
707
  );
309
- }
708
+ };
310
709
 
311
710
  /**
312
711
  * Asserts that a value is non-nullable.
313
712
  *
314
713
  * Following TypeScript's {@link NonNullable}, non-nullable here means neither
315
- * null nor undefined. Use this when a value is logically guaranteed to be
316
- * non-nullable but TypeScript cannot prove it.
714
+ * null nor undefined.
715
+ *
716
+ * @group Assertions
317
717
  */
318
718
  export const assertNonNullable: <T>(
319
719
  value: T,
@@ -322,22 +722,24 @@ export const assertNonNullable: <T>(
322
722
  value,
323
723
  message = "Expected value to be non-nullable.",
324
724
  ) => {
325
- assert(value != null, message);
725
+ assert(value != null, message, {
726
+ actual: value,
727
+ expected: null,
728
+ operator: "!=",
729
+ stackStartFn: assertNonNullable,
730
+ });
326
731
  };
327
732
 
328
733
  /**
329
734
  * Asserts that a value is not null while preserving undefined.
330
735
  *
331
- * Use this when a value is logically guaranteed not to be null but TypeScript
332
- * cannot prove it.
333
- *
334
736
  * ### Example
335
737
  *
336
738
  * ```ts
337
739
  * import {
338
- * assert,
339
740
  * assertEqual,
340
741
  * assertErr,
742
+ * assertInstanceOf,
341
743
  * assertNotNull,
342
744
  * assertType,
343
745
  * trySync,
@@ -345,13 +747,17 @@ export const assertNonNullable: <T>(
345
747
  *
346
748
  * const value = undefined as string | null | undefined;
347
749
  * assertNotNull(value);
348
- * assertType<string | undefined, typeof value>();
750
+ *
751
+ * assertType<typeof value, string | undefined>();
349
752
  * assertEqual(value, undefined);
753
+ *
350
754
  * const result = trySync(() => assertNotNull(null));
351
755
  * assertErr(result);
352
- * assert(result.error instanceof Error, "Expected an Error.");
756
+ * assertInstanceOf(result.error, Error);
353
757
  * assertEqual(result.error.message, "Expected value not to be null.");
354
758
  * ```
759
+ *
760
+ * @group Assertions
355
761
  */
356
762
  export const assertNotNull: <T>(
357
763
  value: T,
@@ -360,22 +766,24 @@ export const assertNotNull: <T>(
360
766
  value,
361
767
  message = "Expected value not to be null.",
362
768
  ) => {
363
- assert(value !== null, message);
769
+ assert(value !== null, message, {
770
+ actual: value,
771
+ expected: null,
772
+ operator: "notStrictEqual",
773
+ stackStartFn: assertNotNull,
774
+ });
364
775
  };
365
776
 
366
777
  /**
367
778
  * Asserts that a value is not undefined while preserving null.
368
779
  *
369
- * Use this when a value is logically guaranteed not to be undefined but
370
- * TypeScript cannot prove it.
371
- *
372
780
  * ### Example
373
781
  *
374
782
  * ```ts
375
783
  * import {
376
- * assert,
377
784
  * assertEqual,
378
785
  * assertErr,
786
+ * assertInstanceOf,
379
787
  * assertNotUndefined,
380
788
  * assertType,
381
789
  * trySync,
@@ -383,13 +791,15 @@ export const assertNotNull: <T>(
383
791
  *
384
792
  * const value = null as string | null | undefined;
385
793
  * assertNotUndefined(value);
386
- * assertType<string | null, typeof value>();
794
+ * assertType<typeof value, string | null>();
387
795
  * assertEqual(value, null);
388
796
  * const result = trySync(() => assertNotUndefined(undefined));
389
797
  * assertErr(result);
390
- * assert(result.error instanceof Error, "Expected an Error.");
798
+ * assertInstanceOf(result.error, Error);
391
799
  * assertEqual(result.error.message, "Expected value not to be undefined.");
392
800
  * ```
801
+ *
802
+ * @group Assertions
393
803
  */
394
804
  export const assertNotUndefined: <T>(
395
805
  value: T,
@@ -398,14 +808,53 @@ export const assertNotUndefined: <T>(
398
808
  value,
399
809
  message = "Expected value not to be undefined.",
400
810
  ) => {
401
- assert(value !== undefined, message);
811
+ assert(value !== undefined, message, {
812
+ actual: value,
813
+ expected: undefined,
814
+ operator: "notStrictEqual",
815
+ stackStartFn: assertNotUndefined,
816
+ });
402
817
  };
403
818
 
404
819
  /**
405
- * Asserts that an array is non-empty.
820
+ * Asserts that a value has the expected length and narrows its length.
821
+ *
822
+ * ### Example
406
823
  *
407
- * Use this when an array is logically guaranteed to be non-empty but TypeScript
408
- * cannot prove it.
824
+ * ```ts
825
+ * import { assertLength, assertType } from "@evolu/common";
826
+ *
827
+ * const values: ReadonlyArray<string> = ["Ada", "Grace"];
828
+ * assertLength(values, 2);
829
+ * assertType<typeof values.length, 2>();
830
+ * ```
831
+ *
832
+ * @group Assertions
833
+ */
834
+ export const assertLength: <
835
+ Value extends ValueWithLength,
836
+ const Length extends number,
837
+ >(
838
+ value: Value,
839
+ expectedLength: Length,
840
+ ) => asserts value is Value & { readonly length: Length } = (
841
+ value,
842
+ expectedLength,
843
+ ) => {
844
+ assert(
845
+ eqSameValue(value.length, expectedLength),
846
+ `Expected value to have length ${expectedLength}.`,
847
+ {
848
+ actual: value.length,
849
+ expected: expectedLength,
850
+ operator: "strictEqual",
851
+ stackStartFn: assertLength,
852
+ },
853
+ );
854
+ };
855
+
856
+ /**
857
+ * Asserts that an array is non-empty.
409
858
  *
410
859
  * ### Example
411
860
  *
@@ -421,11 +870,13 @@ export const assertNotUndefined: <T>(
421
870
  *
422
871
  * const values = [1, 2, 3];
423
872
  * assertNonEmptyArray(values);
424
- * assertType<NonEmptyArray<number>, typeof values>();
873
+ * assertType<typeof values, NonEmptyArray<number>>();
425
874
  * assertEqual(values[0], 1);
426
875
  * const result = trySync(() => assertNonEmptyArray([]));
427
876
  * assertErr(result);
428
877
  * ```
878
+ *
879
+ * @group Assertions
429
880
  */
430
881
  export const assertNonEmptyArray: <T>(
431
882
  arr: Array<T>,
@@ -434,15 +885,17 @@ export const assertNonEmptyArray: <T>(
434
885
  arr,
435
886
  message = "Expected a non-empty array.",
436
887
  ) => {
437
- assert(arr.length > 0, message);
888
+ assert(arr.length > 0, message, {
889
+ actual: arr.length,
890
+ expected: 0,
891
+ operator: ">",
892
+ stackStartFn: assertNonEmptyArray,
893
+ });
438
894
  };
439
895
 
440
896
  /**
441
897
  * Asserts that a readonly array is non-empty.
442
898
  *
443
- * Use this when a readonly array is logically guaranteed to be non-empty but
444
- * TypeScript cannot prove it.
445
- *
446
899
  * ### Example
447
900
  *
448
901
  * ```ts
@@ -457,11 +910,13 @@ export const assertNonEmptyArray: <T>(
457
910
  *
458
911
  * const values: ReadonlyArray<number> = [1, 2, 3];
459
912
  * assertNonEmptyReadonlyArray(values);
460
- * assertType<NonEmptyReadonlyArray<number>, typeof values>();
913
+ * assertType<typeof values, NonEmptyReadonlyArray<number>>();
461
914
  * assertEqual(values[0], 1);
462
915
  * const result = trySync(() => assertNonEmptyReadonlyArray([]));
463
916
  * assertErr(result);
464
917
  * ```
918
+ *
919
+ * @group Assertions
465
920
  */
466
921
  export const assertNonEmptyReadonlyArray: <T>(
467
922
  arr: ReadonlyArray<T>,
@@ -470,7 +925,12 @@ export const assertNonEmptyReadonlyArray: <T>(
470
925
  arr,
471
926
  message = "Expected a non-empty readonly array.",
472
927
  ) => {
473
- assert(arr.length > 0, message);
928
+ assert(arr.length > 0, message, {
929
+ actual: arr.length,
930
+ expected: 0,
931
+ operator: ">",
932
+ stackStartFn: assertNonEmptyReadonlyArray,
933
+ });
474
934
  };
475
935
 
476
936
  /**
@@ -482,30 +942,366 @@ export const assertNonEmptyReadonlyArray: <T>(
482
942
  *
483
943
  * ```ts
484
944
  * import {
485
- * assert,
486
945
  * assertEqual,
487
946
  * assertErr,
947
+ * assertInstanceOf,
488
948
  * assertNotDisposed,
489
949
  * trySync,
490
950
  * } from "@evolu/common";
491
951
  *
492
- * await using disposer = new globalThis.AsyncDisposableStack();
952
+ * await using disposer = new AsyncDisposableStack();
493
953
  * assertNotDisposed(disposer);
494
954
  * await disposer.disposeAsync();
495
955
  * const result = trySync(() => assertNotDisposed(disposer));
496
956
  * assertErr(result);
497
- * assert(result.error instanceof Error, "Expected an Error.");
957
+ * assertInstanceOf(result.error, Error);
498
958
  * assertEqual(result.error.message, "Cannot use a disposed object.");
499
959
  * ```
500
960
  *
501
961
  * This is the JavaScript equivalent of the .NET `ObjectDisposedException`
502
962
  * pattern: once a helper has been disposed, calling its synchronous methods is
503
- * a programmer error and should throw immediately instead of continuing with
504
- * invalid state.
963
+ * a bug and should throw immediately instead of continuing with invalid state.
964
+ *
965
+ * @group Assertions
505
966
  */
506
967
  export const assertNotDisposed = (
507
968
  value:
508
969
  DisposableStack | AsyncDisposableStack | { readonly disposed: boolean },
509
970
  ): void => {
510
- assert(!value.disposed, "Cannot use a disposed object.");
971
+ assert(!value.disposed, "Cannot use a disposed object.", {
972
+ actual: value.disposed,
973
+ expected: false,
974
+ operator: "==",
975
+ stackStartFn: assertNotDisposed,
976
+ });
977
+ };
978
+
979
+ /**
980
+ * Asserts that two values are the same using `Object.is`.
981
+ *
982
+ * Use this when exact sameness is the contract, such as asserting reference
983
+ * identity or narrowing the actual value to the expected value's type, as shown
984
+ * below. For value comparisons, use {@link assertEqual}.
985
+ *
986
+ * Uses the same equality semantics as `assert.strictEqual` from
987
+ * `node:assert/strict`, but is platform-agnostic. See {@link eqSameValue} for
988
+ * the equality semantics.
989
+ *
990
+ * ### Example
991
+ *
992
+ * ```ts
993
+ * import { assertSame, assertType } from "@evolu/common";
994
+ *
995
+ * interface User {
996
+ * readonly name: string;
997
+ * }
998
+ *
999
+ * const user: User = { name: "Ada" };
1000
+ * const value: unknown = user;
1001
+ *
1002
+ * assertSame(value, user);
1003
+ * // `assertSame` narrows `value` from `unknown` to `User`.
1004
+ * assertType<typeof value, User>();
1005
+ * ```
1006
+ *
1007
+ * @group Assertions
1008
+ */
1009
+ export const assertSame: <Expected>(
1010
+ actual: unknown,
1011
+ expected: Expected,
1012
+ ) => asserts actual is Expected = (actual, expected) => {
1013
+ // `Object.is` implements ECMAScript SameValue, whereas strict equality means
1014
+ // `===`. Keep the platform-agnostic "same" wording instead of reproducing
1015
+ // Node.js's value-dependent messages, which sometimes say "strictly equal".
1016
+ assert(eqSameValue(actual, expected), "Expected values to be the same.", {
1017
+ actual,
1018
+ expected,
1019
+ operator: "strictEqual",
1020
+ stackStartFn: assertSame,
1021
+ });
1022
+ };
1023
+
1024
+ /**
1025
+ * Asserts that two values are not the same using `Object.is`.
1026
+ *
1027
+ * The opposite of {@link assertSame}.
1028
+ *
1029
+ * ### Example
1030
+ *
1031
+ * ```ts
1032
+ * import { assertNotSame } from "@evolu/common";
1033
+ *
1034
+ * const first = { name: "Ada" };
1035
+ * const second = { name: "Ada" };
1036
+ *
1037
+ * assertNotSame(first, second);
1038
+ * assertNotSame(0, -0);
1039
+ * ```
1040
+ *
1041
+ * @group Assertions
1042
+ */
1043
+ export const assertNotSame = (actual: unknown, expected: unknown): void => {
1044
+ // `Object.is` implements ECMAScript SameValue, whereas strict equality means
1045
+ // `===`. Keep the platform-agnostic "same" wording instead of reproducing
1046
+ // Node.js's value-dependent messages, which sometimes say "strictly equal".
1047
+ assert(
1048
+ !eqSameValue(actual, expected),
1049
+ "Expected values not to be the same.",
1050
+ {
1051
+ actual,
1052
+ expected,
1053
+ operator: "notStrictEqual",
1054
+ stackStartFn: assertNotSame,
1055
+ },
1056
+ );
1057
+ };
1058
+
1059
+ // oxlint-disable eslint/func-style -- Function declarations make TypeDoc categorize these APIs as functions.
1060
+
1061
+ /**
1062
+ * Asserts that two values are equal.
1063
+ *
1064
+ * Use this for value comparisons of both primitive and deeply structured
1065
+ * values. Use {@link assertSame} when exact sameness or narrowing is required.
1066
+ *
1067
+ * Like `assert.deepEqual` from `node:assert/strict`, it performs deep
1068
+ * structural comparison, but uses deliberately smaller, platform-agnostic
1069
+ * equality logic based on {@link eqData}. More complex comparison logic is
1070
+ * difficult to reason about and rarely needed. Other values are opaque and
1071
+ * compare equal only by identity. If broader comparison semantics are needed in
1072
+ * Node.js, use the native assertion instead.
1073
+ *
1074
+ * ### Example
1075
+ *
1076
+ * ```ts
1077
+ * import { assertEqual } from "@evolu/common";
1078
+ *
1079
+ * assertEqual(21 * 2, 42);
1080
+ *
1081
+ * const actual: unknown = new Map([
1082
+ * ["roles", new Set(["admin", "author"])],
1083
+ * ]);
1084
+ * const expected = new Map([["roles", new Set(["author", "admin"])]]);
1085
+ *
1086
+ * assertEqual(actual, expected);
1087
+ * ```
1088
+ *
1089
+ * @group Assertions
1090
+ */
1091
+ export function assertEqual(actual: unknown, expected: unknown): void {
1092
+ assert(eqUnknown(actual, expected), "Expected values to be equal.", {
1093
+ actual,
1094
+ expected,
1095
+ operator: "eqData",
1096
+ diff: "full",
1097
+ stackStartFn: assertEqual,
1098
+ });
1099
+ }
1100
+
1101
+ /**
1102
+ * Asserts that a `Uint8Array` contains the expected bytes.
1103
+ *
1104
+ * Uses {@link eqArrayNumber}, so the runtime representations may differ. For
1105
+ * example, a `Uint8Array` can be compared directly with a regular array.
1106
+ *
1107
+ * ### Example
1108
+ *
1109
+ * ```ts
1110
+ * import { assertEqualBytes } from "@evolu/common";
1111
+ *
1112
+ * const bytes = new Uint8Array([1, 5, 39, 254]);
1113
+ *
1114
+ * assertEqualBytes(bytes, [1, 5, 39, 254]);
1115
+ * ```
1116
+ *
1117
+ * @group Assertions
1118
+ */
1119
+ export const assertEqualBytes = (
1120
+ actual: Uint8Array,
1121
+ expected: ArrayLike<number>,
1122
+ ): void => {
1123
+ assert(eqArrayNumber(actual, expected), "Expected bytes to be equal.", {
1124
+ actual,
1125
+ expected,
1126
+ operator: "eqArrayNumber",
1127
+ diff: "full",
1128
+ stackStartFn: assertEqualBytes,
1129
+ });
511
1130
  };
1131
+
1132
+ /**
1133
+ * Asserts that two values are not equal.
1134
+ *
1135
+ * The opposite of {@link assertEqual}.
1136
+ *
1137
+ * ### Example
1138
+ *
1139
+ * ```ts
1140
+ * import { assertNotEqual } from "@evolu/common";
1141
+ *
1142
+ * assertNotEqual({ name: "Ada" }, { name: "Grace" });
1143
+ * assertNotEqual([1, 2], [2, 1]);
1144
+ * ```
1145
+ *
1146
+ * @group Assertions
1147
+ */
1148
+ export function assertNotEqual(actual: unknown, expected: unknown): void {
1149
+ assert(!eqUnknown(actual, expected), "Expected values not to be equal.", {
1150
+ actual,
1151
+ expected,
1152
+ operator: "notEqData",
1153
+ stackStartFn: assertNotEqual,
1154
+ });
1155
+ }
1156
+
1157
+ // oxlint-enable eslint/func-style
1158
+
1159
+ // Assertion equality extends eqData to unknown values. The comparator's
1160
+ // SameValue fast path treats identical opaque values as equal, while its
1161
+ // unsupported branches make distinct opaque values unequal.
1162
+ const eqUnknown = eqData as Eq<unknown>;
1163
+
1164
+ /**
1165
+ * Asserts that a {@link Result} is an {@link Ok} and narrows it, optionally
1166
+ * comparing its value.
1167
+ *
1168
+ * When an expected value is provided, it is compared using {@link assertEqual}
1169
+ * semantics by default. Pass a custom {@link Eq} for different equality
1170
+ * semantics.
1171
+ *
1172
+ * ### Example
1173
+ *
1174
+ * ```ts
1175
+ * import {
1176
+ * assertOk,
1177
+ * assertType,
1178
+ * ok,
1179
+ * type Ok,
1180
+ * type Result,
1181
+ * type Typed,
1182
+ * } from "@evolu/common";
1183
+ *
1184
+ * interface User {
1185
+ * readonly id: string;
1186
+ * }
1187
+ *
1188
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {}
1189
+ *
1190
+ * const result: Result<User, UserNotFoundError> = ok({ id: "user-1" });
1191
+ *
1192
+ * assertOk(result, { id: "user-1" });
1193
+ * assertType<typeof result, Ok<User>>();
1194
+ * ```
1195
+ *
1196
+ * @group Assertions
1197
+ */
1198
+ export function assertOk<R extends Result<unknown, unknown>>(
1199
+ result: R,
1200
+ ): asserts result is Extract<R, Ok<unknown>>;
1201
+ export function assertOk<R extends Result<unknown, unknown>>(
1202
+ result: R,
1203
+ expectedValue: InferOk<R>,
1204
+ eq: Eq<InferOk<R>>,
1205
+ ): asserts result is Extract<R, Ok<unknown>>;
1206
+ export function assertOk<R extends Result<unknown, unknown>>(
1207
+ result: R,
1208
+ expectedValue: unknown,
1209
+ ): asserts result is Extract<R, Ok<unknown>>;
1210
+ export function assertOk(
1211
+ result: AnyResult,
1212
+ ...comparison: [] | [expectedValue: unknown, eq?: Eq<any>]
1213
+ ): asserts result is Ok<unknown> {
1214
+ assert(result.ok, "Expected an Ok result.", {
1215
+ actual: result.ok,
1216
+ expected: true,
1217
+ operator: "strictEqual",
1218
+ stackStartFn: assertOk,
1219
+ });
1220
+ if (comparison.length === 0) return;
1221
+
1222
+ const expectedValue = comparison[0];
1223
+ assert(
1224
+ (comparison[1] ?? eqUnknown)(result.value, expectedValue),
1225
+ "Expected the value to equal the expected value.",
1226
+ {
1227
+ actual: result.value,
1228
+ expected: expectedValue,
1229
+ operator: comparison[1] === undefined ? "eqData" : "Eq",
1230
+ diff: comparison[1] === undefined ? "full" : undefined,
1231
+ stackStartFn: assertOk,
1232
+ },
1233
+ );
1234
+ }
1235
+
1236
+ /**
1237
+ * Asserts that a {@link Result} is an {@link Err} and narrows it, optionally
1238
+ * comparing its error.
1239
+ *
1240
+ * When an expected error is provided, it is compared using {@link assertEqual}
1241
+ * semantics by default. Pass a custom {@link Eq} for different equality
1242
+ * semantics.
1243
+ *
1244
+ * ### Example
1245
+ *
1246
+ * ```ts
1247
+ * import {
1248
+ * assertErr,
1249
+ * assertType,
1250
+ * err,
1251
+ * type Err,
1252
+ * type Result,
1253
+ * type Typed,
1254
+ * } from "@evolu/common";
1255
+ *
1256
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {
1257
+ * readonly id: string;
1258
+ * }
1259
+ *
1260
+ * const result: Result<string, UserNotFoundError> = err({
1261
+ * type: "UserNotFound",
1262
+ * id: "user-1",
1263
+ * });
1264
+ *
1265
+ * assertErr(result, { type: "UserNotFound", id: "user-1" });
1266
+ * assertType<typeof result, Err<UserNotFoundError>>();
1267
+ * ```
1268
+ *
1269
+ * @group Assertions
1270
+ */
1271
+ export function assertErr<R extends Result<unknown, unknown>>(
1272
+ result: R,
1273
+ ): asserts result is Extract<R, Err<unknown>>;
1274
+ export function assertErr<R extends Result<unknown, unknown>>(
1275
+ result: R,
1276
+ expectedError: InferErr<R>,
1277
+ eq: Eq<InferErr<R>>,
1278
+ ): asserts result is Extract<R, Err<unknown>>;
1279
+ export function assertErr<R extends Result<unknown, unknown>>(
1280
+ result: R,
1281
+ expectedError: unknown,
1282
+ ): asserts result is Extract<R, Err<unknown>>;
1283
+ export function assertErr(
1284
+ result: AnyResult,
1285
+ ...comparison: [] | [expectedError: unknown, eq?: Eq<any>]
1286
+ ): asserts result is Err<unknown> {
1287
+ assert(!result.ok, "Expected an Err result.", {
1288
+ actual: result.ok,
1289
+ expected: false,
1290
+ operator: "strictEqual",
1291
+ stackStartFn: assertErr,
1292
+ });
1293
+ if (comparison.length === 0) return;
1294
+
1295
+ const expectedError = comparison[0];
1296
+ assert(
1297
+ (comparison[1] ?? eqUnknown)(result.error, expectedError),
1298
+ "Expected the error to equal the expected error.",
1299
+ {
1300
+ actual: result.error,
1301
+ expected: expectedError,
1302
+ operator: comparison[1] === undefined ? "eqData" : "Eq",
1303
+ diff: comparison[1] === undefined ? "full" : undefined,
1304
+ stackStartFn: assertErr,
1305
+ },
1306
+ );
1307
+ }