@evolu/common 8.4.0 → 8.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/dist/src/Array.d.ts +191 -94
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +91 -40
  4. package/dist/src/Assert.d.ts +233 -30
  5. package/dist/src/Assert.d.ts.map +1 -1
  6. package/dist/src/Assert.js +151 -30
  7. package/dist/src/BigInt.d.ts +3 -3
  8. package/dist/src/BigInt.js +3 -3
  9. package/dist/src/Brand.d.ts +7 -7
  10. package/dist/src/Buffer.d.ts +12 -4
  11. package/dist/src/Buffer.d.ts.map +1 -1
  12. package/dist/src/Cache.d.ts +8 -3
  13. package/dist/src/Cache.d.ts.map +1 -1
  14. package/dist/src/Cache.js +8 -3
  15. package/dist/src/Callbacks.d.ts +8 -4
  16. package/dist/src/Callbacks.d.ts.map +1 -1
  17. package/dist/src/Console.d.ts +45 -30
  18. package/dist/src/Console.d.ts.map +1 -1
  19. package/dist/src/Console.js +32 -21
  20. package/dist/src/Crypto.d.ts +13 -8
  21. package/dist/src/Crypto.d.ts.map +1 -1
  22. package/dist/src/Crypto.js +6 -3
  23. package/dist/src/Eq.d.ts +107 -58
  24. package/dist/src/Eq.d.ts.map +1 -1
  25. package/dist/src/Eq.js +308 -112
  26. package/dist/src/Function.d.ts +54 -24
  27. package/dist/src/Function.d.ts.map +1 -1
  28. package/dist/src/Function.js +34 -17
  29. package/dist/src/Http.d.ts +52 -19
  30. package/dist/src/Http.d.ts.map +1 -1
  31. package/dist/src/Identicon.d.ts +9 -4
  32. package/dist/src/Identicon.d.ts.map +1 -1
  33. package/dist/src/Identicon.js +9 -4
  34. package/dist/src/LeakDetector.d.ts.map +1 -1
  35. package/dist/src/LeakDetector.js +4 -1
  36. package/dist/src/LockManager.d.ts +12 -10
  37. package/dist/src/LockManager.d.ts.map +1 -1
  38. package/dist/src/Lookup.d.ts +4 -2
  39. package/dist/src/Lookup.d.ts.map +1 -1
  40. package/dist/src/Lookup.js +5 -2
  41. package/dist/src/Number.d.ts +28 -24
  42. package/dist/src/Number.d.ts.map +1 -1
  43. package/dist/src/Number.js +16 -17
  44. package/dist/src/Object.d.ts +89 -47
  45. package/dist/src/Object.d.ts.map +1 -1
  46. package/dist/src/Object.js +88 -41
  47. package/dist/src/Option.d.ts +14 -5
  48. package/dist/src/Option.d.ts.map +1 -1
  49. package/dist/src/Option.js +14 -5
  50. package/dist/src/Order.d.ts +12 -12
  51. package/dist/src/Order.js +12 -12
  52. package/dist/src/Platform.d.ts +2 -2
  53. package/dist/src/Platform.js +1 -0
  54. package/dist/src/Random.d.ts +7 -4
  55. package/dist/src/Random.d.ts.map +1 -1
  56. package/dist/src/Redacted.d.ts +13 -6
  57. package/dist/src/Redacted.d.ts.map +1 -1
  58. package/dist/src/Redacted.js +4 -2
  59. package/dist/src/Ref.d.ts +4 -4
  60. package/dist/src/Relation.d.ts +5 -7
  61. package/dist/src/Relation.d.ts.map +1 -1
  62. package/dist/src/Relation.js +3 -3
  63. package/dist/src/Resource.d.ts +27 -12
  64. package/dist/src/Resource.d.ts.map +1 -1
  65. package/dist/src/Resource.js +9 -3
  66. package/dist/src/Result.d.ts +258 -114
  67. package/dist/src/Result.d.ts.map +1 -1
  68. package/dist/src/Result.js +74 -39
  69. package/dist/src/Schedule.d.ts +233 -116
  70. package/dist/src/Schedule.d.ts.map +1 -1
  71. package/dist/src/Schedule.js +204 -110
  72. package/dist/src/Set.d.ts +43 -24
  73. package/dist/src/Set.d.ts.map +1 -1
  74. package/dist/src/Set.js +25 -13
  75. package/dist/src/Sqlite.d.ts +4 -4
  76. package/dist/src/Sqlite.d.ts.map +1 -1
  77. package/dist/src/Sqlite.js +18 -15
  78. package/dist/src/Store.d.ts.map +1 -1
  79. package/dist/src/Store.js +3 -1
  80. package/dist/src/String.d.ts.map +1 -1
  81. package/dist/src/String.js +1 -1
  82. package/dist/src/Task.d.ts +555 -264
  83. package/dist/src/Task.d.ts.map +1 -1
  84. package/dist/src/Task.js +278 -168
  85. package/dist/src/Test.d.ts +10 -4
  86. package/dist/src/Test.d.ts.map +1 -1
  87. package/dist/src/Test.js +11 -4
  88. package/dist/src/Time.d.ts +27 -17
  89. package/dist/src/Time.d.ts.map +1 -1
  90. package/dist/src/Time.js +25 -8
  91. package/dist/src/Type.d.ts +1218 -372
  92. package/dist/src/Type.d.ts.map +1 -1
  93. package/dist/src/Type.js +1056 -379
  94. package/dist/src/Types.d.ts +95 -40
  95. package/dist/src/Types.d.ts.map +1 -1
  96. package/dist/src/Types.js +13 -4
  97. package/dist/src/WebSocket.d.ts +14 -6
  98. package/dist/src/WebSocket.d.ts.map +1 -1
  99. package/dist/src/WebSocket.js +8 -0
  100. package/dist/src/Worker.d.ts +10 -6
  101. package/dist/src/Worker.d.ts.map +1 -1
  102. package/dist/src/Worker.js +1 -1
  103. package/dist/src/intl/_en.d.ts +3 -1
  104. package/dist/src/intl/_en.d.ts.map +1 -1
  105. package/dist/src/intl/_en.js +17 -3
  106. package/dist/src/intl/ar.d.ts +3 -1
  107. package/dist/src/intl/ar.d.ts.map +1 -1
  108. package/dist/src/intl/ar.js +15 -2
  109. package/dist/src/intl/bn.d.ts +3 -1
  110. package/dist/src/intl/bn.d.ts.map +1 -1
  111. package/dist/src/intl/bn.js +16 -3
  112. package/dist/src/intl/ca.d.ts +3 -1
  113. package/dist/src/intl/ca.d.ts.map +1 -1
  114. package/dist/src/intl/ca.js +16 -3
  115. package/dist/src/intl/cs.d.ts +3 -1
  116. package/dist/src/intl/cs.d.ts.map +1 -1
  117. package/dist/src/intl/cs.js +16 -3
  118. package/dist/src/intl/da.d.ts +3 -1
  119. package/dist/src/intl/da.d.ts.map +1 -1
  120. package/dist/src/intl/da.js +16 -3
  121. package/dist/src/intl/de.d.ts +3 -1
  122. package/dist/src/intl/de.d.ts.map +1 -1
  123. package/dist/src/intl/de.js +16 -3
  124. package/dist/src/intl/el.d.ts +3 -1
  125. package/dist/src/intl/el.d.ts.map +1 -1
  126. package/dist/src/intl/el.js +16 -3
  127. package/dist/src/intl/es.d.ts +3 -1
  128. package/dist/src/intl/es.d.ts.map +1 -1
  129. package/dist/src/intl/es.js +16 -3
  130. package/dist/src/intl/fa.d.ts +3 -1
  131. package/dist/src/intl/fa.d.ts.map +1 -1
  132. package/dist/src/intl/fa.js +16 -3
  133. package/dist/src/intl/fi.d.ts +3 -1
  134. package/dist/src/intl/fi.d.ts.map +1 -1
  135. package/dist/src/intl/fi.js +16 -3
  136. package/dist/src/intl/fil.d.ts +3 -1
  137. package/dist/src/intl/fil.d.ts.map +1 -1
  138. package/dist/src/intl/fil.js +16 -3
  139. package/dist/src/intl/fr.d.ts +3 -1
  140. package/dist/src/intl/fr.d.ts.map +1 -1
  141. package/dist/src/intl/fr.js +16 -3
  142. package/dist/src/intl/he.d.ts +3 -1
  143. package/dist/src/intl/he.d.ts.map +1 -1
  144. package/dist/src/intl/he.js +16 -3
  145. package/dist/src/intl/hi.d.ts +3 -1
  146. package/dist/src/intl/hi.d.ts.map +1 -1
  147. package/dist/src/intl/hi.js +16 -3
  148. package/dist/src/intl/hr.d.ts +3 -1
  149. package/dist/src/intl/hr.d.ts.map +1 -1
  150. package/dist/src/intl/hr.js +16 -3
  151. package/dist/src/intl/hu.d.ts +2 -1
  152. package/dist/src/intl/hu.d.ts.map +1 -1
  153. package/dist/src/intl/hu.js +15 -3
  154. package/dist/src/intl/id.d.ts +3 -1
  155. package/dist/src/intl/id.d.ts.map +1 -1
  156. package/dist/src/intl/id.js +16 -3
  157. package/dist/src/intl/it.d.ts +3 -1
  158. package/dist/src/intl/it.d.ts.map +1 -1
  159. package/dist/src/intl/it.js +16 -3
  160. package/dist/src/intl/ja.d.ts +3 -1
  161. package/dist/src/intl/ja.d.ts.map +1 -1
  162. package/dist/src/intl/ja.js +16 -3
  163. package/dist/src/intl/ko.d.ts +3 -1
  164. package/dist/src/intl/ko.d.ts.map +1 -1
  165. package/dist/src/intl/ko.js +16 -3
  166. package/dist/src/intl/ml.d.ts +3 -1
  167. package/dist/src/intl/ml.d.ts.map +1 -1
  168. package/dist/src/intl/ml.js +16 -3
  169. package/dist/src/intl/mr.d.ts +3 -1
  170. package/dist/src/intl/mr.d.ts.map +1 -1
  171. package/dist/src/intl/mr.js +16 -3
  172. package/dist/src/intl/ms.d.ts +3 -1
  173. package/dist/src/intl/ms.d.ts.map +1 -1
  174. package/dist/src/intl/ms.js +15 -2
  175. package/dist/src/intl/nb.d.ts +2 -1
  176. package/dist/src/intl/nb.d.ts.map +1 -1
  177. package/dist/src/intl/nb.js +14 -2
  178. package/dist/src/intl/nl.d.ts +3 -1
  179. package/dist/src/intl/nl.d.ts.map +1 -1
  180. package/dist/src/intl/nl.js +16 -3
  181. package/dist/src/intl/pa.d.ts +3 -1
  182. package/dist/src/intl/pa.d.ts.map +1 -1
  183. package/dist/src/intl/pa.js +16 -3
  184. package/dist/src/intl/pl.d.ts +2 -0
  185. package/dist/src/intl/pl.d.ts.map +1 -1
  186. package/dist/src/intl/pl.js +15 -2
  187. package/dist/src/intl/pt-BR.d.ts +3 -1
  188. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  189. package/dist/src/intl/pt-BR.js +16 -3
  190. package/dist/src/intl/pt.d.ts +3 -1
  191. package/dist/src/intl/pt.d.ts.map +1 -1
  192. package/dist/src/intl/pt.js +15 -2
  193. package/dist/src/intl/ro.d.ts +3 -1
  194. package/dist/src/intl/ro.d.ts.map +1 -1
  195. package/dist/src/intl/ro.js +16 -3
  196. package/dist/src/intl/sk.d.ts +3 -1
  197. package/dist/src/intl/sk.d.ts.map +1 -1
  198. package/dist/src/intl/sk.js +15 -2
  199. package/dist/src/intl/sl.d.ts +3 -1
  200. package/dist/src/intl/sl.d.ts.map +1 -1
  201. package/dist/src/intl/sl.js +16 -3
  202. package/dist/src/intl/sv.d.ts +3 -1
  203. package/dist/src/intl/sv.d.ts.map +1 -1
  204. package/dist/src/intl/sv.js +16 -3
  205. package/dist/src/intl/sw.d.ts +1 -0
  206. package/dist/src/intl/sw.d.ts.map +1 -1
  207. package/dist/src/intl/sw.js +14 -2
  208. package/dist/src/intl/ta.d.ts +3 -1
  209. package/dist/src/intl/ta.d.ts.map +1 -1
  210. package/dist/src/intl/ta.js +16 -3
  211. package/dist/src/intl/te.d.ts +3 -1
  212. package/dist/src/intl/te.d.ts.map +1 -1
  213. package/dist/src/intl/te.js +16 -3
  214. package/dist/src/intl/th.d.ts +3 -1
  215. package/dist/src/intl/th.d.ts.map +1 -1
  216. package/dist/src/intl/th.js +16 -3
  217. package/dist/src/intl/tr.d.ts +3 -1
  218. package/dist/src/intl/tr.d.ts.map +1 -1
  219. package/dist/src/intl/tr.js +16 -3
  220. package/dist/src/intl/uk.d.ts +3 -1
  221. package/dist/src/intl/uk.d.ts.map +1 -1
  222. package/dist/src/intl/uk.js +16 -3
  223. package/dist/src/intl/ur.d.ts +3 -1
  224. package/dist/src/intl/ur.d.ts.map +1 -1
  225. package/dist/src/intl/ur.js +15 -2
  226. package/dist/src/intl/vi.d.ts +3 -1
  227. package/dist/src/intl/vi.d.ts.map +1 -1
  228. package/dist/src/intl/vi.js +16 -3
  229. package/dist/src/intl/zh-CN.d.ts +3 -1
  230. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  231. package/dist/src/intl/zh-CN.js +16 -3
  232. package/dist/src/intl/zh-TW.d.ts +3 -1
  233. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  234. package/dist/src/intl/zh-TW.js +16 -3
  235. package/dist/src/local-first/Db.js +3 -2
  236. package/dist/src/local-first/Evolu.d.ts +47 -24
  237. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  238. package/dist/src/local-first/Evolu.js +1 -0
  239. package/dist/src/local-first/Owner.d.ts +18 -14
  240. package/dist/src/local-first/Owner.d.ts.map +1 -1
  241. package/dist/src/local-first/Owner.js +14 -11
  242. package/dist/src/local-first/Protocol.d.ts +5 -5
  243. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  244. package/dist/src/local-first/Protocol.js +35 -29
  245. package/dist/src/local-first/Query.d.ts +34 -21
  246. package/dist/src/local-first/Query.d.ts.map +1 -1
  247. package/dist/src/local-first/Query.js +27 -14
  248. package/dist/src/local-first/Relay.d.ts +12 -5
  249. package/dist/src/local-first/Relay.d.ts.map +1 -1
  250. package/dist/src/local-first/Relay.js +1 -1
  251. package/dist/src/local-first/Schema.d.ts +19 -11
  252. package/dist/src/local-first/Schema.d.ts.map +1 -1
  253. package/dist/src/local-first/Schema.js +11 -6
  254. package/dist/src/local-first/Shared.d.ts.map +1 -1
  255. package/dist/src/local-first/Shared.js +4 -2
  256. package/dist/src/local-first/Storage.d.ts +4 -2
  257. package/dist/src/local-first/Storage.d.ts.map +1 -1
  258. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  259. package/dist/src/local-first/Timestamp.js +1 -1
  260. package/package.json +3 -4
  261. package/src/Array.ts +195 -95
  262. package/src/Assert.ts +337 -31
  263. package/src/BigInt.ts +3 -3
  264. package/src/Brand.ts +7 -7
  265. package/src/Buffer.ts +12 -4
  266. package/src/Cache.ts +8 -3
  267. package/src/Callbacks.ts +8 -4
  268. package/src/Console.ts +45 -30
  269. package/src/Crypto.ts +13 -8
  270. package/src/Eq.ts +450 -122
  271. package/src/Function.ts +54 -24
  272. package/src/Http.ts +52 -19
  273. package/src/Identicon.ts +9 -4
  274. package/src/LeakDetector.ts +4 -3
  275. package/src/LockManager.ts +12 -10
  276. package/src/Lookup.ts +5 -2
  277. package/src/Number.ts +28 -24
  278. package/src/Object.ts +115 -49
  279. package/src/Option.ts +14 -5
  280. package/src/Order.ts +12 -12
  281. package/src/Platform.ts +3 -2
  282. package/src/Random.ts +7 -4
  283. package/src/Redacted.ts +13 -6
  284. package/src/Ref.ts +4 -4
  285. package/src/Relation.ts +8 -10
  286. package/src/Resource.ts +36 -15
  287. package/src/Result.ts +263 -119
  288. package/src/Schedule.ts +233 -116
  289. package/src/Set.ts +43 -24
  290. package/src/Sqlite.ts +18 -15
  291. package/src/Store.ts +3 -1
  292. package/src/String.ts +1 -1
  293. package/src/Task.ts +566 -286
  294. package/src/Test.ts +11 -4
  295. package/src/Time.ts +36 -19
  296. package/src/Type.ts +2191 -590
  297. package/src/Types.ts +108 -40
  298. package/src/WebSocket.ts +22 -6
  299. package/src/Worker.ts +11 -7
  300. package/src/intl/_en.ts +20 -4
  301. package/src/intl/ar.ts +17 -2
  302. package/src/intl/bn.ts +19 -4
  303. package/src/intl/ca.ts +19 -4
  304. package/src/intl/cs.ts +19 -4
  305. package/src/intl/da.ts +19 -4
  306. package/src/intl/de.ts +19 -4
  307. package/src/intl/el.ts +19 -4
  308. package/src/intl/es.ts +19 -4
  309. package/src/intl/fa.ts +19 -4
  310. package/src/intl/fi.ts +19 -4
  311. package/src/intl/fil.ts +19 -4
  312. package/src/intl/fr.ts +19 -4
  313. package/src/intl/he.ts +19 -4
  314. package/src/intl/hi.ts +19 -4
  315. package/src/intl/hr.ts +19 -4
  316. package/src/intl/hu.ts +18 -4
  317. package/src/intl/id.ts +19 -4
  318. package/src/intl/it.ts +19 -4
  319. package/src/intl/ja.ts +19 -4
  320. package/src/intl/ko.ts +19 -4
  321. package/src/intl/ml.ts +19 -4
  322. package/src/intl/mr.ts +19 -4
  323. package/src/intl/ms.ts +17 -2
  324. package/src/intl/nb.ts +16 -2
  325. package/src/intl/nl.ts +19 -4
  326. package/src/intl/pa.ts +19 -4
  327. package/src/intl/pl.ts +18 -2
  328. package/src/intl/pt-BR.ts +19 -4
  329. package/src/intl/pt.ts +17 -2
  330. package/src/intl/ro.ts +19 -4
  331. package/src/intl/sk.ts +17 -2
  332. package/src/intl/sl.ts +19 -4
  333. package/src/intl/sv.ts +19 -4
  334. package/src/intl/sw.ts +15 -2
  335. package/src/intl/ta.ts +19 -4
  336. package/src/intl/te.ts +19 -4
  337. package/src/intl/th.ts +19 -4
  338. package/src/intl/tr.ts +19 -4
  339. package/src/intl/uk.ts +19 -4
  340. package/src/intl/ur.ts +17 -2
  341. package/src/intl/vi.ts +19 -3
  342. package/src/intl/zh-CN.ts +19 -4
  343. package/src/intl/zh-TW.ts +19 -4
  344. package/src/local-first/Db.ts +2 -1
  345. package/src/local-first/Evolu.ts +48 -24
  346. package/src/local-first/Owner.ts +18 -14
  347. package/src/local-first/Protocol.ts +45 -35
  348. package/src/local-first/Query.ts +40 -24
  349. package/src/local-first/Relay.ts +13 -6
  350. package/src/local-first/Schema.ts +19 -11
  351. package/src/local-first/Shared.ts +4 -2
  352. package/src/local-first/Storage.ts +4 -2
  353. package/src/local-first/Timestamp.ts +1 -1
@@ -1,41 +1,39 @@
1
1
  /**
2
- * ## Intro
3
- *
4
2
  * JavaScript-native structured concurrency.
5
3
  *
6
- * Structured concurrency makes ownership of asynchronous work explicit.
7
- * Operations form a tree where every child belongs to a parent. A parent waits
8
- * for its children before it completes, and abort follows the tree: aborting a
9
- * parent requests abort of all its descendants. Races and fail-fast operations
10
- * also abort their remaining sibling branches.
4
+ * Structured concurrency organizes running tasks into a tree. Every child
5
+ * belongs to a parent, a parent waits for its children before it completes, and
6
+ * abort propagates from parents to descendants. Races and fail-fast control
7
+ * flow abort siblings that are no longer needed.
11
8
  *
12
9
  * With plain {@link AbortController} code, these guarantees depend on call-site
13
- * discipline: someone must remember the `finally` that aborts started work and
14
- * the await that waits for cleanup. {@link Run} makes both structural:
15
- * `run(task)` registers every child before it starts, and the parent settles
10
+ * discipline: someone must remember the `finally` that aborts started tasks and
11
+ * the await that waits for cleanup. Evolu makes both structural: `run(task)`
12
+ * registers every child before it starts, and the parent {@link Run} settles
16
13
  * only after child cleanup finishes.
17
14
  *
18
- * Evolu models structured concurrency with ordinary JavaScript:
15
+ * Evolu implements structured concurrency with:
19
16
  *
20
- * - A {@link Task} describes an asynchronous operation and its dependencies.
17
+ * - A {@link Task} is a function passed to Run that returns an {@link Awaitable}
18
+ * {@link Result} and declares its dependencies.
21
19
  * - A {@link Run} starts Tasks and owns their lifetimes.
22
20
  * - A {@link Fiber} is the Promise-backed handle returned when a Run starts a
23
21
  * Task.
24
22
  * - An {@link AbortableFiber} adds explicit abort and async disposal.
25
23
  *
26
- * The runtime core is deliberately small: ordinary functions, a callable Run
27
- * with closed-over state, Promise-backed Fibers, {@link AbortSignal}
28
- * propagation, and JavaScript resource management. Together, these primitives
29
- * provide abort, cleanup, defect handling, dependency injection, monitoring,
30
- * concurrency, and resource bracketing.
24
+ * Together, these APIs provide abort, cleanup, defect handling, dependency
25
+ * injection, monitoring, and resource management.
31
26
  *
32
- * Tasks return domain success or failure as {@link Result}. Abort is control
33
- * flow represented by {@link AbortError}. If a Task throws or rejects with
34
- * anything else, that is a defect: the root Run reports it and shuts down its
35
- * tree so code does not continue in a potentially invalid state.
27
+ * Tasks return a {@link Result} containing either success or a domain error.
28
+ * Abort is control flow represented by {@link AbortError}. If a Task throws or
29
+ * rejects with anything else, that is a defect: the root Run reports it and
30
+ * shuts down its tree so code does not continue in a potentially invalid
31
+ * state.
36
32
  *
37
33
  * ```ts
38
34
  * import {
35
+ * assertOk,
36
+ * assertType,
39
37
  * createRun,
40
38
  * err,
41
39
  * ok,
@@ -78,8 +76,8 @@
78
76
  * });
79
77
  *
80
78
  * const result = await run(getUser(user.id));
81
- * expectTypeOf(result).toEqualTypeOf<Result<User, UserNotFoundError>>();
82
- * expectOk(result, user);
79
+ * assertType<Result<User, UserNotFoundError>, typeof result>();
80
+ * assertOk(result, user);
83
81
  * ```
84
82
  *
85
83
  * In composition roots, prefer the lifecycle API from the matching Evolu
@@ -122,6 +120,8 @@
122
120
  *
123
121
  * ```ts
124
122
  * import {
123
+ * assertOk,
124
+ * assertType,
125
125
  * createRun,
126
126
  * err,
127
127
  * ok,
@@ -180,13 +180,14 @@
180
180
  *
181
181
  * await using run = createRun();
182
182
  * const result = await run(getUserWithProfile("user-1"));
183
- * expectTypeOf(result).toEqualTypeOf<
183
+ * assertType<
184
184
  * Result<
185
185
  * { readonly user: User; readonly profile: Profile },
186
186
  * UserNotFoundError | ProfileNotFoundError
187
- * >
187
+ * >,
188
+ * typeof result
188
189
  * >();
189
- * expectOk(result, {
190
+ * assertOk(result, {
190
191
  * user: { id: "user-1", profileId: "profile-1" },
191
192
  * profile: { id: "profile-1" },
192
193
  * });
@@ -201,10 +202,11 @@
201
202
  *
202
203
  * {@link fetch} with a body mode already returns a plain value, so resilience is
203
204
  * ordinary Task composition. Combine {@link timeout} and {@link retry} to bound
204
- * each attempt and retry recoverable domain failures:
205
+ * each attempt and retry recoverable domain errors:
205
206
  *
206
207
  * ```ts
207
208
  * import {
209
+ * assertType,
208
210
  * exponential,
209
211
  * fetch,
210
212
  * jitter,
@@ -225,8 +227,9 @@
225
227
  * jitter("100%")(maxDelay("20s")(take(2)(exponential("100ms")))),
226
228
  * );
227
229
  *
228
- * expectTypeOf(fetchWithRetry).returns.toEqualTypeOf<
229
- * Task<string, RetryTaskError<FetchError | TimeoutError>>
230
+ * assertType<
231
+ * Task<string, RetryTaskError<FetchError | TimeoutError>>,
232
+ * ReturnType<typeof fetchWithRetry>
230
233
  * >();
231
234
  * ```
232
235
  *
@@ -235,7 +238,15 @@
235
238
  * Run composed Tasks with a `concurrency` option and {@link all}:
236
239
  *
237
240
  * ```ts
238
- * import { all, createRun, ok, sleep, type Task } from "@evolu/common";
241
+ * import {
242
+ * assertEqual,
243
+ * assertOk,
244
+ * all,
245
+ * createRun,
246
+ * ok,
247
+ * sleep,
248
+ * type Task,
249
+ * } from "@evolu/common";
239
250
  *
240
251
  * await using run = createRun();
241
252
  *
@@ -258,8 +269,8 @@
258
269
  *
259
270
  * // At most 2 concurrent requests.
260
271
  * const result = await run(all(urls, fetchUrl, { concurrency: 2 }));
261
- * expectOk(result, urls);
262
- * expect(maxActiveRequests).toBe(2);
272
+ * assertOk(result, urls);
273
+ * assertEqual(maxActiveRequests, 2);
263
274
  * ```
264
275
  *
265
276
  * Task helpers compose Tasks; concurrency primitives are stateful objects that
@@ -291,7 +302,7 @@
291
302
  * resources, or services shared by all code running inside a Run.
292
303
  *
293
304
  * ```ts
294
- * import { createRun, ok, type Task } from "@evolu/common";
305
+ * import { assertOk, createRun, ok, type Task } from "@evolu/common";
295
306
  *
296
307
  * interface GreetingFormatter {
297
308
  * readonly format: (name: string) => string;
@@ -316,10 +327,10 @@
316
327
  * await using run = createRun({ greetingFormatter: formal });
317
328
  *
318
329
  * // Root dependencies are inherited.
319
- * expectOk(await run(greet("Ada")), "Hello, Ada");
330
+ * assertOk(await run(greet("Ada")), "Hello, Ada");
320
331
  *
321
332
  * // Child-specific dependencies replace the root's custom dependencies.
322
- * expectOk(
333
+ * assertOk(
323
334
  * await run(greet("Ada"), { greetingFormatter: casual }),
324
335
  * "Hi, Ada",
325
336
  * );
@@ -370,7 +381,15 @@
370
381
  * `undefined` should represent valid absence, not failure.
371
382
  *
372
383
  * ```ts
373
- * import { createRun, ok, type Task, type Typed } from "@evolu/common";
384
+ * import {
385
+ * assertEqual,
386
+ * assertFalse,
387
+ * assertTrue,
388
+ * createRun,
389
+ * ok,
390
+ * type Task,
391
+ * type Typed,
392
+ * } from "@evolu/common";
374
393
  *
375
394
  * interface Socket extends AsyncDisposable {
376
395
  * readonly send: (message: string) => string;
@@ -384,8 +403,9 @@
384
403
  * const openSocket: Task<Socket, ConnectionFailedError> = () =>
385
404
  * ok({
386
405
  * send: (message) => message,
387
- * [Symbol.asyncDispose]: async () => {
406
+ * [Symbol.asyncDispose]: () => {
388
407
  * socketDisposed = true;
408
+ * return Promise.resolve();
389
409
  * },
390
410
  * });
391
411
  *
@@ -417,16 +437,16 @@
417
437
  *
418
438
  * await using run = createRun();
419
439
  * const result = await run(createConnection);
420
- * assert(result.ok);
421
- * expect(socketDisposed).toBe(false);
422
- * expect(result.value.send("hello")).toBe("hello");
440
+ * assertTrue(result.ok);
441
+ * assertFalse(socketDisposed);
442
+ * assertEqual(result.value.send("hello"), "hello");
423
443
  * await result.value[Symbol.asyncDispose]();
424
- * expect(socketDisposed).toBe(true);
444
+ * assertTrue(socketDisposed);
425
445
  * ```
426
446
  *
427
- * Use {@link Run.ok} with `await using` when an infallible Task returns a
428
- * disposable value. Use {@link acquireUseRelease} when acquisition and release
429
- * are separate operations rather than a disposable value.
447
+ * Use {@link Run.ok} with `await using` when a Task whose error type is `never`
448
+ * returns a disposable value. Use {@link acquireUseRelease} when acquisition and
449
+ * release are separate steps rather than a disposable value.
430
450
  *
431
451
  * ## Awaitable
432
452
  *
@@ -439,7 +459,7 @@
439
459
  *
440
460
  * A Task is an async ownership boundary, not a general unit of program
441
461
  * decomposition. Calling `run(task)` always creates a child Run by design. Use
442
- * ordinary promises when an async operation does not need its own Run.
462
+ * a plain async function when it does not need its own Run.
443
463
  *
444
464
  * A unified sync/async effect API is technically possible. It can detect
445
465
  * Promise-like values with {@link isPromiseLike}, dispose synchronous resources
@@ -457,7 +477,7 @@
457
477
  * code performs effects with the result. For example, a pure function can
458
478
  * accept a {@link RandomNumber} value instead of depending on {@link Random}.
459
479
  *
460
- * Large CPU-bound operations, such as parsing large JSON, sorting millions of
480
+ * Large CPU-bound computations, such as parsing large JSON, sorting millions of
461
481
  * items, or complex cryptography, belong in a worker. Model the asynchronous
462
482
  * call to that worker as a Task so Run can provide timeout, abort, cleanup, and
463
483
  * monitoring.
@@ -517,10 +537,10 @@
517
537
  * ### What should Task code do with defects?
518
538
  *
519
539
  * Nothing. Once a defect reaches the {@link Run}, it is too late: the root Run
520
- * panics, running Tasks are aborted, and the Run tree shuts down. If an
521
- * operation can throw or reject for a recoverable reason, wrap that operation
522
- * with {@link trySync} or {@link tryAsync} so the failure becomes a typed
523
- * {@link Result} error. Let unrecoverable failures propagate as defects.
540
+ * panics, running Tasks are aborted, and the Run tree shuts down. Use
541
+ * {@link trySync} or {@link tryAsync} to turn recoverable exceptions and Promise
542
+ * rejections into typed {@link Result} errors. Let unrecoverable failures
543
+ * propagate as defects.
524
544
  *
525
545
  * ### Why does a defect panic the whole Run tree?
526
546
  *
@@ -578,7 +598,7 @@
578
598
  * each iteration reuses the same stack frame:
579
599
  *
580
600
  * ```ts
581
- * import { createRun, ok, type Task } from "@evolu/common";
601
+ * import { assertOk, createRun, ok, type Task } from "@evolu/common";
582
602
  *
583
603
  * interface TreeNode {
584
604
  * readonly value: string;
@@ -602,7 +622,7 @@
602
622
  * };
603
623
  *
604
624
  * await using run = createRun();
605
- * expectOk(
625
+ * assertOk(
606
626
  * await run(
607
627
  * visitTree({
608
628
  * value: "root",
@@ -619,13 +639,6 @@
619
639
  * periodically await {@link yieldNow} for cooperative scheduling, and move
620
640
  * CPU-bound work to a worker.
621
641
  *
622
- * ### Should a Task be called directly?
623
- *
624
- * Only inside Task internals that explicitly require same-Run execution. A
625
- * direct call, `task(run)`, uses the current Run instead of creating a child
626
- * Run, so it bypasses child lifetime tracking, scheduling metadata, and child
627
- * disposal boundaries. Application code should use `run(task)`.
628
- *
629
642
  * ### Where are fork and join?
630
643
  *
631
644
  * Calling `run(task)` is fork: it starts a child Task and returns a
@@ -662,13 +675,89 @@ import { type Millis, type PositiveDuration, type TimeDep, type TestTimeDep } fr
662
675
  import { type InferType, NonNegativeInt, PositiveInt, String, Unknown, UnknownResult, type Id, type ObjectType, type RecordType, type Typed, type TypedType } from "./Type.ts";
663
676
  import type { Awaitable, ParameterIntersection, Predicate } from "./Types.ts";
664
677
  /**
665
- * An operation run by {@link Run} that returns a {@link Result} synchronously or
666
- * asynchronously and declares its dependencies through `D`.
667
- *
668
- * Its return type is {@link Awaitable}.
678
+ * A function passed to {@link Run} that returns an {@link Awaitable}
679
+ * {@link Result} and declares its dependencies through `D`.
669
680
  *
670
681
  * See the {@link @evolu/common!Task | Task overview}.
671
682
  *
683
+ * ### Example
684
+ *
685
+ * A Task that can't fail with a domain error:
686
+ *
687
+ * ```ts
688
+ * import {
689
+ * assertEqual,
690
+ * assertOk,
691
+ * createRun,
692
+ * ok,
693
+ * type Task,
694
+ * } from "@evolu/common";
695
+ *
696
+ * const greet: Task<string> = () => ok("Hello!");
697
+ * const main: Task<string> = (run) => run(greet);
698
+ *
699
+ * await using run = createRun();
700
+ * assertOk(await run(main), "Hello!");
701
+ *
702
+ * // Without domain errors, `run.ok` returns the Ok value.
703
+ * assertEqual(await run.ok(main), "Hello!");
704
+ * ```
705
+ *
706
+ * A Task that can fail with a domain error:
707
+ *
708
+ * ```ts
709
+ * import {
710
+ * assertErr,
711
+ * createRun,
712
+ * err,
713
+ * type Task,
714
+ * type Typed,
715
+ * } from "@evolu/common";
716
+ *
717
+ * const findUser =
718
+ * (id: string): Task<string, UserNotFoundError> =>
719
+ * () =>
720
+ * err({ type: "UserNotFound", id });
721
+ *
722
+ * interface UserNotFoundError extends Typed<"UserNotFound"> {
723
+ * readonly id: string;
724
+ * }
725
+ *
726
+ * await using run = createRun();
727
+ * assertErr(await run(findUser("user-1")), {
728
+ * type: "UserNotFound",
729
+ * id: "user-1",
730
+ * });
731
+ * ```
732
+ *
733
+ * A Task with dependencies:
734
+ *
735
+ * ```ts
736
+ * import { assertOk, createRun, ok, type Task } from "@evolu/common";
737
+ *
738
+ * interface Config {
739
+ * readonly greeting: string;
740
+ * }
741
+ *
742
+ * interface ConfigDep {
743
+ * readonly config: Config;
744
+ * }
745
+ *
746
+ * const greet: Task<string, never, ConfigDep> = (run) =>
747
+ * ok(`${run.deps.config.greeting}!`);
748
+ *
749
+ * const config: Config = { greeting: "Hello" };
750
+ * await using run = createRun({ config });
751
+ * assertOk(await run(greet), "Hello!");
752
+ * ```
753
+ *
754
+ * Start Tasks with `run(task)`, as shown above.
755
+ *
756
+ * A Task can also be called directly as `task(run)`, but this is rarely needed.
757
+ * The call executes the Task inline in the current Run, as if its body were
758
+ * part of the parent Task, so it does not create a child Run. This is mainly
759
+ * useful for Task composition helpers.
760
+ *
672
761
  * @group Core
673
762
  */
674
763
  export type Task<T, E = never, D = unknown> = (run: Run<D>) => Awaitable<Result<T, E>>;
@@ -794,7 +883,13 @@ export interface Run<D = unknown> {
794
883
  * ### Example
795
884
  *
796
885
  * ```ts
797
- * import { createRun, ok, type Task } from "@evolu/common";
886
+ * import {
887
+ * assertSame,
888
+ * assertOk,
889
+ * createRun,
890
+ * ok,
891
+ * type Task,
892
+ * } from "@evolu/common";
798
893
  *
799
894
  * interface Db {
800
895
  * readonly name: string;
@@ -807,16 +902,23 @@ export interface Run<D = unknown> {
807
902
  * const db: Db = { name: "main" };
808
903
  * const loadUser: Task<string> = () => ok("Ada");
809
904
  * const saveUser: Task<void, never, DbDep> = (run) => {
810
- * expect(run.deps.db).toBe(db);
905
+ * assertSame(run.deps.db, db);
811
906
  * return ok();
812
907
  * };
813
908
  *
814
909
  * await using run = createRun({ db });
815
910
  * const userResult = await run(loadUser);
816
911
  * const savedResult = await run(saveUser);
817
- * expectOk(userResult, "Ada");
818
- * expectOk(savedResult, undefined);
912
+ * assertOk(userResult, "Ada");
913
+ * assertOk(savedResult, undefined);
819
914
  * ```
915
+ *
916
+ * Start Tasks with `run(task)`, as shown above.
917
+ *
918
+ * A Task can also be called directly as `task(run)`, but this is rarely
919
+ * needed. The call executes the Task inline in the current Run, as if its
920
+ * body were part of the parent Task, so it does not create a child Run. This
921
+ * is mainly useful for Task composition helpers.
820
922
  */
821
923
  <T, E>(task: Task<T, E, D>): Fiber<T, E, D>;
822
924
  /**
@@ -837,7 +939,13 @@ export interface Run<D = unknown> {
837
939
  * ### Example
838
940
  *
839
941
  * ```ts
840
- * import { createRun, ok, type Task, type Typed } from "@evolu/common";
942
+ * import {
943
+ * assertEqual,
944
+ * createRun,
945
+ * ok,
946
+ * type Task,
947
+ * type Typed,
948
+ * } from "@evolu/common";
841
949
  *
842
950
  * const loadConfig: Task<string, ConfigInvalidError> = () =>
843
951
  * ok("config");
@@ -845,7 +953,7 @@ export interface Run<D = unknown> {
845
953
  * interface ConfigInvalidError extends Typed<"ConfigInvalid"> {}
846
954
  *
847
955
  * await using run = createRun();
848
- * expect(await run.orThrow(loadConfig)).toBe("config");
956
+ * assertEqual(await run.orThrow(loadConfig), "config");
849
957
  * ```
850
958
  */
851
959
  readonly orThrow: {
@@ -861,7 +969,13 @@ export interface Run<D = unknown> {
861
969
  * ### Example
862
970
  *
863
971
  * ```ts
864
- * import { createRun, ok, type Task } from "@evolu/common";
972
+ * import {
973
+ * assertEqual,
974
+ * assertTrue,
975
+ * createRun,
976
+ * ok,
977
+ * type Task,
978
+ * } from "@evolu/common";
865
979
  *
866
980
  * interface Resource extends AsyncDisposable {
867
981
  * readonly value: string;
@@ -871,17 +985,18 @@ export interface Run<D = unknown> {
871
985
  * const openResource: Task<Resource> = () =>
872
986
  * ok({
873
987
  * value: "resource",
874
- * [Symbol.asyncDispose]: async () => {
988
+ * [Symbol.asyncDispose]: () => {
875
989
  * disposed = true;
990
+ * return Promise.resolve();
876
991
  * },
877
992
  * });
878
993
  *
879
994
  * await using run = createRun();
880
995
  * {
881
996
  * await using resource = await run.ok(openResource);
882
- * expect(resource.value).toBe("resource");
997
+ * assertEqual(resource.value, "resource");
883
998
  * }
884
- * expect(disposed).toBe(true);
999
+ * assertTrue(disposed);
885
1000
  * ```
886
1001
  */
887
1002
  readonly ok: {
@@ -906,6 +1021,9 @@ export interface Run<D = unknown> {
906
1021
  *
907
1022
  * ```ts
908
1023
  * import {
1024
+ * assertFalse,
1025
+ * assertType,
1026
+ * assertTrue,
909
1027
  * AbortError,
910
1028
  * createRun,
911
1029
  * ok,
@@ -926,13 +1044,11 @@ export interface Run<D = unknown> {
926
1044
  *
927
1045
  * await using run = createRun();
928
1046
  * const fiber = run.abortable(loadUser, { db });
929
- * expectTypeOf(fiber).toEqualTypeOf<
930
- * AbortableFiber<string, never, DbDep>
931
- * >();
1047
+ * assertType<AbortableFiber<string, never, DbDep>, typeof fiber>();
932
1048
  * fiber.abort();
933
1049
  * const userResult = await fiber;
934
- * assert(!userResult.ok);
935
- * expect(AbortError.is(userResult.error)).toBe(true);
1050
+ * assertFalse(userResult.ok);
1051
+ * assertTrue(AbortError.is(userResult.error));
936
1052
  * ```
937
1053
  */
938
1054
  readonly abortable: {
@@ -968,35 +1084,45 @@ export interface Run<D = unknown> {
968
1084
  * ### Abort masks
969
1085
  *
970
1086
  * ```ts
971
- * import { createRun, ok, unabortable, type Task } from "@evolu/common";
1087
+ * import {
1088
+ * assertEqual,
1089
+ * assertOk,
1090
+ * assertTrue,
1091
+ * createRun,
1092
+ * ok,
1093
+ * unabortable,
1094
+ * type Task,
1095
+ * } from "@evolu/common";
972
1096
  *
973
1097
  * const syncUsers: Task<string> = () => ok("synced");
974
1098
  * const syncParent = unabortable(async (run) => {
975
- * expect(run.snapshot().abortMask).toBe(1);
1099
+ * assertEqual(run.snapshot().abortMask, 1);
976
1100
  *
977
1101
  * // Plain daemon — the caller's mask does not follow it, so abort
978
1102
  * // requests are observed.
979
1103
  * const fiber = run.daemon(syncUsers);
980
- * expect(fiber.run.snapshot().abortMask).toBe(0);
1104
+ * assertEqual(fiber.run.snapshot().abortMask, 0);
981
1105
  *
982
1106
  * // Explicitly masked daemon — finishes once started.
983
1107
  * const maskedFiber = run.daemon(unabortable(syncUsers));
984
- * expect(maskedFiber.run.snapshot().abortMask).toBe(1);
1108
+ * assertEqual(maskedFiber.run.snapshot().abortMask, 1);
985
1109
  * const firstResult = await fiber;
986
1110
  * const secondResult = await maskedFiber;
987
- * assert(firstResult.ok);
988
- * assert(secondResult.ok);
1111
+ * assertTrue(firstResult.ok);
1112
+ * assertTrue(secondResult.ok);
989
1113
  * return ok([firstResult.value, secondResult.value] as const);
990
1114
  * });
991
1115
  *
992
1116
  * await using run = createRun();
993
- * expectOk(await run(syncParent), ["synced", "synced"]);
1117
+ * assertOk(await run(syncParent), ["synced", "synced"]);
994
1118
  * ```
995
1119
  *
996
1120
  * ### Aborting a daemon
997
1121
  *
998
1122
  * ```ts
999
1123
  * import {
1124
+ * assertFalse,
1125
+ * assertTrue,
1000
1126
  * AbortError,
1001
1127
  * createRun,
1002
1128
  * ok,
@@ -1018,14 +1144,21 @@ export interface Run<D = unknown> {
1018
1144
  * const fiber = run.daemon(syncUsers, { db });
1019
1145
  * fiber.abort();
1020
1146
  * const syncResult = await fiber;
1021
- * assert(!syncResult.ok);
1022
- * expect(AbortError.is(syncResult.error)).toBe(true);
1147
+ * assertFalse(syncResult.ok);
1148
+ * assertTrue(AbortError.is(syncResult.error));
1023
1149
  * ```
1024
1150
  *
1025
1151
  * ### Disposing a daemon
1026
1152
  *
1027
1153
  * ```ts
1028
- * import { createRun, ok, waitForAbort, type Task } from "@evolu/common";
1154
+ * import {
1155
+ * assertOk,
1156
+ * assertTrue,
1157
+ * createRun,
1158
+ * ok,
1159
+ * waitForAbort,
1160
+ * type Task,
1161
+ * } from "@evolu/common";
1029
1162
  *
1030
1163
  * let syncStopped = false;
1031
1164
  * const syncUsers: Task<never> = async (run) => {
@@ -1040,9 +1173,9 @@ export interface Run<D = unknown> {
1040
1173
  * {
1041
1174
  * // Async disposal requests abort and waits for the daemon to stop.
1042
1175
  * await using _syncFiber = run.daemon(syncUsers);
1043
- * expectOk(await run(loadUser), "Ada");
1176
+ * assertOk(await run(loadUser), "Ada");
1044
1177
  * }
1045
- * expect(syncStopped).toBe(true);
1178
+ * assertTrue(syncStopped);
1046
1179
  * ```
1047
1180
  */
1048
1181
  readonly daemon: {
@@ -1053,8 +1186,8 @@ export interface Run<D = unknown> {
1053
1186
  * Creates a {@link DisposableRun} attached to the root {@link Run} with this
1054
1187
  * Run's deps.
1055
1188
  *
1056
- * Use it when you need a Run that can be reused across multiple operations.
1057
- * For a single long-lived {@link Task}, use {@link Run.daemon}.
1189
+ * Use it to give multiple related Tasks a shared lifetime. For a single
1190
+ * long-lived {@link Task}, use {@link Run.daemon}.
1058
1191
  *
1059
1192
  * Use deps to replace the created Run's custom deps. Default deps
1060
1193
  * ({@link RunDefaultDeps}) are inherited unless explicitly replaced with
@@ -1068,7 +1201,13 @@ export interface Run<D = unknown> {
1068
1201
  * ### Example
1069
1202
  *
1070
1203
  * ```ts
1071
- * import { createRun, ok, type Task } from "@evolu/common";
1204
+ * import {
1205
+ * assertEqual,
1206
+ * assertOk,
1207
+ * createRun,
1208
+ * ok,
1209
+ * type Task,
1210
+ * } from "@evolu/common";
1072
1211
  *
1073
1212
  * interface DbDep {
1074
1213
  * readonly db: { readonly users: Array<string> };
@@ -1086,9 +1225,9 @@ export interface Run<D = unknown> {
1086
1225
  * await using createdRun = run.create({ db });
1087
1226
  * const userResult = await createdRun(loadUser);
1088
1227
  * const savedResult = await createdRun(saveUser);
1089
- * expectOk(userResult, "Ada");
1090
- * expectOk(savedResult, undefined);
1091
- * expect(db.users).toEqual(["Ada", "Grace"]);
1228
+ * assertOk(userResult, "Ada");
1229
+ * assertOk(savedResult, undefined);
1230
+ * assertEqual(db.users, ["Ada", "Grace"]);
1092
1231
  * ```
1093
1232
  */
1094
1233
  readonly create: {
@@ -1139,20 +1278,27 @@ export interface Run<D = unknown> {
1139
1278
  * ### Example
1140
1279
  *
1141
1280
  * ```ts
1142
- * import { AbortError, createRun, ok, sleep } from "@evolu/common";
1281
+ * import {
1282
+ * assertFalse,
1283
+ * assertTrue,
1284
+ * AbortError,
1285
+ * createRun,
1286
+ * ok,
1287
+ * sleep,
1288
+ * } from "@evolu/common";
1143
1289
  *
1144
1290
  * let socketClosed = false;
1145
1291
  * const openSocket = () => ({
1146
1292
  * close: () => {
1147
1293
  * socketClosed = true;
1148
1294
  * },
1149
- * read: async () => "message",
1295
+ * read: () => Promise.resolve("message"),
1150
1296
  * });
1151
1297
  *
1152
1298
  * await using run = createRun();
1153
1299
  * const fiber = run.abortable(async (run) => {
1154
1300
  * const socket = openSocket();
1155
- * using closeOnAbort = run.onAbort(() => {
1301
+ * using _closeOnAbort = run.onAbort(() => {
1156
1302
  * socket.close();
1157
1303
  * });
1158
1304
  *
@@ -1162,9 +1308,9 @@ export interface Run<D = unknown> {
1162
1308
  * });
1163
1309
  * fiber.abort();
1164
1310
  * const result = await fiber;
1165
- * assert(!result.ok);
1166
- * expect(AbortError.is(result.error)).toBe(true);
1167
- * expect(socketClosed).toBe(true);
1311
+ * assertFalse(result.ok);
1312
+ * assertTrue(AbortError.is(result.error));
1313
+ * assertTrue(socketClosed);
1168
1314
  * ```
1169
1315
  */
1170
1316
  readonly onAbort: (callback: (abortError: AbortError) => void) => Disposable | null;
@@ -1229,7 +1375,7 @@ export interface DisposableRun<D = unknown> extends Run<D>, Disposable, AsyncDis
1229
1375
  * ### Example
1230
1376
  *
1231
1377
  * ```ts
1232
- * import { createRun } from "@evolu/common";
1378
+ * import { assertFalse, assertTrue, createRun } from "@evolu/common";
1233
1379
  *
1234
1380
  * let connectionClosed = false;
1235
1381
  * {
@@ -1238,9 +1384,9 @@ export interface DisposableRun<D = unknown> extends Run<D>, Disposable, AsyncDis
1238
1384
  * connectionClosed = true;
1239
1385
  * });
1240
1386
  *
1241
- * expect(connectionClosed).toBe(false);
1387
+ * assertFalse(connectionClosed);
1242
1388
  * }
1243
- * expect(connectionClosed).toBe(true);
1389
+ * assertTrue(connectionClosed);
1244
1390
  * ```
1245
1391
  */
1246
1392
  readonly defer: (finalizer: () => Awaitable<void>) => void;
@@ -1304,7 +1450,15 @@ export interface DisposableRun<D = unknown> extends Run<D>, Disposable, AsyncDis
1304
1450
  * ### Example
1305
1451
  *
1306
1452
  * ```ts
1307
- * import { createRun, ok, type Fiber, type Task } from "@evolu/common";
1453
+ * import {
1454
+ * assertEqual,
1455
+ * assertOk,
1456
+ * assertType,
1457
+ * createRun,
1458
+ * ok,
1459
+ * type Fiber,
1460
+ * type Task,
1461
+ * } from "@evolu/common";
1308
1462
  *
1309
1463
  * await using run = createRun();
1310
1464
  *
@@ -1314,9 +1468,9 @@ export interface DisposableRun<D = unknown> extends Run<D>, Disposable, AsyncDis
1314
1468
  * const userResult = await fiber;
1315
1469
  * const snapshot = fiber.run.snapshot();
1316
1470
  *
1317
- * expectTypeOf(fiber).toEqualTypeOf<Fiber<string, never>>();
1318
- * expectOk(userResult, "Ada");
1319
- * expect(snapshot.id).toBe(fiber.run.id);
1471
+ * assertType<Fiber<string, never>, typeof fiber>();
1472
+ * assertOk(userResult, "Ada");
1473
+ * assertEqual(snapshot.id, fiber.run.id);
1320
1474
  * ```
1321
1475
  *
1322
1476
  * @group Core
@@ -1366,6 +1520,9 @@ export type InferFiberDeps<TFiber extends AnyFiber> = TFiber extends Fiber<any,
1366
1520
  *
1367
1521
  * ```ts
1368
1522
  * import {
1523
+ * assertFalse,
1524
+ * assertType,
1525
+ * assertTrue,
1369
1526
  * AbortError,
1370
1527
  * createRun,
1371
1528
  * ok,
@@ -1382,11 +1539,11 @@ export type InferFiberDeps<TFiber extends AnyFiber> = TFiber extends Fiber<any,
1382
1539
  * };
1383
1540
  *
1384
1541
  * const fiber = run.abortable<string, never>(fetchData);
1385
- * expectTypeOf(fiber).toEqualTypeOf<AbortableFiber<string, never>>();
1542
+ * assertType<AbortableFiber<string, never>, typeof fiber>();
1386
1543
  * fiber.abort();
1387
1544
  * const result = await fiber;
1388
- * assert(!result.ok);
1389
- * expect(AbortError.is(result.error)).toBe(true);
1545
+ * assertFalse(result.ok);
1546
+ * assertTrue(AbortError.is(result.error));
1390
1547
  * ```
1391
1548
  *
1392
1549
  * @group Core
@@ -1759,7 +1916,7 @@ export interface CreateRun {
1759
1916
  * ### Example
1760
1917
  *
1761
1918
  * ```ts
1762
- * import { createRun, ok, type Task } from "@evolu/common";
1919
+ * import { assertOk, createRun, ok, type Task } from "@evolu/common";
1763
1920
  *
1764
1921
  * interface ConfigDep {
1765
1922
  * readonly config: { readonly apiUrl: string };
@@ -1771,7 +1928,7 @@ export interface CreateRun {
1771
1928
  * await using run = createRun({
1772
1929
  * config: { apiUrl: "https://api.example.com" },
1773
1930
  * });
1774
- * expectOk(await run(loadApiUrl), "https://api.example.com");
1931
+ * assertOk(await run(loadApiUrl), "https://api.example.com");
1775
1932
  * ```
1776
1933
  *
1777
1934
  * @group Run
@@ -1852,12 +2009,12 @@ export declare const testCreateDeps: (options?: {
1852
2009
  * ### Example
1853
2010
  *
1854
2011
  * ```ts
1855
- * import { ok, testCreateRun, type Task } from "@evolu/common";
2012
+ * import { assertOk, ok, testCreateRun, type Task } from "@evolu/common";
1856
2013
  *
1857
2014
  * const readTime: Task<number> = (run) => ok(run.deps.time.now());
1858
2015
  *
1859
2016
  * await using run = testCreateRun();
1860
- * expectOk(await run(readTime), 0);
2017
+ * assertOk(await run(readTime), 0);
1861
2018
  * ```
1862
2019
  *
1863
2020
  * @group Testing
@@ -1869,7 +2026,7 @@ export declare function testCreateRun(deps?: TestRunDefaultDeps): DisposableRun<
1869
2026
  * ### Example
1870
2027
  *
1871
2028
  * ```ts
1872
- * import { ok, testCreateRun, type Task } from "@evolu/common";
2029
+ * import { assertOk, ok, testCreateRun, type Task } from "@evolu/common";
1873
2030
  *
1874
2031
  * interface FeatureDep {
1875
2032
  * readonly feature: { readonly enabled: boolean };
@@ -1879,7 +2036,7 @@ export declare function testCreateRun(deps?: TestRunDefaultDeps): DisposableRun<
1879
2036
  * ok(run.deps.feature.enabled);
1880
2037
  *
1881
2038
  * await using run = testCreateRun({ feature: { enabled: true } });
1882
- * expectOk(await run(isFeatureEnabled), true);
2039
+ * assertOk(await run(isFeatureEnabled), true);
1883
2040
  * ```
1884
2041
  */
1885
2042
  export declare function testCreateRun<D extends object>(deps: RunCustomDeps<D>): DisposableRun<TestRunDefaultDeps & D>;
@@ -1906,7 +2063,7 @@ export type InferTaskRecordDeps<TTasks extends TaskRecord> = InferTasksDeps<Read
1906
2063
  * Options shared by {@link Task} collection helpers.
1907
2064
  *
1908
2065
  * `concurrency` controls how many Tasks run at once. It defaults to `1`. For
1909
- * CPU-bound Tasks backed by workers or parallel native operations, a platform
2066
+ * CPU-bound Tasks backed by workers or native parallelism, a platform
1910
2067
  * `availableParallelism()` result is often a good limit. For network or
1911
2068
  * database Tasks, choose a limit based on the transport, server, connection
1912
2069
  * pool, and rate limits.
@@ -1967,6 +2124,9 @@ export type InferTasksOk<TTasks> = {
1967
2124
  *
1968
2125
  * ```ts
1969
2126
  * import {
2127
+ * assertErr,
2128
+ * assertEqual,
2129
+ * assertType,
1970
2130
  * all,
1971
2131
  * createRun,
1972
2132
  * err,
@@ -1997,14 +2157,12 @@ export type InferTasksOk<TTasks> = {
1997
2157
  * collect: false,
1998
2158
  * }),
1999
2159
  * );
2000
- * expectTypeOf(saveResult).toEqualTypeOf<
2001
- * Result<void, SaveUserFailedError>
2002
- * >();
2003
- * expectErr(saveResult, {
2160
+ * assertType<Result<void, SaveUserFailedError>, typeof saveResult>();
2161
+ * assertErr(saveResult, {
2004
2162
  * type: "SaveUserFailed",
2005
2163
  * userId: "missing",
2006
2164
  * });
2007
- * expect(savedUserIds).toEqual(["user-1"]);
2165
+ * assertEqual(savedUserIds, ["user-1"]);
2008
2166
  * ```
2009
2167
  *
2010
2168
  * @group Collection
@@ -2019,6 +2177,8 @@ export declare function all<const TTasks extends TaskRecord>(tasks: TTasks, opti
2019
2177
  *
2020
2178
  * ```ts
2021
2179
  * import {
2180
+ * assertOk,
2181
+ * assertType,
2022
2182
  * all,
2023
2183
  * createRun,
2024
2184
  * ok,
@@ -2040,10 +2200,11 @@ export declare function all<const TTasks extends TaskRecord>(tasks: TTasks, opti
2040
2200
  *
2041
2201
  * await using run = createRun();
2042
2202
  * const dashboard = await run(all([fetchUser, fetchPosts]));
2043
- * expectTypeOf(dashboard).toEqualTypeOf<
2044
- * Result<readonly [User, ReadonlyArray<Post>]>
2203
+ * assertType<
2204
+ * Result<readonly [User, ReadonlyArray<Post>]>,
2205
+ * typeof dashboard
2045
2206
  * >();
2046
- * expectOk(dashboard, [{ id: "user-1" }, [{ id: "post-1" }]]);
2207
+ * assertOk(dashboard, [{ id: "user-1" }, [{ id: "post-1" }]]);
2047
2208
  * ```
2048
2209
  */
2049
2210
  export declare function all<const TTasks extends ReadonlyArray<AnyTask>>(tasks: TTasks, options?: TaskCollectionOptions): Task<InferTasksOk<TTasks>, InferTaskErr<TTasks[number]>, InferTasksDeps<TTasks>>;
@@ -2054,6 +2215,8 @@ export declare function all<const TTasks extends ReadonlyArray<AnyTask>>(tasks:
2054
2215
  *
2055
2216
  * ```ts
2056
2217
  * import {
2218
+ * assertOk,
2219
+ * assertType,
2057
2220
  * all,
2058
2221
  * createRun,
2059
2222
  * ok,
@@ -2076,10 +2239,11 @@ export declare function all<const TTasks extends ReadonlyArray<AnyTask>>(tasks:
2076
2239
  * await using run = createRun();
2077
2240
  * const result = await run(all({ user: fetchUser, posts: fetchPosts }));
2078
2241
  *
2079
- * expectTypeOf(result).toEqualTypeOf<
2080
- * Result<{ readonly user: User; readonly posts: ReadonlyArray<Post> }>
2242
+ * assertType<
2243
+ * Result<{ readonly user: User; readonly posts: ReadonlyArray<Post> }>,
2244
+ * typeof result
2081
2245
  * >();
2082
- * expectOk(result, {
2246
+ * assertOk(result, {
2083
2247
  * user: { id: "user-1" },
2084
2248
  * posts: [{ id: "post-1" }],
2085
2249
  * });
@@ -2098,6 +2262,9 @@ export declare function all<const TValues extends Readonly<Record<string, unknow
2098
2262
  *
2099
2263
  * ```ts
2100
2264
  * import {
2265
+ * assertEqual,
2266
+ * assertOk,
2267
+ * assertType,
2101
2268
  * all,
2102
2269
  * createRun,
2103
2270
  * ok,
@@ -2122,12 +2289,12 @@ export declare function all<const TValues extends Readonly<Record<string, unknow
2122
2289
  * });
2123
2290
  *
2124
2291
  * // Mapping is eager: it happens before the returned Task starts.
2125
- * expect(indexes).toEqual([0, 1]);
2292
+ * assertEqual(indexes, [0, 1]);
2126
2293
  *
2127
2294
  * await using run = createRun();
2128
2295
  * const result = await run(loadUsers);
2129
- * expectTypeOf(result).toEqualTypeOf<Result<readonly [User, User]>>();
2130
- * expectOk(result, [{ id: "user-1" }, { id: "user-2" }]);
2296
+ * assertType<Result<readonly [User, User]>, typeof result>();
2297
+ * assertOk(result, [{ id: "user-1" }, { id: "user-2" }]);
2131
2298
  * ```
2132
2299
  */
2133
2300
  export declare function all<const TValues extends ReadonlyArray<unknown>, TTask extends AnyTask>(values: TValues, fn: (value: TValues[number], index: number) => TTask, options?: TaskCollectionOptions): Task<{
@@ -2140,6 +2307,9 @@ export declare function all<const TValues extends ReadonlyArray<unknown>, TTask
2140
2307
  *
2141
2308
  * ```ts
2142
2309
  * import {
2310
+ * assertEqual,
2311
+ * assertOk,
2312
+ * assertType,
2143
2313
  * all,
2144
2314
  * createRun,
2145
2315
  * ok,
@@ -2167,14 +2337,15 @@ export declare function all<const TValues extends ReadonlyArray<unknown>, TTask
2167
2337
  * });
2168
2338
  *
2169
2339
  * // Mapping is eager: it happens before the returned Task starts.
2170
- * expect(roles).toEqual(["admin", "reviewer"]);
2340
+ * assertEqual(roles, ["admin", "reviewer"]);
2171
2341
  *
2172
2342
  * await using run = createRun();
2173
2343
  * const result = await run(loadUsersByRole);
2174
- * expectTypeOf(result).toEqualTypeOf<
2175
- * Result<{ readonly admin: User; readonly reviewer: User }>
2344
+ * assertType<
2345
+ * Result<{ readonly admin: User; readonly reviewer: User }>,
2346
+ * typeof result
2176
2347
  * >();
2177
- * expectOk(result, {
2348
+ * assertOk(result, {
2178
2349
  * admin: { id: "user-1" },
2179
2350
  * reviewer: { id: "user-2" },
2180
2351
  * });
@@ -2217,6 +2388,9 @@ export type InferTasksSettled<TTasks> = {
2217
2388
  *
2218
2389
  * ```ts
2219
2390
  * import {
2391
+ * assertOk,
2392
+ * assertType,
2393
+ * assertTrue,
2220
2394
  * allSettled,
2221
2395
  * createRun,
2222
2396
  * err,
@@ -2239,20 +2413,21 @@ export type InferTasksSettled<TTasks> = {
2239
2413
  *
2240
2414
  * await using run = createRun();
2241
2415
  * const results = await run(allSettled([loadProfile, loadActivity]));
2242
- * expectTypeOf(results).toEqualTypeOf<
2416
+ * assertType<
2243
2417
  * Result<
2244
2418
  * readonly [
2245
2419
  * Result<string, ProfileNotFoundError>,
2246
2420
  * Result<ReadonlyArray<string>>,
2247
2421
  * ]
2248
- * >
2422
+ * >,
2423
+ * typeof results
2249
2424
  * >();
2250
- * expectOk(results, [
2425
+ * assertOk(results, [
2251
2426
  * { ok: false, error: { type: "ProfileNotFound" } },
2252
2427
  * { ok: true, value: ["signed-in"] },
2253
2428
  * ]);
2254
2429
  * // Unlike all, a later Task still runs after an Err.
2255
- * expect(activityLoaded).toBe(true);
2430
+ * assertTrue(activityLoaded);
2256
2431
  * ```
2257
2432
  *
2258
2433
  * @group Collection
@@ -2265,6 +2440,8 @@ export declare function allSettled<const TTasks extends ReadonlyArray<AnyTask>>(
2265
2440
  *
2266
2441
  * ```ts
2267
2442
  * import {
2443
+ * assertOk,
2444
+ * assertType,
2268
2445
  * allSettled,
2269
2446
  * createRun,
2270
2447
  * err,
@@ -2289,13 +2466,14 @@ export declare function allSettled<const TTasks extends ReadonlyArray<AnyTask>>(
2289
2466
  * allSettled({ user: fetchUser, profile: fetchProfile }),
2290
2467
  * );
2291
2468
  *
2292
- * expectTypeOf(results).toEqualTypeOf<
2469
+ * assertType<
2293
2470
  * Result<{
2294
2471
  * readonly user: Result<User>;
2295
2472
  * readonly profile: Result<string, ProfileNotFoundError>;
2296
- * }>
2473
+ * }>,
2474
+ * typeof results
2297
2475
  * >();
2298
- * expectOk(results, {
2476
+ * assertOk(results, {
2299
2477
  * user: { ok: true, value: { id: "user-1" } },
2300
2478
  * profile: { ok: false, error: { type: "ProfileNotFound" } },
2301
2479
  * });
@@ -2309,6 +2487,9 @@ export declare function allSettled<const TTasks extends TaskRecord>(tasks: TTask
2309
2487
  *
2310
2488
  * ```ts
2311
2489
  * import {
2490
+ * assertEqual,
2491
+ * assertOk,
2492
+ * assertType,
2312
2493
  * allSettled,
2313
2494
  * createRun,
2314
2495
  * err,
@@ -2337,19 +2518,20 @@ export declare function allSettled<const TTasks extends TaskRecord>(tasks: TTask
2337
2518
  * });
2338
2519
  *
2339
2520
  * // Mapping is eager: it happens before the returned Task starts.
2340
- * expect(indexes).toEqual([0, 1]);
2521
+ * assertEqual(indexes, [0, 1]);
2341
2522
  *
2342
2523
  * await using run = createRun();
2343
2524
  * const results = await run(loadUsers);
2344
- * expectTypeOf(results).toEqualTypeOf<
2525
+ * assertType<
2345
2526
  * Result<
2346
2527
  * readonly [
2347
2528
  * Result<User, UserNotFoundError>,
2348
2529
  * Result<User, UserNotFoundError>,
2349
2530
  * ]
2350
- * >
2531
+ * >,
2532
+ * typeof results
2351
2533
  * >();
2352
- * expectOk(results, [
2534
+ * assertOk(results, [
2353
2535
  * { ok: true, value: { id: "user-1" } },
2354
2536
  * { ok: false, error: { type: "UserNotFound" } },
2355
2537
  * ]);
@@ -2365,6 +2547,9 @@ export declare function allSettled<const TValues extends ReadonlyArray<unknown>,
2365
2547
  *
2366
2548
  * ```ts
2367
2549
  * import {
2550
+ * assertEqual,
2551
+ * assertOk,
2552
+ * assertType,
2368
2553
  * allSettled,
2369
2554
  * createRun,
2370
2555
  * err,
@@ -2393,17 +2578,18 @@ export declare function allSettled<const TValues extends ReadonlyArray<unknown>,
2393
2578
  * });
2394
2579
  *
2395
2580
  * // Mapping is eager: it happens before the returned Task starts.
2396
- * expect(roles).toEqual(["admin", "reviewer"]);
2581
+ * assertEqual(roles, ["admin", "reviewer"]);
2397
2582
  *
2398
2583
  * await using run = createRun();
2399
2584
  * const results = await run(loadUsersByRole);
2400
- * expectTypeOf(results).toEqualTypeOf<
2585
+ * assertType<
2401
2586
  * Result<{
2402
2587
  * readonly admin: Result<User, UserNotFoundError>;
2403
2588
  * readonly reviewer: Result<User, UserNotFoundError>;
2404
- * }>
2589
+ * }>,
2590
+ * typeof results
2405
2591
  * >();
2406
- * expectOk(results, {
2592
+ * assertOk(results, {
2407
2593
  * admin: { ok: true, value: { id: "user-1" } },
2408
2594
  * reviewer: { ok: false, error: { type: "UserNotFound" } },
2409
2595
  * });
@@ -2428,7 +2614,7 @@ export declare function allSettled<const TValues extends Readonly<Record<string,
2428
2614
  * This helper is a callback bridge. If `reject` forwards an Error created in a
2429
2615
  * separate async chain, V8 cannot reconstruct the caller's zero-cost async
2430
2616
  * stack through this bridge. Prefer native promise APIs and `await` when the
2431
- * wrapped operation already has a promise-shaped API.
2617
+ * wrapped API already returns a Promise.
2432
2618
  *
2433
2619
  * One-shot settlement applies only to `resolve` and `reject`. A synchronous
2434
2620
  * throw from the setup function is a defect that panics the Run tree even after
@@ -2453,14 +2639,23 @@ export declare function allSettled<const TValues extends Readonly<Record<string,
2453
2639
  * ### Example
2454
2640
  *
2455
2641
  * ```ts
2456
- * import { callback, createRun, ok, type Task } from "@evolu/common";
2642
+ * import {
2643
+ * assertEqual,
2644
+ * assertOk,
2645
+ * callback,
2646
+ * createRun,
2647
+ * ok,
2648
+ * type Task,
2649
+ * } from "@evolu/common";
2457
2650
  *
2458
2651
  * const listeners = new Set<(message: string) => void>();
2459
2652
  * const subscribe = (
2460
2653
  * listener: (message: string) => void,
2461
2654
  * ): (() => void) => {
2462
2655
  * listeners.add(listener);
2463
- * return () => listeners.delete(listener);
2656
+ * return () => {
2657
+ * listeners.delete(listener);
2658
+ * };
2464
2659
  * };
2465
2660
  * const nextMessage: Task<string> = callback(({ resolve }) =>
2466
2661
  * subscribe((message) => resolve(ok(message))),
@@ -2468,11 +2663,11 @@ export declare function allSettled<const TValues extends Readonly<Record<string,
2468
2663
  *
2469
2664
  * await using run = createRun();
2470
2665
  * const fiber = run(nextMessage);
2471
- * expect(listeners.size).toBe(1);
2666
+ * assertEqual(listeners.size, 1);
2472
2667
  * for (const listener of listeners) listener("ready");
2473
- * expectOk(await fiber, "ready");
2668
+ * assertOk(await fiber, "ready");
2474
2669
  * // The callback cleanup unsubscribes after settlement.
2475
- * expect(listeners.size).toBe(0);
2670
+ * assertEqual(listeners.size, 0);
2476
2671
  * ```
2477
2672
  *
2478
2673
  * @group Interop
@@ -2490,10 +2685,10 @@ export declare const callback: <T, E = never, D = unknown>(fn: (options: {
2490
2685
  * ### Example
2491
2686
  *
2492
2687
  * ```ts
2493
- * import { createRun, sleep } from "@evolu/common";
2688
+ * import { assertOk, createRun, sleep } from "@evolu/common";
2494
2689
  *
2495
2690
  * await using run = createRun();
2496
- * expectOk(await run(sleep("1ms")), undefined);
2691
+ * assertOk(await run(sleep("1ms")), undefined);
2497
2692
  * ```
2498
2693
  *
2499
2694
  * @group Timing
@@ -2512,6 +2707,8 @@ export declare const sleep: (duration: PositiveDuration) => Task<void>;
2512
2707
  *
2513
2708
  * ```ts
2514
2709
  * import {
2710
+ * assertErr,
2711
+ * assertType,
2515
2712
  * createRun,
2516
2713
  * timeout,
2517
2714
  * timeoutError,
@@ -2523,8 +2720,8 @@ export declare const sleep: (duration: PositiveDuration) => Task<void>;
2523
2720
  * await using run = createRun();
2524
2721
  *
2525
2722
  * const result = await run(timeout(waitForAbort, "1ms"));
2526
- * expectTypeOf(result).toEqualTypeOf<Result<never, TimeoutError>>();
2527
- * expectErr(result, timeoutError);
2723
+ * assertType<Result<never, TimeoutError>, typeof result>();
2724
+ * assertErr(result, timeoutError);
2528
2725
  * ```
2529
2726
  *
2530
2727
  * @group Timing
@@ -2606,6 +2803,8 @@ export interface RetryAttempt<E, Output> extends ScheduleStep<Output> {
2606
2803
  *
2607
2804
  * ```ts
2608
2805
  * import {
2806
+ * assertErr,
2807
+ * assertType,
2609
2808
  * createRun,
2610
2809
  * err,
2611
2810
  * recurs,
@@ -2625,10 +2824,11 @@ export interface RetryAttempt<E, Output> extends ScheduleStep<Output> {
2625
2824
  *
2626
2825
  * await using run = createRun();
2627
2826
  * const result = await run(fetchWithRetry);
2628
- * expectTypeOf(result).toEqualTypeOf<
2629
- * Result<string, RetryTaskError<ServiceUnavailableError>>
2827
+ * assertType<
2828
+ * Result<string, RetryTaskError<ServiceUnavailableError>>,
2829
+ * typeof result
2630
2830
  * >();
2631
- * expectErr(result, {
2831
+ * assertErr(result, {
2632
2832
  * type: "RetryError",
2633
2833
  * attempts: 3,
2634
2834
  * lastError: { type: "ServiceUnavailable" },
@@ -2639,6 +2839,7 @@ export interface RetryAttempt<E, Output> extends ScheduleStep<Output> {
2639
2839
  *
2640
2840
  * ```ts
2641
2841
  * import {
2842
+ * assertErr,
2642
2843
  * createRun,
2643
2844
  * err,
2644
2845
  * recurs,
@@ -2661,7 +2862,8 @@ export interface RetryAttempt<E, Output> extends ScheduleStep<Output> {
2661
2862
  * });
2662
2863
  *
2663
2864
  * await using run = createRun();
2664
- * expectErr(await run(fetchWithRetry), {
2865
+ * const result = await run(fetchWithRetry);
2866
+ * assertErr(result, {
2665
2867
  * type: "RetryError",
2666
2868
  * attempts: 1,
2667
2869
  * lastError: { type: "PermanentFailure" },
@@ -2741,7 +2943,15 @@ export interface RepeatAttempt<T, Output> extends ScheduleStep<Output> {
2741
2943
  * ### Repeating successes
2742
2944
  *
2743
2945
  * ```ts
2744
- * import { createRun, ok, recurs, repeat, type Task } from "@evolu/common";
2946
+ * import {
2947
+ * assertEqual,
2948
+ * assertOk,
2949
+ * createRun,
2950
+ * ok,
2951
+ * recurs,
2952
+ * repeat,
2953
+ * type Task,
2954
+ * } from "@evolu/common";
2745
2955
  *
2746
2956
  * let attempts = 0;
2747
2957
  * const checkStatus: Task<string> = () => {
@@ -2752,14 +2962,16 @@ export interface RepeatAttempt<T, Output> extends ScheduleStep<Output> {
2752
2962
  * const poll = repeat(checkStatus, recurs(3));
2753
2963
  *
2754
2964
  * await using run = createRun();
2755
- * expectOk(await run(poll), "pending");
2756
- * expect(attempts).toBe(4);
2965
+ * assertOk(await run(poll), "pending");
2966
+ * assertEqual(attempts, 4);
2757
2967
  * ```
2758
2968
  *
2759
2969
  * ### Stopping with Done
2760
2970
  *
2761
2971
  * ```ts
2762
2972
  * import {
2973
+ * assertErr,
2974
+ * assertEqual,
2763
2975
  * createRun,
2764
2976
  * done,
2765
2977
  * err,
@@ -2782,8 +2994,8 @@ export interface RepeatAttempt<T, Output> extends ScheduleStep<Output> {
2782
2994
  *
2783
2995
  * await using run = createRun();
2784
2996
  * const result = await run(repeat(processQueue, spaced("1ms")));
2785
- * expectErr(result, done());
2786
- * expect(queue).toEqual([]);
2997
+ * assertErr(result, done());
2998
+ * assertEqual(queue, []);
2787
2999
  * ```
2788
3000
  *
2789
3001
  * @group Repetition
@@ -2818,6 +3030,9 @@ export type InferTasksResult<TTasks extends NonEmptyReadonlyArray<AnyTask>> = Re
2818
3030
  *
2819
3031
  * ```ts
2820
3032
  * import {
3033
+ * assertOk,
3034
+ * assertType,
3035
+ * assertTrue,
2821
3036
  * any,
2822
3037
  * createRun,
2823
3038
  * err,
@@ -2841,11 +3056,9 @@ export type InferTasksResult<TTasks extends NonEmptyReadonlyArray<AnyTask>> = Re
2841
3056
  * await using run = createRun();
2842
3057
  * const result = await run(any([unavailable, fallback]));
2843
3058
  *
2844
- * expectTypeOf(result).toEqualTypeOf<
2845
- * Result<string, ServiceUnavailableError>
2846
- * >();
2847
- * expectOk(result, "fallback");
2848
- * expect(fallbackStarted).toBe(true);
3059
+ * assertType<Result<string, ServiceUnavailableError>, typeof result>();
3060
+ * assertOk(result, "fallback");
3061
+ * assertTrue(fallbackStarted);
2849
3062
  * ```
2850
3063
  *
2851
3064
  * @group Racing
@@ -2877,6 +3090,7 @@ export declare const any: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tasks:
2877
3090
  *
2878
3091
  * ```ts
2879
3092
  * import {
3093
+ * assertOk,
2880
3094
  * createRun,
2881
3095
  * isNonEmptyArray,
2882
3096
  * ok,
@@ -2888,7 +3102,7 @@ export declare const any: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tasks:
2888
3102
  * await using run = createRun();
2889
3103
  * if (isNonEmptyArray(tasks)) {
2890
3104
  * const result = await run(race(tasks));
2891
- * expectOk(result, "first");
3105
+ * assertOk(result, "first");
2892
3106
  * }
2893
3107
  * ```
2894
3108
  *
@@ -2896,6 +3110,9 @@ export declare const any: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tasks:
2896
3110
  *
2897
3111
  * ```ts
2898
3112
  * import {
3113
+ * assertFalse,
3114
+ * assertOk,
3115
+ * assertType,
2899
3116
  * createRun,
2900
3117
  * ok,
2901
3118
  * race,
@@ -2917,9 +3134,9 @@ export declare const any: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tasks:
2917
3134
  * // Input order does not matter: the first settled Result wins, and the
2918
3135
  * // still-running loser is aborted.
2919
3136
  * const result = await run(race([slow, fast]));
2920
- * expectTypeOf(result).toEqualTypeOf<Result<string>>();
2921
- * expectOk(result, "fast");
2922
- * expect(slowCompleted).toBe(false);
3137
+ * assertType<Result<string>, typeof result>();
3138
+ * assertOk(result, "fast");
3139
+ * assertFalse(slowCompleted);
2923
3140
  * ```
2924
3141
  *
2925
3142
  * @group Racing
@@ -2941,6 +3158,8 @@ export declare const race: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tasks
2941
3158
  *
2942
3159
  * ```ts
2943
3160
  * import {
3161
+ * assertFalse,
3162
+ * assertOk,
2944
3163
  * createRun,
2945
3164
  * err,
2946
3165
  * firstN,
@@ -2971,8 +3190,8 @@ export declare const race: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tasks
2971
3190
  *
2972
3191
  * // Errs do not count. After two Ok values settle, the slow Task is aborted.
2973
3192
  * const result = await run(firstN(tasks, 2, { concurrency: 4 }));
2974
- * expectOk(result, ["fast-1", "fast-2"]);
2975
- * expect(slowCompleted).toBe(false);
3193
+ * assertOk(result, ["fast-1", "fast-2"]);
3194
+ * assertFalse(slowCompleted);
2976
3195
  * ```
2977
3196
  *
2978
3197
  * @group Racing
@@ -2994,6 +3213,8 @@ export declare const firstN: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tas
2994
3213
  *
2995
3214
  * ```ts
2996
3215
  * import {
3216
+ * assertFalse,
3217
+ * assertOk,
2997
3218
  * createRun,
2998
3219
  * err,
2999
3220
  * firstNSettled,
@@ -3019,11 +3240,11 @@ export declare const firstN: <TTasks extends NonEmptyReadonlyArray<AnyTask>>(tas
3019
3240
  *
3020
3241
  * // Err and Ok both count, and Results use settlement order.
3021
3242
  * const result = await run(firstNSettled(tasks, 2, { concurrency: 3 }));
3022
- * expectOk(result, [
3243
+ * assertOk(result, [
3023
3244
  * { ok: false, error: { type: "ServiceUnavailable" } },
3024
3245
  * { ok: true, value: "fast" },
3025
3246
  * ]);
3026
- * expect(slowCompleted).toBe(false);
3247
+ * assertFalse(slowCompleted);
3027
3248
  * ```
3028
3249
  *
3029
3250
  * @group Racing
@@ -3079,6 +3300,9 @@ export type EachCallback<TTasks extends NonEmptyReadonlyArray<AnyTask>> = (resul
3079
3300
  *
3080
3301
  * ```ts
3081
3302
  * import {
3303
+ * assertEqual,
3304
+ * assertFalse,
3305
+ * assertOk,
3082
3306
  * createRun,
3083
3307
  * each,
3084
3308
  * err,
@@ -3114,9 +3338,9 @@ export type EachCallback<TTasks extends NonEmptyReadonlyArray<AnyTask>> = (resul
3114
3338
  * ),
3115
3339
  * );
3116
3340
  *
3117
- * expectOk(result, undefined);
3118
- * expect(first).toEqual(["fast", 2]);
3119
- * expect(slowCompleted).toBe(false);
3341
+ * assertOk(result, undefined);
3342
+ * assertEqual(first, ["fast", 2]);
3343
+ * assertFalse(slowCompleted);
3120
3344
  * ```
3121
3345
  *
3122
3346
  * `onResult` is a synchronous scheduling decision, not a place to do work. It
@@ -3160,13 +3384,19 @@ export type TaskPriority = "user-blocking" | "user-visible" | "background";
3160
3384
  * ### Example
3161
3385
  *
3162
3386
  * ```ts
3163
- * import { createRun, ok, prioritized, type Task } from "@evolu/common";
3387
+ * import {
3388
+ * assertOk,
3389
+ * createRun,
3390
+ * ok,
3391
+ * prioritized,
3392
+ * type Task,
3393
+ * } from "@evolu/common";
3164
3394
  *
3165
3395
  * const rebuildSearchIndex: Task<string> = () => ok("indexed");
3166
3396
  * const backgroundIndexing = prioritized("background", rebuildSearchIndex);
3167
3397
  *
3168
3398
  * await using run = createRun();
3169
- * expectOk(await run(backgroundIndexing), "indexed");
3399
+ * assertOk(await run(backgroundIndexing), "indexed");
3170
3400
  * ```
3171
3401
  *
3172
3402
  * @group Scheduling
@@ -3186,7 +3416,13 @@ export declare const prioritized: <T, E, D = unknown>(priority: TaskPriority, ta
3186
3416
  * ### Example
3187
3417
  *
3188
3418
  * ```ts
3189
- * import { createRun, ok, yieldNow, type Task } from "@evolu/common";
3419
+ * import {
3420
+ * assertOk,
3421
+ * createRun,
3422
+ * ok,
3423
+ * yieldNow,
3424
+ * type Task,
3425
+ * } from "@evolu/common";
3190
3426
  *
3191
3427
  * const sumTo =
3192
3428
  * (count: number): Task<number> =>
@@ -3202,7 +3438,7 @@ export declare const prioritized: <T, E, D = unknown>(priority: TaskPriority, ta
3202
3438
  * };
3203
3439
  *
3204
3440
  * await using run = createRun();
3205
- * expectOk(await run(sumTo(1001)), 500500);
3441
+ * assertOk(await run(sumTo(1001)), 500500);
3206
3442
  * ```
3207
3443
  *
3208
3444
  * @group Scheduling
@@ -3219,6 +3455,10 @@ export declare const yieldNow: Task<void>;
3219
3455
  *
3220
3456
  * ```ts
3221
3457
  * import {
3458
+ * assertEqual,
3459
+ * assertFalse,
3460
+ * assertType,
3461
+ * assertTrue,
3222
3462
  * AbortError,
3223
3463
  * createRun,
3224
3464
  * ok,
@@ -3235,23 +3475,22 @@ export declare const yieldNow: Task<void>;
3235
3475
  * const serverStarted = Promise.withResolvers<void>();
3236
3476
  * let serverStopped = false;
3237
3477
  * const startServer: Task<Server, never, ServerDep> = (run) => {
3238
- * expect(run.deps.port).toBe(3000);
3478
+ * assertEqual(run.deps.port, 3000);
3239
3479
  * serverStarted.resolve();
3240
3480
  * return ok({
3241
- * [Symbol.asyncDispose]: async () => {
3481
+ * [Symbol.asyncDispose]: () => {
3242
3482
  * serverStopped = true;
3483
+ * return Promise.resolve();
3243
3484
  * },
3244
3485
  * });
3245
3486
  * };
3246
3487
  *
3247
3488
  * const serve = (): Task<never, never, ServerDep> => async (run) => {
3248
- * await using server = await run.ok(startServer);
3489
+ * await using _ = await run.ok(startServer);
3249
3490
  * return await run(waitForAbort);
3250
3491
  * };
3251
3492
  *
3252
- * expectTypeOf(serve).returns.toEqualTypeOf<
3253
- * Task<never, never, ServerDep>
3254
- * >();
3493
+ * assertType<Task<never, never, ServerDep>, ReturnType<typeof serve>>();
3255
3494
  *
3256
3495
  * await using run = createRun();
3257
3496
  * const fiber = run.abortable(serve(), { port: 3000 });
@@ -3259,9 +3498,9 @@ export declare const yieldNow: Task<void>;
3259
3498
  * fiber.abort();
3260
3499
  *
3261
3500
  * const result = await fiber;
3262
- * assert(!result.ok);
3263
- * expect(AbortError.is(result.error)).toBe(true);
3264
- * expect(serverStopped).toBe(true);
3501
+ * assertFalse(result.ok);
3502
+ * assertTrue(AbortError.is(result.error));
3503
+ * assertTrue(serverStopped);
3265
3504
  * ```
3266
3505
  *
3267
3506
  * @group Abortability
@@ -3277,8 +3516,8 @@ export declare const waitForAbort: Task<never>;
3277
3516
  * execution. The daemon Task continues under root Run ownership until it
3278
3517
  * settles, observes abort, or the root Run is disposed.
3279
3518
  *
3280
- * This is not a replacement for direct {@link AbortSignal} support in operations
3281
- * that can observe abort, such as {@link fetch}, timers that accept a signal, or
3519
+ * This is not a replacement for direct {@link AbortSignal} support in APIs that
3520
+ * can observe abort, such as {@link fetch}, timers that accept a signal, or
3282
3521
  * callback APIs that accept a signal. Use it as an escape hatch for Tasks that
3283
3522
  * ignore abort when an abort request must stop waiting immediately.
3284
3523
  *
@@ -3311,6 +3550,9 @@ export declare const waitForAbort: Task<never>;
3311
3550
  *
3312
3551
  * ```ts
3313
3552
  * import {
3553
+ * assertEqual,
3554
+ * assertFalse,
3555
+ * assertTrue,
3314
3556
  * createRun,
3315
3557
  * daemon,
3316
3558
  * ok,
@@ -3331,18 +3573,24 @@ export declare const waitForAbort: Task<never>;
3331
3573
  * {
3332
3574
  * await using run = createRun();
3333
3575
  * const result = await run(timeout(daemon(taskNotUsingAbort), "1ms"));
3334
- * assert(!result.ok);
3335
- * expect(result.error.type).toBe("TimeoutError");
3336
- * expect(finished).toBe(false);
3576
+ * assertFalse(result.ok);
3577
+ * assertEqual(result.error.type, "TimeoutError");
3578
+ * assertFalse(finished);
3337
3579
  * finishTask();
3338
3580
  * }
3339
- * expect(finished).toBe(true);
3581
+ * assertTrue(finished);
3340
3582
  * ```
3341
3583
  *
3342
- * Promise-producing operations should start inside the Task, not before it.
3584
+ * Promise-returning functions should be called inside the Task, not before it.
3343
3585
  *
3344
3586
  * ```ts
3345
- * import { createRun, ok, type Result, type Task } from "@evolu/common";
3587
+ * import {
3588
+ * assertOk,
3589
+ * createRun,
3590
+ * ok,
3591
+ * type Result,
3592
+ * type Task,
3593
+ * } from "@evolu/common";
3346
3594
  *
3347
3595
  * type ResultValue = string;
3348
3596
  * const createPromiseReturningResult = (): Promise<Result<ResultValue>> =>
@@ -3351,14 +3599,20 @@ export declare const waitForAbort: Task<never>;
3351
3599
  * const task: Task<ResultValue> = () => createPromiseReturningResult();
3352
3600
  *
3353
3601
  * await using run = createRun();
3354
- * expectOk(await run(task), "value");
3602
+ * assertOk(await run(task), "value");
3355
3603
  * ```
3356
3604
  *
3357
3605
  * Do not reuse an already-running Promise. It started outside the Task, so the
3358
3606
  * Run cannot own its lifetime or request abort before it begins.
3359
3607
  *
3360
3608
  * ```ts
3361
- * import { ok, type Result, type Task } from "@evolu/common";
3609
+ * import {
3610
+ * assertType,
3611
+ * assertTrue,
3612
+ * ok,
3613
+ * type Result,
3614
+ * type Task,
3615
+ * } from "@evolu/common";
3362
3616
  *
3363
3617
  * type ResultValue = string;
3364
3618
  * let promiseStarted = false;
@@ -3373,8 +3627,8 @@ export declare const waitForAbort: Task<never>;
3373
3627
  * const promise = createPromiseReturningResult();
3374
3628
  * const task: Task<ResultValue> = () => promise;
3375
3629
  *
3376
- * expect(promiseStarted).toBe(true);
3377
- * expectTypeOf(task).toEqualTypeOf<Task<ResultValue>>();
3630
+ * assertTrue(promiseStarted);
3631
+ * assertType<Task<ResultValue>, typeof task>();
3378
3632
  * ```
3379
3633
  *
3380
3634
  * @group Lifetime
@@ -3395,14 +3649,21 @@ export declare const daemon: <T, E, D = unknown>(task: Task<T, E, D>) => Task<T,
3395
3649
  * ### Example
3396
3650
  *
3397
3651
  * ```ts
3398
- * import { createRun, ok, unabortable, type Task } from "@evolu/common";
3652
+ * import {
3653
+ * assertFalse,
3654
+ * assertOk,
3655
+ * createRun,
3656
+ * ok,
3657
+ * unabortable,
3658
+ * type Task,
3659
+ * } from "@evolu/common";
3399
3660
  *
3400
3661
  * const commitStarted = Promise.withResolvers<void>();
3401
3662
  * const finishCommit = Promise.withResolvers<void>();
3402
3663
  * const commit: Task<string> = unabortable(async (run) => {
3403
3664
  * commitStarted.resolve();
3404
3665
  * await finishCommit.promise;
3405
- * expect(run.signal.aborted).toBe(false);
3666
+ * assertFalse(run.signal.aborted);
3406
3667
  * return ok("committed");
3407
3668
  * });
3408
3669
  *
@@ -3412,7 +3673,7 @@ export declare const daemon: <T, E, D = unknown>(task: Task<T, E, D>) => Task<T,
3412
3673
  * fiber.abort();
3413
3674
  * finishCommit.resolve();
3414
3675
  *
3415
- * expectOk(await fiber, "committed");
3676
+ * assertOk(await fiber, "committed");
3416
3677
  * ```
3417
3678
  *
3418
3679
  * @group Abortability
@@ -3428,7 +3689,7 @@ export declare const unabortable: <T, E, D>(task: Task<T, E, D>) => Task<T, E, D
3428
3689
  *
3429
3690
  * An abort request before the mask Task starts prevents entering the mask. Once
3430
3691
  * the body starts, plain child Tasks inherit the mask, so acquire and release
3431
- * can run after abort. Put release operations directly in the original mask's
3692
+ * can run after abort. Start release Tasks directly in the original mask's
3432
3693
  * `finally`; do not wrap release in a nested `unabortableMask`, which is a new
3433
3694
  * critical-section entry and may not start after abort.
3434
3695
  *
@@ -3440,6 +3701,9 @@ export declare const unabortable: <T, E, D>(task: Task<T, E, D>) => Task<T, E, D
3440
3701
  *
3441
3702
  * ```ts
3442
3703
  * import {
3704
+ * assertEqual,
3705
+ * assertFalse,
3706
+ * assertTrue,
3443
3707
  * AbortError,
3444
3708
  * createRun,
3445
3709
  * ok,
@@ -3456,17 +3720,17 @@ export declare const unabortable: <T, E, D>(task: Task<T, E, D>) => Task<T, E, D
3456
3720
  * const operationStarted = Promise.withResolvers<void>();
3457
3721
  * const operate =
3458
3722
  * (resource: Resource): Task<never> =>
3459
- * async (run) => {
3460
- * expect(resource.id).toBe("resource-1");
3723
+ * (run) => {
3724
+ * assertEqual(resource.id, "resource-1");
3461
3725
  * operationStarted.resolve();
3462
- * return await run(waitForAbort);
3726
+ * return run(waitForAbort);
3463
3727
  * };
3464
3728
  * let released = false;
3465
3729
  * const release =
3466
3730
  * (_resource: Resource): Task<void> =>
3467
3731
  * (run) => {
3468
3732
  * // Release inherits the mask even after abort was requested.
3469
- * expect(run.signal.aborted).toBe(false);
3733
+ * assertFalse(run.signal.aborted);
3470
3734
  * released = true;
3471
3735
  * return ok();
3472
3736
  * };
@@ -3490,9 +3754,9 @@ export declare const unabortable: <T, E, D>(task: Task<T, E, D>) => Task<T, E, D
3490
3754
  * await operationStarted.promise;
3491
3755
  * fiber.abort();
3492
3756
  * const result = await fiber;
3493
- * assert(!result.ok);
3494
- * expect(AbortError.is(result.error)).toBe(true);
3495
- * expect(released).toBe(true);
3757
+ * assertFalse(result.ok);
3758
+ * assertTrue(AbortError.is(result.error));
3759
+ * assertTrue(released);
3496
3760
  * ```
3497
3761
  *
3498
3762
  * @group Abortability
@@ -3512,13 +3776,15 @@ export declare const unabortableMask: <T, E, D = unknown>(fn: (restore: <T2, E2,
3512
3776
  * Prefer native `using`, `await using`, or {@link AsyncDisposableStack} for
3513
3777
  * owned values that implement {@link Disposable} or {@link AsyncDisposable}. Use
3514
3778
  * `acquireUseRelease` when acquisition must be balanced with a separate release
3515
- * operation, such as unlocking, returning a pooled value, releasing a lease, or
3779
+ * step, such as unlocking, returning a pooled value, releasing a lease, or
3516
3780
  * logging out of a session.
3517
3781
  *
3518
3782
  * ### Example
3519
3783
  *
3520
3784
  * ```ts
3521
3785
  * import {
3786
+ * assertErr,
3787
+ * assertTrue,
3522
3788
  * acquireUseRelease,
3523
3789
  * createRun,
3524
3790
  * err,
@@ -3558,9 +3824,9 @@ export declare const unabortableMask: <T, E, D = unknown>(fn: (restore: <T2, E2,
3558
3824
  * );
3559
3825
  *
3560
3826
  * await using run = createRun();
3561
- * expectErr(await run(queryUser), { type: "UserUnavailable" });
3827
+ * assertErr(await run(queryUser), { type: "UserUnavailable" });
3562
3828
  * // Release still runs when use returns a domain error.
3563
- * expect(connectionClosed).toBe(true);
3829
+ * assertTrue(connectionClosed);
3564
3830
  * ```
3565
3831
  *
3566
3832
  * @group Lifetime
@@ -3594,6 +3860,10 @@ export interface Deferred<T, E = never> {
3594
3860
  *
3595
3861
  * ```ts
3596
3862
  * import {
3863
+ * assertFalse,
3864
+ * assertOk,
3865
+ * assertType,
3866
+ * assertTrue,
3597
3867
  * createDeferred,
3598
3868
  * createRun,
3599
3869
  * ok,
@@ -3604,22 +3874,28 @@ export interface Deferred<T, E = never> {
3604
3874
  * const deferred = createDeferred<string>();
3605
3875
  *
3606
3876
  * const fiber = run(deferred.task);
3607
- * expect(deferred.resolve(ok("ready"))).toBe(true);
3877
+ * assertTrue(deferred.resolve(ok("ready")));
3608
3878
  *
3609
3879
  * const result = await fiber;
3610
- * expectTypeOf(result).toEqualTypeOf<Result<string>>();
3611
- * expectOk(result, "ready");
3880
+ * assertType<Result<string>, typeof result>();
3881
+ * assertOk(result, "ready");
3612
3882
  *
3613
3883
  * // A Deferred is one-shot: later resolutions are ignored, and future
3614
3884
  * // waiters receive the original Result.
3615
- * expect(deferred.resolve(ok("late"))).toBe(false);
3616
- * expectOk(await run(deferred.task), "ready");
3885
+ * assertFalse(deferred.resolve(ok("late")));
3886
+ * assertOk(await run(deferred.task), "ready");
3617
3887
  * ```
3618
3888
  *
3619
3889
  * ### Aborting a waiter
3620
3890
  *
3621
3891
  * ```ts
3622
- * import { AbortError, createDeferred, createRun } from "@evolu/common";
3892
+ * import {
3893
+ * assertFalse,
3894
+ * assertTrue,
3895
+ * AbortError,
3896
+ * createDeferred,
3897
+ * createRun,
3898
+ * } from "@evolu/common";
3623
3899
  *
3624
3900
  * await using run = createRun();
3625
3901
  * const deferred = createDeferred<string>();
@@ -3628,8 +3904,8 @@ export interface Deferred<T, E = never> {
3628
3904
  * fiber.abort({ type: "NoLongerNeeded" });
3629
3905
  *
3630
3906
  * const result = await fiber;
3631
- * assert(!result.ok);
3632
- * expect(AbortError.is(result.error)).toBe(true);
3907
+ * assertFalse(result.ok);
3908
+ * assertTrue(AbortError.is(result.error));
3633
3909
  * ```
3634
3910
  *
3635
3911
  * @group Concurrency primitives
@@ -3671,7 +3947,15 @@ export interface Gate {
3671
3947
  * ### Example
3672
3948
  *
3673
3949
  * ```ts
3674
- * import { createGate, createRun, ok, type Task } from "@evolu/common";
3950
+ * import {
3951
+ * assertEqual,
3952
+ * assertOk,
3953
+ * assertTrue,
3954
+ * createGate,
3955
+ * createRun,
3956
+ * ok,
3957
+ * type Task,
3958
+ * } from "@evolu/common";
3675
3959
  *
3676
3960
  * await using run = createRun();
3677
3961
  * const networkGate = createGate();
@@ -3687,13 +3971,13 @@ export interface Gate {
3687
3971
  *
3688
3972
  * const first = run(syncOnce("first"));
3689
3973
  * const second = run(syncOnce("second"));
3690
- * expect(uploadedItems).toEqual([]);
3974
+ * assertEqual(uploadedItems, []);
3691
3975
  *
3692
3976
  * networkGate.open();
3693
- * expectOk(await first, undefined);
3694
- * expectOk(await second, undefined);
3695
- * expect(uploadedItems).toEqual(["first", "second"]);
3696
- * expect(networkGate.isOpen()).toBe(true);
3977
+ * assertOk(await first, undefined);
3978
+ * assertOk(await second, undefined);
3979
+ * assertEqual(uploadedItems, ["first", "second"]);
3980
+ * assertTrue(networkGate.isOpen());
3697
3981
  * ```
3698
3982
  *
3699
3983
  * @group Concurrency primitives
@@ -3706,8 +3990,8 @@ export declare const createGate: ({ isOpen, }?: {
3706
3990
  *
3707
3991
  * Use {@link Semaphore.withPermit} or {@link Semaphore.withPermits} to acquire
3708
3992
  * permits for one Task and release them when it settles. Use
3709
- * {@link Semaphore.take} when permits must be held across multiple operations;
3710
- * the returned {@link SemaphorePermit} owns release and is disposable.
3993
+ * {@link Semaphore.take} when one permit must cover several child Tasks; the
3994
+ * returned {@link SemaphorePermit} owns release and is disposable.
3711
3995
  *
3712
3996
  * Requests are not capped by the current permit count because
3713
3997
  * {@link Semaphore.resize} can increase it later.
@@ -3796,8 +4080,8 @@ export interface SemaphorePermit extends Disposable {
3796
4080
  * semaphore does not reorder requests to maximize utilization.
3797
4081
  *
3798
4082
  * Use `"fifo"` when fairness and predictable progress matter, such as tenant
3799
- * sync, API quota, or database operations where large requests must not be
3800
- * starved by a stream of smaller requests.
4083
+ * sync, API quota, or database pools where large requests must not be starved
4084
+ * by a stream of smaller requests.
3801
4085
  *
3802
4086
  * Use `"greedy"` when permits represent a shared budget and smaller or
3803
4087
  * latency-sensitive requests should proceed around larger queued requests. For
@@ -3845,6 +4129,7 @@ export interface SemaphoreSnapshot {
3845
4129
  *
3846
4130
  * ```ts
3847
4131
  * import {
4132
+ * assertEqual,
3848
4133
  * createRun,
3849
4134
  * createSemaphore,
3850
4135
  * getOk,
@@ -3876,8 +4161,8 @@ export interface SemaphoreSnapshot {
3876
4161
  * ]);
3877
4162
  *
3878
4163
  * const savedUsers = results.map(getOk);
3879
- * expect(savedUsers).toEqual(["saved:1", "saved:2", "saved:3"]);
3880
- * expect(maxActiveSaves).toBe(2);
4164
+ * assertEqual(savedUsers, ["saved:1", "saved:2", "saved:3"]);
4165
+ * assertEqual(maxActiveSaves, 2);
3881
4166
  * ```
3882
4167
  *
3883
4168
  * @group Concurrency primitives
@@ -3911,6 +4196,8 @@ export interface Mutex {
3911
4196
  *
3912
4197
  * ```ts
3913
4198
  * import {
4199
+ * assertEqual,
4200
+ * assertOk,
3914
4201
  * createMutex,
3915
4202
  * createRun,
3916
4203
  * ok,
@@ -3933,9 +4220,9 @@ export interface Mutex {
3933
4220
  * run(deposit(2)),
3934
4221
  * run(deposit(3)),
3935
4222
  * ]);
3936
- * expectOk(first, undefined);
3937
- * expectOk(second, undefined);
3938
- * expect(balance).toBe(5);
4223
+ * assertOk(first, undefined);
4224
+ * assertOk(second, undefined);
4225
+ * assertEqual(balance, 5);
3939
4226
  * ```
3940
4227
  *
3941
4228
  * @group Concurrency primitives
@@ -3950,8 +4237,8 @@ export declare const createMutex: () => Mutex;
3950
4237
  * resources, making idle-key cleanup less predictable and making accidental key
3951
4238
  * retention easier.
3952
4239
  *
3953
- * Use Semaphore directly when callers need to hold permits across multiple
3954
- * operations or resize a permit pool. Use `SemaphoreByKey` when permit
4240
+ * Use Semaphore directly when callers need to hold permits while starting
4241
+ * several child Tasks or resize a permit pool. Use `SemaphoreByKey` when permit
3955
4242
  * ownership should be tied to one Task lifetime and idle keys can be forgotten
3956
4243
  * automatically.
3957
4244
  *
@@ -3981,6 +4268,8 @@ export interface CreateSemaphoreByKeyOptions<K, L = K> extends LookupOption<K, L
3981
4268
  *
3982
4269
  * ```ts
3983
4270
  * import {
4271
+ * assertOk,
4272
+ * assertTrue,
3984
4273
  * createRun,
3985
4274
  * createSemaphoreByKey,
3986
4275
  * ok,
@@ -3993,11 +4282,11 @@ export interface CreateSemaphoreByKeyOptions<K, L = K> extends LookupOption<K, L
3993
4282
  * downloadsByHost.withPermit(host, () => ok(`${host}/${file}`));
3994
4283
  *
3995
4284
  * await using run = createRun();
3996
- * expectOk(
4285
+ * assertOk(
3997
4286
  * await run(download("a.example", "index.json")),
3998
4287
  * "a.example/index.json",
3999
4288
  * );
4000
- * expect(downloadsByHost.isIdle("a.example")).toBe(true);
4289
+ * assertTrue(downloadsByHost.isIdle("a.example"));
4001
4290
  * ```
4002
4291
  *
4003
4292
  * @group Concurrency primitives
@@ -4033,6 +4322,8 @@ export interface CreateMutexByKeyOptions<K, L = K> extends LookupOption<K, L> {
4033
4322
  *
4034
4323
  * ```ts
4035
4324
  * import {
4325
+ * assertOk,
4326
+ * assertTrue,
4036
4327
  * createMutexByKey,
4037
4328
  * createRun,
4038
4329
  * ok,
@@ -4049,9 +4340,9 @@ export interface CreateMutexByKeyOptions<K, L = K> extends LookupOption<K, L> {
4049
4340
  * });
4050
4341
  *
4051
4342
  * await using run = createRun();
4052
- * expectOk(await run(deposit("checking", 2)), 2);
4053
- * expectOk(await run(deposit("checking", 3)), 5);
4054
- * expect(accountLocks.isIdle("checking")).toBe(true);
4343
+ * assertOk(await run(deposit("checking", 2)), 2);
4344
+ * assertOk(await run(deposit("checking", 3)), 5);
4345
+ * assertTrue(accountLocks.isIdle("checking"));
4055
4346
  * ```
4056
4347
  *
4057
4348
  * @group Concurrency primitives
@@ -4062,9 +4353,9 @@ export declare function createMutexByKey<K, L>(options: CreateMutexByKeyOptions<
4062
4353
  /**
4063
4354
  * {@link Ref} protected by a {@link Mutex}.
4064
4355
  *
4065
- * `MutexRef` serializes reads, writes, and updates through an internal Mutex,
4066
- * so every operation observes one consistent value transition at a time. When
4067
- * an update fails or is aborted, the previous value is preserved.
4356
+ * `MutexRef` serializes reads, writes, and updates through an internal Mutex.
4357
+ * Reads see a stable value, while writes and updates commit one transition at a
4358
+ * time. When an update fails or is aborted, the previous value is preserved.
4068
4359
  *
4069
4360
  * `MutexRef` is non-reentrant. Updaters and modifiers run while holding the
4070
4361
  * internal Mutex, so calling another method on the same MutexRef from inside
@@ -4074,8 +4365,8 @@ export declare function createMutexByKey<K, L>(options: CreateMutexByKeyOptions<
4074
4365
  * read-modify-write. Plain Ref cannot express that — between a sync read and a
4075
4366
  * later write, a concurrent transition can interleave and get lost.
4076
4367
  *
4077
- * `MutexRef` operations are Tasks and incur normal {@link Run} lifecycle
4078
- * overhead. Use {@link Ref} instead for synchronous state transitions,
4368
+ * `MutexRef` reads, writes, and updates are Tasks and incur normal {@link Run}
4369
+ * lifecycle overhead. Use {@link Ref} instead for synchronous state transitions,
4079
4370
  * especially on allocation-sensitive hot paths.
4080
4371
  *
4081
4372
  * @group Concurrency primitives
@@ -4107,14 +4398,14 @@ export interface MutexRef<T> {
4107
4398
  * ### Example
4108
4399
  *
4109
4400
  * ```ts
4110
- * import { createMutexRef, createRun, ok } from "@evolu/common";
4401
+ * import { assertOk, createMutexRef, createRun, ok } from "@evolu/common";
4111
4402
  *
4112
4403
  * const counter = createMutexRef(0);
4113
4404
  * const increment = counter.updateAndGet((value) => () => ok(value + 1));
4114
4405
  *
4115
4406
  * await using run = createRun();
4116
- * expectOk(await run(increment), 1);
4117
- * expectOk(await run(counter.get), 1);
4407
+ * assertOk(await run(increment), 1);
4408
+ * assertOk(await run(counter.get), 1);
4118
4409
  * ```
4119
4410
  *
4120
4411
  * @group Concurrency primitives