@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
package/dist/src/Type.js CHANGED
@@ -1,6 +1,4 @@
1
1
  /**
2
- * ## Intro
3
- *
4
2
  * Runtime validation with precise TypeScript types and structured errors.
5
3
  *
6
4
  * Evolu {@link Type} is a pure, synchronous codec for defining semantic domains.
@@ -49,6 +47,11 @@
49
47
  *
50
48
  * ```ts
51
49
  * import {
50
+ * assertEqual,
51
+ * Data,
52
+ * assertErr,
53
+ * assertOk,
54
+ * assertType,
52
55
  * Number,
53
56
  * NonEmptyTrimmedString100,
54
57
  * brand,
@@ -73,14 +76,15 @@
73
76
  * const Age = brand("Age", lessThan(200)(NonNegativeInt));
74
77
  * type Age = typeof Age.Output;
75
78
  *
76
- * expectTypeOf<Age>().toEqualTypeOf<
79
+ * assertType<
77
80
  * number &
78
81
  * Brand<"NonNaN"> &
79
82
  * Brand<"Finite"> &
80
83
  * Brand<"Int"> &
81
84
  * Brand<"NonNegative"> &
82
85
  * Brand<"LessThan200"> &
83
- * Brand<"Age">
86
+ * Brand<"Age">,
87
+ * Age
84
88
  * >();
85
89
  *
86
90
  * const User = object({
@@ -92,12 +96,16 @@
92
96
  * const value: unknown = { name: "Ada", age: 37 };
93
97
  * const user = User.fromUnknown(value);
94
98
  *
95
- * expectOk(user, { name: "Ada", age: 37 });
96
- * expectTypeOf(user.value).toExtend<User>();
99
+ * assertOk(user, { name: "Ada", age: 37 });
100
+ * assertType<typeof User.Output, typeof user.value>();
97
101
  *
98
102
  * const invalidUser = User.fromUnknown({ name: "Ada", age: 37.5 });
99
103
  *
100
- * expectErr(invalidUser, {
104
+ * assertErr(invalidUser);
105
+ * // InferErrors includes every structured error User.fromUnknown can return.
106
+ * assertType<InferErrors<typeof User>, typeof invalidUser.error>();
107
+ * assertType(Data, invalidUser.error);
108
+ * assertEqual(invalidUser.error, {
101
109
  * type: "Object",
102
110
  * reason: {
103
111
  * kind: "Properties",
@@ -106,22 +114,26 @@
106
114
  * },
107
115
  * },
108
116
  * });
109
- *
110
- * // InferErrors includes every structured error User.fromUnknown can return.
111
- * expectTypeOf(invalidUser.error).toEqualTypeOf<
112
- * InferErrors<typeof User>
113
- * >();
114
117
  * ```
115
118
  *
116
119
  * A Type can format its structured errors into user-facing messages:
117
120
  *
118
121
  * ```ts
119
- * import { Age } from "@evolu/common";
122
+ * import {
123
+ * assertEqual,
124
+ * assertType,
125
+ * Data,
126
+ * assertErr,
127
+ * Age,
128
+ * } from "@evolu/common";
120
129
  *
121
130
  * const age = Age.fromUnknown(37.5);
122
131
  *
123
- * expectErr(age, { type: "Int", value: 37.5 });
124
- * expect(Age.formatError(age.error)).toBe(
132
+ * assertErr(age);
133
+ * assertType(Data, age.error);
134
+ * assertEqual(age.error, { type: "Int", value: 37.5 });
135
+ * assertEqual(
136
+ * Age.formatError(age.error),
125
137
  * "The value 37.5 must be a safe integer.",
126
138
  * );
127
139
  * ```
@@ -138,6 +150,8 @@
138
150
  *
139
151
  * ```ts
140
152
  * import {
153
+ * assertOk,
154
+ * assertType,
141
155
  * NonEmptyTrimmedString100,
142
156
  * NonEmptyTrimmedString1000,
143
157
  * object,
@@ -152,7 +166,7 @@
152
166
  *
153
167
  * // This is type-checked: Todo.from expects NonEmptyTrimmedString100.
154
168
  * const title = NonEmptyTrimmedString100.orThrow("Buy milk");
155
- * expectOk(Todo.from({ title }), { title });
169
+ * assertOk(Todo.from({ title }), { title });
156
170
  *
157
171
  * // Imagine the UI input component is changed to allow longer titles.
158
172
  * // TypeScript rejects the mismatch, so users never see a save error
@@ -170,13 +184,14 @@
170
184
  * );
171
185
  *
172
186
  * // No "not a string" or "not trimmed" errors: the input guarantees both.
173
- * expectTypeOf(validatedTitle).toEqualTypeOf<
187
+ * assertType<
174
188
  * Result<
175
189
  * NonEmptyTrimmedString100,
176
190
  * MaxLengthError<100> | MinLengthError<1>
177
- * >
191
+ * >,
192
+ * typeof validatedTitle
178
193
  * >();
179
- * expectOk(validatedTitle, "Buy milk");
194
+ * assertOk(validatedTitle, "Buy milk");
180
195
  * ```
181
196
  *
182
197
  * Evolu includes dozens of predefined Types and Type factories. Use Types such
@@ -203,6 +218,10 @@
203
218
  * cannot express. They are correctness checks, not defenses against malicious
204
219
  * code.
205
220
  *
221
+ * Evolu does not support subclassing native JavaScript objects. Such subclasses
222
+ * can be classified as their reported built-in representation, but their
223
+ * behavior is unspecified.
224
+ *
206
225
  * ## FAQ
207
226
  *
208
227
  * ### What does a Type represent?
@@ -378,16 +397,19 @@
378
397
  * unknown values, {@link object} defines a closed plain-object representation,
379
398
  * and {@link record} defines a plain-dictionary representation whose complete
380
399
  * set of own properties are its entries. Their realm-neutral plain-object rule
381
- * accepts a `null` prototype or an immediate root prototype whose own prototype
382
- * is `null`; ordinary class instances and deeper prototype chains are rejected.
383
- * Every property must be an enumerable data property; inherited members are not
384
- * entries, while accessors and hidden properties are invalid instead of being
385
- * invoked or ignored. {@link array} similarly defines a dense sequence whose
386
- * only own properties are `length` and its indexed data properties;
387
- * {@link tuple} applies the same representation rules with a fixed length and a
388
- * distinct Type for each position. Only an explicit {@link transform} changes
389
- * the representation. Consequently, `is` tests exact Output membership and `to`
390
- * stays total for valid Outputs.
400
+ * uses {@link isPlainObject}: it accepts a `null` prototype or an immediate root
401
+ * prototype with own `hasOwnProperty` and `isPrototypeOf` properties. A custom
402
+ * root prototype with the same shape can therefore be classified as plain;
403
+ * other custom prototypes and class instances are rejected. This heuristic
404
+ * assumes trusted JavaScript and is not a security boundary. Every property
405
+ * must be an enumerable data property; inherited members are not entries, while
406
+ * accessors and hidden properties are invalid instead of being invoked or
407
+ * ignored. {@link array} similarly defines a dense sequence whose only own
408
+ * properties are `length` and its indexed data properties; {@link tuple} applies
409
+ * the same representation rules with a fixed length and a distinct Type for
410
+ * each position. Only an explicit {@link transform} changes the representation.
411
+ * Consequently, `is` tests exact Output membership and `to` stays total for
412
+ * valid Outputs.
391
413
  *
392
414
  * ### Why is JsonValue stricter than JSON.stringify?
393
415
  *
@@ -431,9 +453,10 @@ import { utf8ToBytes } from "@noble/ciphers/utils.js";
431
453
  import { sha256 } from "@noble/hashes/sha2.js";
432
454
  import * as bip39 from "@scure/bip39";
433
455
  import { wordlist } from "@scure/bip39/wordlists/english.js";
456
+ import { createMutableArray, } from "./Array.js";
434
457
  import { assert, assertNonNullable } from "./Assert.js";
435
458
  import { identity } from "./Function.js";
436
- import { createMutableRecord } from "./Object.js";
459
+ import { createMutableRecord, getObjectKind, isPlainObject } from "./Object.js";
437
460
  import { hasNodeBuffer } from "./Platform.js";
438
461
  import { err, flatMapResult, getOk, getOrNull, getOrThrow, ok, trySync, } from "./Result.js";
439
462
  import { safelyStringifyUnknownValue } from "./String.js";
@@ -470,33 +493,9 @@ const createCollectionRuntimeTypeIssues = (name, issuesKind, defaultFormatter, g
470
493
  });
471
494
  };
472
495
  const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
473
- /**
474
- * Asserts that a value belongs to a {@link Type} Output domain.
475
- *
476
- * Use this for internal invariants, not external input. Validate external input
477
- * with `Type.fromUnknown` so validation failures remain typed values. A failed
478
- * assertion uses the Type name for its message and preserves the exact Output
479
- * validation error as the thrown Error's cause.
480
- *
481
- * ### Example
482
- *
483
- * ```ts
484
- * import {
485
- * NonEmptyTrimmedString100,
486
- * assertType,
487
- * type Brand,
488
- * } from "@evolu/common";
489
- *
490
- * const value: unknown = "Evolu";
491
- * assertType(NonEmptyTrimmedString100, value);
492
- * expectTypeOf(value).toEqualTypeOf<
493
- * string & Brand<"Trimmed"> & Brand<"MinLength1"> & Brand<"MaxLength100">
494
- * >();
495
- * ```
496
- *
497
- * @group Core
498
- */
499
- export const assertType = (type, value) => {
496
+ export function assertType(type, value) {
497
+ if (type === undefined)
498
+ return;
500
499
  // TODO: Make assert prepend "Expected " and accept an optional third cause
501
500
  // argument. Then use it here and migrate every other assertion to pass an
502
501
  // expectation fragment.
@@ -505,7 +504,7 @@ export const assertType = (type, value) => {
505
504
  if (!result.ok) {
506
505
  throw new Error(`Expected ${runtimeType.name}.`, { cause: result.error });
507
506
  }
508
- };
507
+ }
509
508
  const assertTypeOutput = (name, is, validateOutput, value, options = firstValidationOptions) => {
510
509
  if (is(value))
511
510
  return;
@@ -538,13 +537,22 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
538
537
  * every locale an app supports also allows language changes without a network
539
538
  * connection.
540
539
  *
541
- * The selected Type map, locale map, and formatter maps must be plain objects
542
- * with own enumerable string-keyed data properties.
540
+ * The selected Type map, locale map, and formatter maps must satisfy the
541
+ * realm-neutral structural heuristic described by {@link isPlainObject} and have
542
+ * own enumerable string-keyed data properties.
543
543
  *
544
544
  * ### Example
545
545
  *
546
546
  * ```ts
547
- * import { String, localizeTypes, minLength } from "@evolu/common";
547
+ * import {
548
+ * assertEqual,
549
+ * Data,
550
+ * assertErr,
551
+ * assertType,
552
+ * String,
553
+ * localizeTypes,
554
+ * minLength,
555
+ * } from "@evolu/common";
548
556
  * import { cs } from "@evolu/common/intl";
549
557
  *
550
558
  * const Label = minLength(1)(String);
@@ -559,13 +567,14 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
559
567
  * },
560
568
  * );
561
569
  *
562
- * expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
563
- * typeof Label
564
- * >();
570
+ * assertType<typeof Label, typeof typesByLocale.cs.Label>();
565
571
  *
566
572
  * const result = typesByLocale.cs.Label.fromUnknown("");
567
- * expectErr(result, { type: "MinLength1", min: 1, value: "" });
568
- * expect(typesByLocale.cs.Label.formatError(result.error)).toBe(
573
+ * assertErr(result);
574
+ * assertType(Data, result.error);
575
+ * assertEqual(result.error, { type: "MinLength1", min: 1, value: "" });
576
+ * assertEqual(
577
+ * typesByLocale.cs.Label.formatError(result.error),
569
578
  * "Text nesmí být prázdný.",
570
579
  * );
571
580
  * ```
@@ -647,9 +656,7 @@ export const localizeTypes = ((typesByName, formatErrorByTypeByLocale) => {
647
656
  });
648
657
  const getLocalizationMapKeys = (value) => {
649
658
  const errorMessage = "localizeTypes maps must be plain objects with own enumerable string-keyed data properties.";
650
- assert(value !== null && typeof value === "object", errorMessage);
651
- const prototype = globalThis.Object.getPrototypeOf(value);
652
- assert(prototype === null || globalThis.Object.getPrototypeOf(prototype) === null, errorMessage);
659
+ assert(isPlainObject(value), errorMessage);
653
660
  const keys = Reflect.ownKeys(value);
654
661
  for (const key of keys) {
655
662
  const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
@@ -699,12 +706,11 @@ const localizeTypeReflection = (value, formatIssue, localizedTypeBySource) => {
699
706
  value["~evolu/instance"] === "Type") {
700
707
  return withFormatError(value, formatIssue, localizedTypeBySource);
701
708
  }
702
- if (globalThis.Array.isArray(value)) {
709
+ if (Array.isArray(value)) {
703
710
  return value.map((value) => localizeTypeReflection(value, formatIssue, localizedTypeBySource));
704
711
  }
705
- if (value === null || typeof value !== "object" || !isPlainObject(value)) {
712
+ if (!isPlainObject(value))
706
713
  return value;
707
- }
708
714
  const localized = globalThis.Object.create(globalThis.Object.getPrototypeOf(value));
709
715
  for (const key of Reflect.ownKeys(value)) {
710
716
  localized[key] = localizeTypeReflection(value[key], formatIssue, localizedTypeBySource);
@@ -847,7 +853,7 @@ const createToOperation = (parent, own) => {
847
853
  return to;
848
854
  };
849
855
  function getTerminalRuntimeNode(node) {
850
- while (node.parent)
856
+ while (node.parent != null)
851
857
  node = node.parent;
852
858
  return node;
853
859
  }
@@ -945,16 +951,20 @@ const createTypeOfType = (name) => {
945
951
  * and significant whitespace:
946
952
  *
947
953
  * ```ts
948
- * import { String, maxLength, type Brand } from "@evolu/common";
954
+ * import {
955
+ * assertOk,
956
+ * assertType,
957
+ * String,
958
+ * maxLength,
959
+ * type Brand,
960
+ * } from "@evolu/common";
949
961
  *
950
962
  * const WireValue100 = maxLength(100)(String);
951
963
  * type WireValue100 = typeof WireValue100.Output;
952
964
  *
953
- * expectTypeOf<WireValue100>().toEqualTypeOf<
954
- * string & Brand<"MaxLength100">
955
- * >();
956
- * expectOk(WireValue100.fromUnknown(""), "");
957
- * expectOk(WireValue100.fromUnknown(" value "), " value ");
965
+ * assertType<string & Brand<"MaxLength100">, WireValue100>();
966
+ * assertOk(WireValue100.fromUnknown(""), "");
967
+ * assertOk(WireValue100.fromUnknown(" value "), " value ");
958
968
  * ```
959
969
  *
960
970
  * @group String
@@ -980,7 +990,12 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
980
990
  *
981
991
  * ```ts
982
992
  * import {
993
+ * assertEqual,
994
+ * assertErr,
995
+ * assertOk,
996
+ * assertType,
983
997
  * Age,
998
+ * Data,
984
999
  * FiniteNumber,
985
1000
  * Int,
986
1001
  * NonNaNNumber,
@@ -990,36 +1005,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
990
1005
  * } from "@evolu/common";
991
1006
  *
992
1007
  * // Note how every additional constraint accumulates its Brand.
993
- * expectTypeOf<typeof Number.Output>().toEqualTypeOf<number>();
994
- * expectTypeOf<typeof NonNaNNumber.Output>().toEqualTypeOf<
995
- * number & Brand<"NonNaN">
996
- * >();
997
- * expectTypeOf<typeof FiniteNumber.Output>().toEqualTypeOf<
998
- * number & Brand<"NonNaN"> & Brand<"Finite">
1008
+ * assertType<number, typeof Number.Output>();
1009
+ * assertType<number & Brand<"NonNaN">, typeof NonNaNNumber.Output>();
1010
+ * assertType<
1011
+ * number & Brand<"NonNaN"> & Brand<"Finite">,
1012
+ * typeof FiniteNumber.Output
999
1013
  * >();
1000
- * expectTypeOf<typeof Int.Output>().toEqualTypeOf<
1001
- * number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int">
1014
+ * assertType<
1015
+ * number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int">,
1016
+ * typeof Int.Output
1002
1017
  * >();
1003
- * expectTypeOf<typeof NonNegativeInt.Output>().toEqualTypeOf<
1018
+ * assertType<
1004
1019
  * number &
1005
1020
  * Brand<"NonNaN"> &
1006
1021
  * Brand<"Finite"> &
1007
1022
  * Brand<"Int"> &
1008
- * Brand<"NonNegative">
1023
+ * Brand<"NonNegative">,
1024
+ * typeof NonNegativeInt.Output
1009
1025
  * >();
1010
1026
  *
1011
- * expectTypeOf<typeof Age.Output>().toEqualTypeOf<
1027
+ * assertType<
1012
1028
  * number &
1013
1029
  * Brand<"NonNaN"> &
1014
1030
  * Brand<"Finite"> &
1015
1031
  * Brand<"Int"> &
1016
1032
  * Brand<"NonNegative"> &
1017
1033
  * Brand<"LessThan200"> &
1018
- * Brand<"Age">
1034
+ * Brand<"Age">,
1035
+ * typeof Age.Output
1019
1036
  * >();
1020
1037
  *
1021
- * expectOk(Age.fromUnknown(122), 122);
1022
- * expectErr(Age.fromUnknown(200), {
1038
+ * assertOk(Age.fromUnknown(122), 122);
1039
+ * const invalid = Age.fromUnknown(200);
1040
+ * assertErr(invalid);
1041
+ * assertType(Data, invalid.error);
1042
+ * assertEqual(invalid.error, {
1023
1043
  * type: "LessThan200",
1024
1044
  * value: 200,
1025
1045
  * max: 200,
@@ -1116,7 +1136,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
1116
1136
  * ### Example
1117
1137
  *
1118
1138
  * ```ts
1119
- * import { instanceOf } from "@evolu/common";
1139
+ * import { assertFalse, assertTrue, instanceOf } from "@evolu/common";
1120
1140
  *
1121
1141
  * class User {
1122
1142
  * readonly name: string;
@@ -1128,8 +1148,8 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
1128
1148
  *
1129
1149
  * const UserInstance = instanceOf(User);
1130
1150
  *
1131
- * assert(UserInstance.is(new User("Ada")));
1132
- * assert(!UserInstance.is({ name: "Ada" }));
1151
+ * assertTrue(UserInstance.is(new User("Ada")));
1152
+ * assertFalse(UserInstance.is({ name: "Ada" }));
1133
1153
  * ```
1134
1154
  *
1135
1155
  * @group Base
@@ -1156,13 +1176,23 @@ export const instanceOf = (constructor) => {
1156
1176
  * ### Example
1157
1177
  *
1158
1178
  * ```ts
1159
- * import { literal } from "@evolu/common";
1179
+ * import {
1180
+ * assertEqual,
1181
+ * assertErr,
1182
+ * assertOk,
1183
+ * assertType,
1184
+ * Data,
1185
+ * literal,
1186
+ * } from "@evolu/common";
1160
1187
  *
1161
1188
  * const Ready = literal("ready");
1162
1189
  *
1163
- * expectTypeOf<typeof Ready.Output>().toEqualTypeOf<"ready">();
1164
- * expectOk(Ready.fromUnknown("ready"), "ready");
1165
- * expectErr(Ready.fromUnknown("pending"), {
1190
+ * assertType<"ready", typeof Ready.Output>();
1191
+ * assertOk(Ready.fromUnknown("ready"), "ready");
1192
+ * const invalid = Ready.fromUnknown("pending");
1193
+ * assertErr(invalid);
1194
+ * assertType(Data, invalid.error);
1195
+ * assertEqual(invalid.error, {
1166
1196
  * type: "Literal",
1167
1197
  * expected: "ready",
1168
1198
  * value: "pending",
@@ -1257,11 +1287,11 @@ const createUnionValidation = (members, validateMember) => (value, options = fir
1257
1287
  * ### Example
1258
1288
  *
1259
1289
  * ```ts
1260
- * import { String, undefinedOr } from "@evolu/common";
1290
+ * import { assertOk, String, undefinedOr } from "@evolu/common";
1261
1291
  *
1262
1292
  * const StringOrUndefined = undefinedOr(String);
1263
1293
  *
1264
- * expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
1294
+ * assertOk(StringOrUndefined.fromUnknown(undefined), undefined);
1265
1295
  * ```
1266
1296
  *
1267
1297
  * @group Unions
@@ -1273,11 +1303,11 @@ export const undefinedOr = (type) => union(type, Undefined);
1273
1303
  * ### Example
1274
1304
  *
1275
1305
  * ```ts
1276
- * import { String, nullOr } from "@evolu/common";
1306
+ * import { assertOk, String, nullOr } from "@evolu/common";
1277
1307
  *
1278
1308
  * const NullableString = nullOr(String);
1279
1309
  *
1280
- * expectOk(NullableString.fromUnknown(null), null);
1310
+ * assertOk(NullableString.fromUnknown(null), null);
1281
1311
  * ```
1282
1312
  *
1283
1313
  * @group Unions
@@ -1289,20 +1319,18 @@ export const nullOr = (type) => union(type, Null);
1289
1319
  * ### Example
1290
1320
  *
1291
1321
  * ```ts
1292
- * import { String, nullishOr } from "@evolu/common";
1322
+ * import { assertOk, String, nullishOr } from "@evolu/common";
1293
1323
  *
1294
1324
  * const NullishString = nullishOr(String);
1295
1325
  *
1296
- * expectOk(NullishString.fromUnknown(undefined), undefined);
1297
- * expectOk(NullishString.fromUnknown(null), null);
1326
+ * assertOk(NullishString.fromUnknown(undefined), undefined);
1327
+ * assertOk(NullishString.fromUnknown(null), null);
1298
1328
  * ```
1299
1329
  *
1300
1330
  * @group Unions
1301
1331
  */
1302
1332
  export const nullishOr = (type) => union(type, Null, Undefined);
1303
- const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1304
- "members" in type &&
1305
- globalThis.Array.isArray(type.members);
1333
+ const isRuntimeUnionTypeNode = (type) => type.name === "Union" && "members" in type && Array.isArray(type.members);
1306
1334
  /**
1307
1335
  * Template literal {@link Type} that parses canonical strings into Tuples.
1308
1336
  *
@@ -1338,7 +1366,16 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1338
1366
  * the structured data decoded from it:
1339
1367
  *
1340
1368
  * ```ts
1341
- * import { templateLiteralParser, union } from "@evolu/common";
1369
+ * import {
1370
+ * assertFalse,
1371
+ * assertEqual,
1372
+ * assertErr,
1373
+ * assertOk,
1374
+ * assertType,
1375
+ * Data,
1376
+ * templateLiteralParser,
1377
+ * union,
1378
+ * } from "@evolu/common";
1342
1379
  *
1343
1380
  * const Language = union("en", "cs");
1344
1381
  * const Region = union("US", "CZ");
@@ -1348,36 +1385,39 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1348
1385
  *
1349
1386
  * // Output is the decoded language and region.
1350
1387
  * type SupportedLocale = typeof SupportedLocale.Output;
1351
- * expectTypeOf<SupportedLocale>().toEqualTypeOf<
1352
- * readonly ["en" | "cs", "US" | "CZ"]
1353
- * >();
1388
+ * assertType<readonly ["en" | "cs", "US" | "CZ"], SupportedLocale>();
1354
1389
  *
1355
1390
  * // The parent Output is the canonical locale string.
1356
1391
  * type SupportedLocaleLiteral = typeof SupportedLocale.parent.Output;
1357
- * expectTypeOf<SupportedLocaleLiteral>().toEqualTypeOf<
1358
- * "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
1392
+ * assertType<
1393
+ * "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
1394
+ * SupportedLocaleLiteral
1359
1395
  * >();
1360
1396
  *
1361
1397
  * // Parse an unknown string into structured data.
1362
1398
  * const result = SupportedLocale.fromUnknown("cs-CZ");
1363
- * expectOk(result, ["cs", "CZ"]);
1399
+ * assertOk(result, ["cs", "CZ"]);
1364
1400
  * const locale = result.value;
1365
- * expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
1366
- * expectErr(SupportedLocale.fromUnknown("cs/CZ"), {
1401
+ * assertType<SupportedLocale, typeof locale>();
1402
+ * const invalid = SupportedLocale.fromUnknown("cs/CZ");
1403
+ * assertErr(invalid);
1404
+ * assertType(Data, invalid.error);
1405
+ * const error: Data = invalid.error;
1406
+ * assertEqual(error, {
1367
1407
  * type: "TemplateLiteral",
1368
1408
  * value: "cs/CZ",
1369
1409
  * });
1370
1410
  *
1371
1411
  * // Encode structured data into its canonical string.
1372
1412
  * const localeLiteral = SupportedLocale.to(locale);
1373
- * expectTypeOf(localeLiteral).toEqualTypeOf<SupportedLocaleLiteral>();
1374
- * expect(localeLiteral).toBe("cs-CZ");
1413
+ * assertType<SupportedLocaleLiteral, typeof localeLiteral>();
1414
+ * assertEqual(localeLiteral, "cs-CZ");
1375
1415
  *
1376
1416
  * // Validate a string configuration value.
1377
1417
  * const configValue: unknown = "cs-CZ";
1378
- * assert(SupportedLocale.parent.is(configValue));
1379
- * expectTypeOf(configValue).toEqualTypeOf<SupportedLocaleLiteral>();
1380
- * expect(SupportedLocale.parent.is("fr-CZ")).toBe(false);
1418
+ * assertType(SupportedLocale.parent, configValue);
1419
+ * assertType<SupportedLocaleLiteral, typeof configValue>();
1420
+ * assertFalse(SupportedLocale.parent.is("fr-CZ"));
1381
1421
  * ```
1382
1422
  *
1383
1423
  * `SupportedLocale` is structured data for application code.
@@ -1390,6 +1430,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1390
1430
  *
1391
1431
  * ```ts
1392
1432
  * import {
1433
+ * assertEqual,
1434
+ * assertOk,
1393
1435
  * NonNegativeDecimalString,
1394
1436
  * templateLiteralParser,
1395
1437
  * } from "@evolu/common";
@@ -1402,8 +1444,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1402
1444
  * // DecimalText.to requires a validated NonNegativeDecimalString.
1403
1445
  * const zero = NonNegativeDecimalString.orThrow("0");
1404
1446
  *
1405
- * expectOk(DecimalText.fromUnknown("decimal:0"), [zero]);
1406
- * expect(DecimalText.to([zero])).toBe("decimal:0");
1447
+ * assertOk(DecimalText.fromUnknown("decimal:0"), [zero]);
1448
+ * assertEqual(DecimalText.to([zero]), "decimal:0");
1407
1449
  * ```
1408
1450
  *
1409
1451
  * Capture Types (the Type arguments passed to `templateLiteralParser`) can use
@@ -1411,6 +1453,9 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1411
1453
  *
1412
1454
  * ```ts
1413
1455
  * import {
1456
+ * assertEqual,
1457
+ * assertOk,
1458
+ * assertType,
1414
1459
  * Int64FromInt64String,
1415
1460
  * templateLiteralParser,
1416
1461
  * } from "@evolu/common";
@@ -1421,24 +1466,29 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1421
1466
  *
1422
1467
  * // Decode the string into structured data.
1423
1468
  * const result = ItemId.fromUnknown("item-42");
1424
- * expectOk(result, [42n]);
1469
+ * assertOk(result, [42n]);
1425
1470
  * const itemId = result.value;
1426
- * expectTypeOf(itemId).toEqualTypeOf<ItemId>();
1471
+ * assertType<ItemId, typeof itemId>();
1427
1472
  *
1428
1473
  * // Encode the structured data into its canonical string.
1429
1474
  * const itemIdLiteral = ItemId.to(itemId);
1430
- * expectTypeOf(itemIdLiteral).toEqualTypeOf<ItemIdLiteral>();
1431
- * expect(itemIdLiteral).toBe("item-42");
1475
+ * assertType<ItemIdLiteral, typeof itemIdLiteral>();
1476
+ * assertEqual(itemIdLiteral, "item-42");
1432
1477
  *
1433
1478
  * // TypeScript cannot prove from the literal alone that "42" is a valid Int64 encoding.
1434
1479
  * // @ts-expect-error Validate it with ItemId.parent or create it with ItemId.to.
1435
- * const invalidItemIdLiteral: ItemIdLiteral = "item-42";
1480
+ * const _invalidItemIdLiteral: ItemIdLiteral = "item-42";
1436
1481
  * ```
1437
1482
  *
1438
1483
  * Fixed-width captures can be adjacent:
1439
1484
  *
1440
1485
  * ```ts
1441
- * import { templateLiteralParser, union } from "@evolu/common";
1486
+ * import {
1487
+ * assertEqual,
1488
+ * assertOk,
1489
+ * templateLiteralParser,
1490
+ * union,
1491
+ * } from "@evolu/common";
1442
1492
  *
1443
1493
  * const Digit = union("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
1444
1494
  * const TwoDigits = templateLiteralParser(Digit, Digit);
@@ -1448,10 +1498,10 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1448
1498
  * const twoDigits: TwoDigits = ["4", "2"];
1449
1499
  * const twoDigitsLiteral: TwoDigitsLiteral = "42";
1450
1500
  * // @ts-expect-error TwoDigitsLiteral requires exactly two digits.
1451
- * const threeDigitsLiteral: TwoDigitsLiteral = "123";
1501
+ * const _threeDigitsLiteral: TwoDigitsLiteral = "123";
1452
1502
  *
1453
- * expectOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
1454
- * expect(TwoDigits.to(twoDigits)).toBe(twoDigitsLiteral);
1503
+ * assertOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
1504
+ * assertEqual(TwoDigits.to(twoDigits), twoDigitsLiteral);
1455
1505
  * ```
1456
1506
  *
1457
1507
  * TypeScript rejects multiple variable-width captures because their encoded
@@ -1582,17 +1632,24 @@ const createTemplateLiteralParserType = (templateParts) => {
1582
1632
  * ### Example
1583
1633
  *
1584
1634
  * ```ts
1585
- * import { templateLiteral, union } from "@evolu/common";
1635
+ * import {
1636
+ * assertFalse,
1637
+ * assertOk,
1638
+ * assertType,
1639
+ * templateLiteral,
1640
+ * union,
1641
+ * } from "@evolu/common";
1586
1642
  *
1587
1643
  * const Language = union("en", "cs");
1588
1644
  * const Region = union("US", "CZ");
1589
1645
  * const Locale = templateLiteral(Language, "-", Region);
1590
1646
  *
1591
- * expectTypeOf<typeof Locale.Output>().toEqualTypeOf<
1592
- * "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
1647
+ * assertType<
1648
+ * "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
1649
+ * typeof Locale.Output
1593
1650
  * >();
1594
- * expectOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
1595
- * expect(Locale.is("fr-CZ")).toBe(false);
1651
+ * assertOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
1652
+ * assertFalse(Locale.is("fr-CZ"));
1596
1653
  * ```
1597
1654
  *
1598
1655
  * @group Template literals
@@ -1608,7 +1665,7 @@ const compileTemplateLiteralParser = (parts) => {
1608
1665
  return [part, partFraming.width];
1609
1666
  });
1610
1667
  return (input) => {
1611
- const inputCodePoints = globalThis.Array.from(input);
1668
+ const inputCodePoints = Array.from(input);
1612
1669
  const variableWidth = inputCodePoints.length - fixedPartsWidth;
1613
1670
  if (variableWidth < 0) {
1614
1671
  return err({ type: "TemplateLiteral", value: input });
@@ -1664,7 +1721,7 @@ const getStringTemplateLiteralFraming = (value) => {
1664
1721
  const firstCodeUnit = value.charCodeAt(0);
1665
1722
  const lastCodeUnit = value.charCodeAt(value.length - 1);
1666
1723
  return {
1667
- width: globalThis.Array.from(value).length,
1724
+ width: Array.from(value).length,
1668
1725
  canBeEmpty: value.length === 0,
1669
1726
  canStartWithLowSurrogate: firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff,
1670
1727
  canEndWithHighSurrogate: lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff,
@@ -1718,13 +1775,21 @@ export function brand(name, parent, validate, formatError) {
1718
1775
  * ### Example
1719
1776
  *
1720
1777
  * ```ts
1721
- * import { DateIso } from "@evolu/common";
1778
+ * import {
1779
+ * assertEqual,
1780
+ * assertErr,
1781
+ * assertOk,
1782
+ * assertType,
1783
+ * Data,
1784
+ * DateIso,
1785
+ * } from "@evolu/common";
1722
1786
  *
1723
- * expectOk(
1724
- * DateIso.fromUnknown("2023-01-01T12:00:00.000Z"),
1725
- * "2023-01-01T12:00:00.000Z",
1726
- * );
1727
- * expectErr(DateIso.fromUnknown("2023-01-01"), {
1787
+ * const value = "2023-01-01T12:00:00.000Z";
1788
+ * assertOk(DateIso.fromUnknown(value), value);
1789
+ * const invalid = DateIso.fromUnknown("2023-01-01");
1790
+ * assertErr(invalid);
1791
+ * assertType(Data, invalid.error);
1792
+ * assertEqual(invalid.error, {
1728
1793
  * type: "DateIso",
1729
1794
  * value: "2023-01-01",
1730
1795
  * });
@@ -1741,13 +1806,13 @@ export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => value.l
1741
1806
  * ### Example
1742
1807
  *
1743
1808
  * ```ts
1744
- * import { DateIsoFromDate } from "@evolu/common";
1809
+ * import { assertEqual, assertOk, DateIsoFromDate } from "@evolu/common";
1745
1810
  *
1746
- * const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
1811
+ * const date = new Date("2025-01-01T12:00:00.000Z");
1747
1812
  * const result = DateIsoFromDate.fromUnknown(date);
1748
1813
  *
1749
- * expectOk(result, "2025-01-01T12:00:00.000Z");
1750
- * expect(DateIsoFromDate.to(result.value)).toEqual(date);
1814
+ * assertOk(result, "2025-01-01T12:00:00.000Z");
1815
+ * assertEqual(DateIsoFromDate.to(result.value), date);
1751
1816
  * ```
1752
1817
  *
1753
1818
  * @group String
@@ -1780,17 +1845,27 @@ export const UInt64 = /*#__PURE__*/ brand("UInt64", BigInt, (value) => globalThi
1780
1845
  * ### Example
1781
1846
  *
1782
1847
  * ```ts
1783
- * import { String, capitalized, type Brand } from "@evolu/common";
1848
+ * import {
1849
+ * assertEqual,
1850
+ * assertErr,
1851
+ * assertOk,
1852
+ * assertType,
1853
+ * Data,
1854
+ * String,
1855
+ * capitalized,
1856
+ * type Brand,
1857
+ * } from "@evolu/common";
1784
1858
  *
1785
1859
  * const CapitalizedString = capitalized(String);
1786
1860
  * type CapitalizedString = typeof CapitalizedString.Output;
1787
1861
  *
1788
- * expectTypeOf<CapitalizedString>().toEqualTypeOf<
1789
- * string & Brand<"Capitalized">
1790
- * >();
1862
+ * assertType<string & Brand<"Capitalized">, CapitalizedString>();
1791
1863
  *
1792
- * expectOk(CapitalizedString.fromUnknown("Evolu"), "Evolu");
1793
- * expectErr(CapitalizedString.fromUnknown("evolu"), {
1864
+ * assertOk(CapitalizedString.fromUnknown("Evolu"), "Evolu");
1865
+ * const invalid = CapitalizedString.fromUnknown("evolu");
1866
+ * assertErr(invalid);
1867
+ * assertType(Data, invalid.error);
1868
+ * assertEqual(invalid.error, {
1794
1869
  * type: "Capitalized",
1795
1870
  * value: "evolu",
1796
1871
  * });
@@ -1816,11 +1891,11 @@ export const CapitalizedString = /*#__PURE__*/ capitalized(String);
1816
1891
  * ### Example
1817
1892
  *
1818
1893
  * ```ts
1819
- * import { String, trimmed } from "@evolu/common";
1894
+ * import { assertOk, String, trimmed } from "@evolu/common";
1820
1895
  *
1821
1896
  * const Trimmed = trimmed(String);
1822
1897
  *
1823
- * expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
1898
+ * assertOk(Trimmed.fromUnknown("Evolu"), "Evolu");
1824
1899
  * ```
1825
1900
  *
1826
1901
  * @group String
@@ -1846,9 +1921,9 @@ export const TrimmedString = /*#__PURE__*/ trimmed(String);
1846
1921
  * ### Example
1847
1922
  *
1848
1923
  * ```ts
1849
- * import { trim } from "@evolu/common";
1924
+ * import { assertEqual, trim } from "@evolu/common";
1850
1925
  *
1851
- * expect(trim(" Evolu ")).toBe("Evolu");
1926
+ * assertEqual(trim(" Evolu "), "Evolu");
1852
1927
  * ```
1853
1928
  *
1854
1929
  * @group String
@@ -1860,13 +1935,13 @@ export const trim = (value) => value.trim();
1860
1935
  * ### Example
1861
1936
  *
1862
1937
  * ```ts
1863
- * import { String, array, minLength } from "@evolu/common";
1938
+ * import { assertOk, String, array, minLength } from "@evolu/common";
1864
1939
  *
1865
1940
  * const AtLeastThreeCharacters = minLength(3)(String);
1866
1941
  * const AtLeastTwoItems = minLength(2)(array(String));
1867
1942
  *
1868
- * expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
1869
- * expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1943
+ * assertOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
1944
+ * assertOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1870
1945
  * ```
1871
1946
  *
1872
1947
  * @group String
@@ -1899,13 +1974,13 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
1899
1974
  * ### Example
1900
1975
  *
1901
1976
  * ```ts
1902
- * import { String, array, maxLength } from "@evolu/common";
1977
+ * import { assertOk, String, array, maxLength } from "@evolu/common";
1903
1978
  *
1904
1979
  * const AtMostThreeCharacters = maxLength(3)(String);
1905
1980
  * const AtMostTwoItems = maxLength(2)(array(String));
1906
1981
  *
1907
- * expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
1908
- * expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1982
+ * assertOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
1983
+ * assertOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1909
1984
  * ```
1910
1985
  *
1911
1986
  * @group String
@@ -1935,13 +2010,13 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(NonEmptyT
1935
2010
  * ### Example
1936
2011
  *
1937
2012
  * ```ts
1938
- * import { String, array, length } from "@evolu/common";
2013
+ * import { assertOk, String, array, length } from "@evolu/common";
1939
2014
  *
1940
2015
  * const ThreeCharacters = length(3)(String);
1941
2016
  * const TwoItems = length(2)(array(String));
1942
2017
  *
1943
- * expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
1944
- * expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
2018
+ * assertOk(ThreeCharacters.fromUnknown("abc"), "abc");
2019
+ * assertOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1945
2020
  * ```
1946
2021
  *
1947
2022
  * @group String
@@ -1961,21 +2036,34 @@ export const length = (exact) => (parent) => {
1961
2036
  * A non-empty string using the URL-safe alphabet:
1962
2037
  *
1963
2038
  * ```ts
1964
- * import { String, regex, type Brand } from "@evolu/common";
2039
+ * import {
2040
+ * assertEqual,
2041
+ * assertErr,
2042
+ * assertOk,
2043
+ * assertType,
2044
+ * Data,
2045
+ * String,
2046
+ * regex,
2047
+ * type Brand,
2048
+ * } from "@evolu/common";
1965
2049
  *
1966
- * const UrlSafeString = regex("UrlSafeString", /^[A-Za-z0-9_-]+$/)(String);
2050
+ * const UrlSafeString = regex(
2051
+ * "UrlSafeString",
2052
+ * /^[A-Za-z0-9_-]+$/u,
2053
+ * )(String);
1967
2054
  * type UrlSafeString = typeof UrlSafeString.Output;
1968
2055
  *
1969
- * expectTypeOf<UrlSafeString>().toEqualTypeOf<
1970
- * string & Brand<"UrlSafeString">
1971
- * >();
2056
+ * assertType<string & Brand<"UrlSafeString">, UrlSafeString>();
1972
2057
  *
1973
- * expectOk(UrlSafeString.fromUnknown("abc-123_DEF"), "abc-123_DEF");
1974
- * expectErr(UrlSafeString.fromUnknown("not safe"), {
2058
+ * assertOk(UrlSafeString.fromUnknown("abc-123_DEF"), "abc-123_DEF");
2059
+ * const invalid = UrlSafeString.fromUnknown("not safe");
2060
+ * assertErr(invalid);
2061
+ * assertType(Data, invalid.error);
2062
+ * assertEqual(invalid.error, {
1975
2063
  * type: "UrlSafeString",
1976
2064
  * value: "not safe",
1977
2065
  * source: "^[A-Za-z0-9_-]+$",
1978
- * flags: "",
2066
+ * flags: "u",
1979
2067
  * });
1980
2068
  * ```
1981
2069
  *
@@ -2007,7 +2095,7 @@ export const regex = (name, pattern) => {
2007
2095
  *
2008
2096
  * @group String
2009
2097
  */
2010
- export const UrlSafeString = /*#__PURE__*/ regex("UrlSafeString", /^[A-Za-z0-9_-]+$/)(String);
2098
+ export const UrlSafeString = /*#__PURE__*/ regex("UrlSafeString", /^[A-Za-z0-9_-]+$/u)(String);
2011
2099
  const base64UrlOptions = {
2012
2100
  alphabet: "base64url",
2013
2101
  omitPadding: true,
@@ -2020,9 +2108,9 @@ const uint8ArrayToBase64UrlString = (bytes) => {
2020
2108
  if ("toBase64" in uint8ArrayPrototype) {
2021
2109
  return bytes.toBase64(base64UrlOptions);
2022
2110
  }
2023
- const binaryString = globalThis.Array.from(bytes, (byte) => globalThis.String.fromCodePoint(byte)).join("");
2024
- const base64 = globalThis.btoa(binaryString);
2025
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
2111
+ const binaryString = Array.from(bytes, (byte) => globalThis.String.fromCodePoint(byte)).join("");
2112
+ const base64 = btoa(binaryString);
2113
+ return base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
2026
2114
  };
2027
2115
  const base64UrlStringToUint8Array = (value) => {
2028
2116
  if (hasNodeBuffer) {
@@ -2033,10 +2121,10 @@ const base64UrlStringToUint8Array = (value) => {
2033
2121
  if ("fromBase64" in uint8ArrayConstructor) {
2034
2122
  return globalThis.Uint8Array.fromBase64(value, base64UrlOptions);
2035
2123
  }
2036
- let base64 = value.replace(/-/g, "+").replace(/_/g, "/");
2124
+ let base64 = value.replaceAll("-", "+").replaceAll("_", "/");
2037
2125
  while (base64.length % 4 !== 0)
2038
2126
  base64 += "=";
2039
- const binaryString = globalThis.atob(base64);
2127
+ const binaryString = atob(base64);
2040
2128
  return globalThis.Uint8Array.from(binaryString, (character) => character.charCodeAt(0));
2041
2129
  };
2042
2130
  /**
@@ -2059,9 +2147,10 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
2059
2147
  * ### Example
2060
2148
  *
2061
2149
  * ```ts
2062
- * import { uint8ArrayToBase64Url } from "@evolu/common";
2150
+ * import { assertEqual, uint8ArrayToBase64Url } from "@evolu/common";
2063
2151
  *
2064
- * expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
2152
+ * assertEqual(
2153
+ * uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255])),
2065
2154
  * "AAEC_w",
2066
2155
  * );
2067
2156
  * ```
@@ -2075,11 +2164,16 @@ export const uint8ArrayToBase64Url = (bytes) => uint8ArrayToBase64UrlString(byte
2075
2164
  * ### Example
2076
2165
  *
2077
2166
  * ```ts
2078
- * import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
2167
+ * import {
2168
+ * assertEqual,
2169
+ * Base64Url,
2170
+ * base64UrlToUint8Array,
2171
+ * } from "@evolu/common";
2079
2172
  *
2080
2173
  * const value = Base64Url.orThrow("AAEC_w");
2081
2174
  *
2082
- * expect(base64UrlToUint8Array(value)).toEqual(
2175
+ * assertEqual(
2176
+ * base64UrlToUint8Array(value),
2083
2177
  * new Uint8Array([0, 1, 2, 255]),
2084
2178
  * );
2085
2179
  * ```
@@ -2137,6 +2231,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
2137
2231
  *
2138
2232
  * ```ts
2139
2233
  * import {
2234
+ * assertTrue,
2235
+ * assertType,
2140
2236
  * Id,
2141
2237
  * createId,
2142
2238
  * createRandomBytes,
@@ -2145,8 +2241,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
2145
2241
  *
2146
2242
  * const userId = createId<"User">({ randomBytes: createRandomBytes() });
2147
2243
  *
2148
- * expect(Id.is(userId)).toBe(true);
2149
- * expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
2244
+ * assertTrue(Id.is(userId));
2245
+ * assertType<Id & Brand<"User">, typeof userId>();
2150
2246
  * ```
2151
2247
  *
2152
2248
  * @group String
@@ -2162,14 +2258,20 @@ export const createId = (deps, ..._validation) => uint8ArrayToBase64Url(deps.ran
2162
2258
  * ### Example
2163
2259
  *
2164
2260
  * ```ts
2165
- * import { createIdFromString, type Brand, type Id } from "@evolu/common";
2261
+ * import {
2262
+ * assertEqual,
2263
+ * assertType,
2264
+ * createIdFromString,
2265
+ * type Brand,
2266
+ * type Id,
2267
+ * } from "@evolu/common";
2166
2268
  *
2167
2269
  * const first = createIdFromString("external-user-123");
2168
2270
  * const second = createIdFromString("external-user-123");
2169
2271
  * const todoId = createIdFromString<"Todo">("external-todo-456");
2170
2272
  *
2171
- * expect(first).toBe(second);
2172
- * expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
2273
+ * assertEqual(first, second);
2274
+ * assertType<Id & Brand<"Todo">, typeof todoId>();
2173
2275
  * ```
2174
2276
  *
2175
2277
  * @group String
@@ -2187,6 +2289,7 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
2187
2289
  *
2188
2290
  * ```ts
2189
2291
  * import {
2292
+ * assertEqual,
2190
2293
  * createIdAsUuidv7,
2191
2294
  * createRandomBytes,
2192
2295
  * createTime,
@@ -2199,8 +2302,8 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
2199
2302
  * });
2200
2303
  * const bytes = idToIdBytes(value);
2201
2304
  *
2202
- * expect(bytes[6] >> 4).toBe(0x7);
2203
- * expect(bytes[8] & 0xc0).toBe(0x80);
2305
+ * assertEqual(bytes[6] >> 4, 0x7);
2306
+ * assertEqual(bytes[8] & 0xc0, 0x80);
2204
2307
  * ```
2205
2308
  *
2206
2309
  * @group String
@@ -2225,6 +2328,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
2225
2328
  *
2226
2329
  * ```ts
2227
2330
  * import {
2331
+ * assertType,
2228
2332
  * createIdFromString,
2229
2333
  * id,
2230
2334
  * type Brand,
@@ -2234,7 +2338,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
2234
2338
  * const TodoId = id("Todo");
2235
2339
  * const todoId = TodoId.orThrow(createIdFromString("todo"));
2236
2340
  *
2237
- * expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
2341
+ * assertType<Id & Brand<"Todo">, typeof todoId>();
2238
2342
  * ```
2239
2343
  *
2240
2344
  * @group String
@@ -2264,11 +2368,15 @@ export const idBytesTypeValueLength = 16;
2264
2368
  * ### Example
2265
2369
  *
2266
2370
  * ```ts
2267
- * import { createIdFromString, idToIdBytes } from "@evolu/common";
2371
+ * import {
2372
+ * assertEqual,
2373
+ * createIdFromString,
2374
+ * idToIdBytes,
2375
+ * } from "@evolu/common";
2268
2376
  *
2269
2377
  * const bytes = idToIdBytes(createIdFromString("todo"));
2270
2378
  *
2271
- * expect(bytes).toHaveLength(16);
2379
+ * assertEqual(bytes.length, 16);
2272
2380
  * ```
2273
2381
  *
2274
2382
  * @group String
@@ -2281,6 +2389,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
2281
2389
  *
2282
2390
  * ```ts
2283
2391
  * import {
2392
+ * assertEqual,
2284
2393
  * createIdFromString,
2285
2394
  * idBytesToId,
2286
2395
  * idToIdBytes,
@@ -2288,7 +2397,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
2288
2397
  *
2289
2398
  * const value = createIdFromString("todo");
2290
2399
  *
2291
- * expect(idBytesToId(idToIdBytes(value))).toBe(value);
2400
+ * assertEqual(idBytesToId(idToIdBytes(value)), value);
2292
2401
  * ```
2293
2402
  *
2294
2403
  * @group String
@@ -2302,7 +2411,7 @@ export const idBytesToId = (value) => uint8ArrayToBase64Url(value);
2302
2411
  export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedString, (value) => {
2303
2412
  const negative = value.startsWith("-");
2304
2413
  if (value.length > (negative ? 20 : 19) ||
2305
- !/^(?:0|-?[1-9]\d*)$/.test(value)) {
2414
+ !/^(?:0|-?[1-9]\d*)$/u.test(value)) {
2306
2415
  return err({ type: "Int64String", value });
2307
2416
  }
2308
2417
  const digits = negative ? value.slice(1) : value;
@@ -2321,12 +2430,17 @@ export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedStr
2321
2430
  * ### Example
2322
2431
  *
2323
2432
  * ```ts
2324
- * import { Int64FromInt64String } from "@evolu/common";
2433
+ * import {
2434
+ * assertEqual,
2435
+ * assertOk,
2436
+ * Int64FromInt64String,
2437
+ * } from "@evolu/common";
2325
2438
  *
2326
2439
  * const result = Int64FromInt64String.fromUnknown("9223372036854775807");
2327
2440
  *
2328
- * expectOk(result, 9223372036854775807n);
2329
- * expect(Int64FromInt64String.to(result.value)).toBe(
2441
+ * assertOk(result, 9223372036854775807n);
2442
+ * assertEqual(
2443
+ * Int64FromInt64String.to(result.value),
2330
2444
  * "9223372036854775807",
2331
2445
  * );
2332
2446
  * ```
@@ -2343,11 +2457,11 @@ export const Int64FromInt64String = /*#__PURE__*/ transform("Int64FromInt64Strin
2343
2457
  * ### Example
2344
2458
  *
2345
2459
  * ```ts
2346
- * import { Number, nonNegative } from "@evolu/common";
2460
+ * import { assertOk, Number, nonNegative } from "@evolu/common";
2347
2461
  *
2348
2462
  * const NonNegative = nonNegative(Number);
2349
2463
  *
2350
- * expectOk(NonNegative.fromUnknown(0), 0);
2464
+ * assertOk(NonNegative.fromUnknown(0), 0);
2351
2465
  * ```
2352
2466
  *
2353
2467
  * @group Number
@@ -2365,11 +2479,11 @@ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
2365
2479
  * ### Example
2366
2480
  *
2367
2481
  * ```ts
2368
- * import { Number, positive } from "@evolu/common";
2482
+ * import { assertOk, Number, positive } from "@evolu/common";
2369
2483
  *
2370
2484
  * const Positive = positive(Number);
2371
2485
  *
2372
- * expectOk(Positive.fromUnknown(1), 1);
2486
+ * assertOk(Positive.fromUnknown(1), 1);
2373
2487
  * ```
2374
2488
  *
2375
2489
  * @group Number
@@ -2390,11 +2504,11 @@ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
2390
2504
  * ### Example
2391
2505
  *
2392
2506
  * ```ts
2393
- * import { Number, nonPositive } from "@evolu/common";
2507
+ * import { assertOk, Number, nonPositive } from "@evolu/common";
2394
2508
  *
2395
2509
  * const NonPositive = nonPositive(Number);
2396
2510
  *
2397
- * expectOk(NonPositive.fromUnknown(0), 0);
2511
+ * assertOk(NonPositive.fromUnknown(0), 0);
2398
2512
  * ```
2399
2513
  *
2400
2514
  * @group Number
@@ -2412,11 +2526,11 @@ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
2412
2526
  * ### Example
2413
2527
  *
2414
2528
  * ```ts
2415
- * import { Number, negative } from "@evolu/common";
2529
+ * import { assertOk, Number, negative } from "@evolu/common";
2416
2530
  *
2417
2531
  * const Negative = negative(Number);
2418
2532
  *
2419
- * expectOk(Negative.fromUnknown(-1), -1);
2533
+ * assertOk(Negative.fromUnknown(-1), -1);
2420
2534
  * ```
2421
2535
  *
2422
2536
  * @group Number
@@ -2437,11 +2551,11 @@ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
2437
2551
  * ### Example
2438
2552
  *
2439
2553
  * ```ts
2440
- * import { Number, nonNaN } from "@evolu/common";
2554
+ * import { assertOk, Number, nonNaN } from "@evolu/common";
2441
2555
  *
2442
2556
  * const NonNaN = nonNaN(Number);
2443
2557
  *
2444
- * expectOk(NonNaN.fromUnknown(Infinity), Infinity);
2558
+ * assertOk(NonNaN.fromUnknown(Infinity), Infinity);
2445
2559
  * ```
2446
2560
  *
2447
2561
  * @group Number
@@ -2466,11 +2580,11 @@ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
2466
2580
  * ### Example
2467
2581
  *
2468
2582
  * ```ts
2469
- * import { Number, finite } from "@evolu/common";
2583
+ * import { assertOk, Number, finite } from "@evolu/common";
2470
2584
  *
2471
2585
  * const Finite = finite(Number);
2472
2586
  *
2473
- * expectOk(Finite.fromUnknown(42), 42);
2587
+ * assertOk(Finite.fromUnknown(42), 42);
2474
2588
  * ```
2475
2589
  *
2476
2590
  * @group Number
@@ -2505,15 +2619,28 @@ export const PositiveFiniteNumber = /*#__PURE__*/ positive(NonNegativeFiniteNumb
2505
2619
  * ### Example
2506
2620
  *
2507
2621
  * ```ts
2508
- * import { Number, int, type Brand } from "@evolu/common";
2622
+ * import {
2623
+ * assertEqual,
2624
+ * assertErr,
2625
+ * assertOk,
2626
+ * assertType,
2627
+ * Data,
2628
+ * Number,
2629
+ * int,
2630
+ * type Brand,
2631
+ * } from "@evolu/common";
2509
2632
  *
2510
2633
  * const Int = int(Number);
2511
2634
  * type Int = typeof Int.Output;
2512
2635
  *
2513
- * expectTypeOf<Int>().toEqualTypeOf<number & Brand<"Int">>();
2636
+ * assertType<number & Brand<"Int">, Int>();
2514
2637
  *
2515
- * expectOk(Int.fromUnknown(42), 42);
2516
- * expectErr(Int.fromUnknown(1.5), { type: "Int", value: 1.5 });
2638
+ * assertOk(Int.fromUnknown(42), 42);
2639
+ * const invalid = Int.fromUnknown(1.5);
2640
+ * assertErr(invalid);
2641
+ * assertType(Data, invalid.error);
2642
+ * const error: Data = invalid.error;
2643
+ * assertEqual(error, { type: "Int", value: 1.5 });
2517
2644
  * ```
2518
2645
  *
2519
2646
  * @group Number
@@ -2582,11 +2709,11 @@ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
2582
2709
  * ### Example
2583
2710
  *
2584
2711
  * ```ts
2585
- * import { Number, greaterThan } from "@evolu/common";
2712
+ * import { assertOk, Number, greaterThan } from "@evolu/common";
2586
2713
  *
2587
2714
  * const GreaterThanTen = greaterThan(10)(Number);
2588
2715
  *
2589
- * expectOk(GreaterThanTen.fromUnknown(11), 11);
2716
+ * assertOk(GreaterThanTen.fromUnknown(11), 11);
2590
2717
  * ```
2591
2718
  *
2592
2719
  * @group Number
@@ -2603,11 +2730,11 @@ export const greaterThan = (min) => (parent) => {
2603
2730
  * ### Example
2604
2731
  *
2605
2732
  * ```ts
2606
- * import { Number, greaterThanOrEqualTo } from "@evolu/common";
2733
+ * import { assertOk, Number, greaterThanOrEqualTo } from "@evolu/common";
2607
2734
  *
2608
2735
  * const AtLeastTen = greaterThanOrEqualTo(10)(Number);
2609
2736
  *
2610
- * expectOk(AtLeastTen.fromUnknown(10), 10);
2737
+ * assertOk(AtLeastTen.fromUnknown(10), 10);
2611
2738
  * ```
2612
2739
  *
2613
2740
  * @group Number
@@ -2628,11 +2755,11 @@ export const greaterThanOrEqualTo = (min) => (parent) => {
2628
2755
  * ### Example
2629
2756
  *
2630
2757
  * ```ts
2631
- * import { Number, lessThan } from "@evolu/common";
2758
+ * import { assertOk, Number, lessThan } from "@evolu/common";
2632
2759
  *
2633
2760
  * const LessThanTen = lessThan(10)(Number);
2634
2761
  *
2635
- * expectOk(LessThanTen.fromUnknown(9), 9);
2762
+ * assertOk(LessThanTen.fromUnknown(9), 9);
2636
2763
  * ```
2637
2764
  *
2638
2765
  * @group Number
@@ -2656,11 +2783,11 @@ export const Age = /*#__PURE__*/ brand("Age",
2656
2783
  * ### Example
2657
2784
  *
2658
2785
  * ```ts
2659
- * import { Number, lessThanOrEqualTo } from "@evolu/common";
2786
+ * import { assertOk, Number, lessThanOrEqualTo } from "@evolu/common";
2660
2787
  *
2661
2788
  * const AtMostTen = lessThanOrEqualTo(10)(Number);
2662
2789
  *
2663
- * expectOk(AtMostTen.fromUnknown(10), 10);
2790
+ * assertOk(AtMostTen.fromUnknown(10), 10);
2664
2791
  * ```
2665
2792
  *
2666
2793
  * @group Number
@@ -2707,13 +2834,23 @@ export const Ratio = /*#__PURE__*/ brand("Ratio",
2707
2834
  * ### Example
2708
2835
  *
2709
2836
  * ```ts
2710
- * import { DecimalString } from "@evolu/common";
2837
+ * import {
2838
+ * assertEqual,
2839
+ * assertErr,
2840
+ * assertOk,
2841
+ * assertType,
2842
+ * Data,
2843
+ * DecimalString,
2844
+ * } from "@evolu/common";
2711
2845
  *
2712
- * expectOk(DecimalString.fromUnknown("-10.25"), "-10.25");
2713
- * expectOk(DecimalString.fromUnknown("0"), "0");
2714
- * expectOk(DecimalString.fromUnknown("10.25"), "10.25");
2846
+ * assertOk(DecimalString.fromUnknown("-10.25"), "-10.25");
2847
+ * assertOk(DecimalString.fromUnknown("0"), "0");
2848
+ * assertOk(DecimalString.fromUnknown("10.25"), "10.25");
2715
2849
  *
2716
- * expectErr(DecimalString.fromUnknown("10.250"), {
2850
+ * const invalid = DecimalString.fromUnknown("10.250");
2851
+ * assertErr(invalid);
2852
+ * assertType(Data, invalid.error);
2853
+ * assertEqual(invalid.error, {
2717
2854
  * type: "DecimalString",
2718
2855
  * value: "10.250",
2719
2856
  * });
@@ -2721,7 +2858,7 @@ export const Ratio = /*#__PURE__*/ brand("Ratio",
2721
2858
  *
2722
2859
  * @group Number
2723
2860
  */
2724
- export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value) => /^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))$/.test(value)
2861
+ export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value) => /^(?:0|-?(?:[1-9]\d*|(?:0|[1-9]\d*)\.\d*[1-9]))$/u.test(value)
2725
2862
  ? ok()
2726
2863
  : err({ type: "DecimalString", value }), (error) => `The value ${safelyStringifyUnknownValue(error.value)} must be a canonical decimal string.`);
2727
2864
  /**
@@ -2730,11 +2867,15 @@ export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value
2730
2867
  * ### Example
2731
2868
  *
2732
2869
  * ```ts
2733
- * import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
2870
+ * import {
2871
+ * assertOk,
2872
+ * DecimalString,
2873
+ * nonNegativeDecimalString,
2874
+ * } from "@evolu/common";
2734
2875
  *
2735
2876
  * const NonNegative = nonNegativeDecimalString(DecimalString);
2736
2877
  *
2737
- * expectOk(NonNegative.fromUnknown("0.5"), "0.5");
2878
+ * assertOk(NonNegative.fromUnknown("0.5"), "0.5");
2738
2879
  * ```
2739
2880
  *
2740
2881
  * @group Number
@@ -2758,11 +2899,15 @@ export const NonNegativeDecimalString =
2758
2899
  * ### Example
2759
2900
  *
2760
2901
  * ```ts
2761
- * import { DecimalString, positiveDecimalString } from "@evolu/common";
2902
+ * import {
2903
+ * assertOk,
2904
+ * DecimalString,
2905
+ * positiveDecimalString,
2906
+ * } from "@evolu/common";
2762
2907
  *
2763
2908
  * const Positive = positiveDecimalString(DecimalString);
2764
2909
  *
2765
- * expectOk(Positive.fromUnknown("0.5"), "0.5");
2910
+ * assertOk(Positive.fromUnknown("0.5"), "0.5");
2766
2911
  * ```
2767
2912
  *
2768
2913
  * @group Number
@@ -2788,11 +2933,15 @@ export const PositiveDecimalString = /*#__PURE__*/ positiveDecimalString(NonNega
2788
2933
  * ### Example
2789
2934
  *
2790
2935
  * ```ts
2791
- * import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
2936
+ * import {
2937
+ * assertOk,
2938
+ * DecimalString,
2939
+ * nonPositiveDecimalString,
2940
+ * } from "@evolu/common";
2792
2941
  *
2793
2942
  * const NonPositive = nonPositiveDecimalString(DecimalString);
2794
2943
  *
2795
- * expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
2944
+ * assertOk(NonPositive.fromUnknown("-0.5"), "-0.5");
2796
2945
  * ```
2797
2946
  *
2798
2947
  * @group Number
@@ -2816,11 +2965,15 @@ export const NonPositiveDecimalString =
2816
2965
  * ### Example
2817
2966
  *
2818
2967
  * ```ts
2819
- * import { DecimalString, negativeDecimalString } from "@evolu/common";
2968
+ * import {
2969
+ * assertOk,
2970
+ * DecimalString,
2971
+ * negativeDecimalString,
2972
+ * } from "@evolu/common";
2820
2973
  *
2821
2974
  * const Negative = negativeDecimalString(DecimalString);
2822
2975
  *
2823
- * expectOk(Negative.fromUnknown("-0.5"), "-0.5");
2976
+ * assertOk(Negative.fromUnknown("-0.5"), "-0.5");
2824
2977
  * ```
2825
2978
  *
2826
2979
  * @group Number
@@ -2854,16 +3007,29 @@ export const NegativeDecimalString = /*#__PURE__*/ negativeDecimalString(NonPosi
2854
3007
  * ### Example
2855
3008
  *
2856
3009
  * ```ts
2857
- * import { FiniteNumber, multipleOf, type Brand } from "@evolu/common";
3010
+ * import {
3011
+ * assertEqual,
3012
+ * assertErr,
3013
+ * assertOk,
3014
+ * assertType,
3015
+ * Data,
3016
+ * FiniteNumber,
3017
+ * multipleOf,
3018
+ * type Brand,
3019
+ * } from "@evolu/common";
2858
3020
  *
2859
3021
  * const Tenths = multipleOf("0.1")(FiniteNumber);
2860
3022
  *
2861
- * expectTypeOf<typeof Tenths.Output>().toEqualTypeOf<
2862
- * FiniteNumber & Brand<"MultipleOf0.1">
3023
+ * assertType<
3024
+ * FiniteNumber & Brand<"MultipleOf0.1">,
3025
+ * typeof Tenths.Output
2863
3026
  * >();
2864
3027
  *
2865
- * expectOk(Tenths.fromUnknown(0.3), 0.3);
2866
- * expectErr(Tenths.fromUnknown(0.31), {
3028
+ * assertOk(Tenths.fromUnknown(0.3), 0.3);
3029
+ * const invalid = Tenths.fromUnknown(0.31);
3030
+ * assertErr(invalid);
3031
+ * assertType(Data, invalid.error);
3032
+ * assertEqual(invalid.error, {
2867
3033
  * type: "MultipleOf0.1",
2868
3034
  * value: 0.31,
2869
3035
  * divisor: "0.1",
@@ -2919,11 +3085,11 @@ const decimalStringToParts = (value) => {
2919
3085
  * ### Example
2920
3086
  *
2921
3087
  * ```ts
2922
- * import { Number, between } from "@evolu/common";
3088
+ * import { assertOk, Number, between } from "@evolu/common";
2923
3089
  *
2924
3090
  * const Percentage = between(0, 100)(Number);
2925
3091
  *
2926
- * expectOk(Percentage.fromUnknown(75), 75);
3092
+ * assertOk(Percentage.fromUnknown(75), 75);
2927
3093
  * ```
2928
3094
  *
2929
3095
  * @group Number
@@ -2965,6 +3131,11 @@ export const between = (min, max) => (parent) => {
2965
3131
  *
2966
3132
  * ```ts
2967
3133
  * import {
3134
+ * assertEqual,
3135
+ * assertErr,
3136
+ * assertOk,
3137
+ * assertType,
3138
+ * Data,
2968
3139
  * String,
2969
3140
  * array,
2970
3141
  * brand,
@@ -2976,12 +3147,16 @@ export const between = (min, max) => (parent) => {
2976
3147
  * const UserIds = array(UserId);
2977
3148
  * const result = UserIds.from.parent(["ada", "grace"]);
2978
3149
  *
2979
- * expectTypeOf(result).toEqualTypeOf<
2980
- * Result<ReadonlyArray<string & Brand<"UserId">>, never>
3150
+ * assertType<
3151
+ * Result<ReadonlyArray<string & Brand<"UserId">>>,
3152
+ * typeof result
2981
3153
  * >();
2982
- * expectOk(result, ["ada", "grace"]);
2983
- * expectOk(UserIds.fromUnknown(["ada", "grace"]), ["ada", "grace"]);
2984
- * expectErr(UserIds.fromUnknown("ada"), {
3154
+ * assertOk(result, ["ada", "grace"]);
3155
+ * assertOk(UserIds.fromUnknown(["ada", "grace"]), ["ada", "grace"]);
3156
+ * const invalid = UserIds.fromUnknown("ada");
3157
+ * assertErr(invalid);
3158
+ * assertType(Data, invalid.error);
3159
+ * assertEqual(invalid.error, {
2985
3160
  * type: "Array",
2986
3161
  * reason: { kind: "NotArray", value: "ada" },
2987
3162
  * });
@@ -3120,7 +3295,7 @@ const validateIndexedArrayItems = (name, value, validate, options, checkStructur
3120
3295
  });
3121
3296
  };
3122
3297
  const copyArrayPrefix = (value, endIndex) => {
3123
- const output = new Array(value.length);
3298
+ const output = createMutableArray(value.length);
3124
3299
  for (let index = 0; index < endIndex; index++) {
3125
3300
  output[index] = value[index];
3126
3301
  }
@@ -3129,20 +3304,20 @@ const copyArrayPrefix = (value, endIndex) => {
3129
3304
  /**
3130
3305
  * Set {@link Type} whose every element must match one Type.
3131
3306
  *
3132
- * Direct Sets from this or another realm are accepted. Set subclasses are
3133
- * rejected. A Set must have no own properties; its elements are validated in
3134
- * iteration order. Classification uses the realm-neutral object tag and
3135
- * prototype structure under Evolu Type's trusted JavaScript policy.
3307
+ * Sets from this or another realm are accepted. A Set must have no own
3308
+ * properties; its elements are validated in iteration order. Classification
3309
+ * uses the realm-neutral object tag under Evolu Type's trusted JavaScript
3310
+ * policy.
3136
3311
  *
3137
3312
  * ### Example
3138
3313
  *
3139
3314
  * ```ts
3140
- * import { String, set } from "@evolu/common";
3315
+ * import { assertOk, String, set } from "@evolu/common";
3141
3316
  *
3142
3317
  * const Tags = set(String);
3143
3318
  * const tags = new Set(["local-first", "offline"]);
3144
3319
  *
3145
- * expectOk(Tags.fromUnknown(tags), tags);
3320
+ * assertOk(Tags.fromUnknown(tags), tags);
3146
3321
  * ```
3147
3322
  *
3148
3323
  * @group Collection
@@ -3176,68 +3351,44 @@ const createHomogeneousCollectionType = (typeElement, config) => {
3176
3351
  return type;
3177
3352
  };
3178
3353
  const setTypeByElement = /*#__PURE__*/ new WeakMap();
3179
- const validateSetCollection = (value, validateElement, options) => {
3180
- if (!hasObjectTag(value, "Set")) {
3181
- return err({
3182
- type: "Set",
3183
- reason: { kind: "NotSet", value },
3184
- });
3185
- }
3186
- if (!hasDirectSetPrototype(value)) {
3187
- return err({
3188
- type: "Set",
3189
- reason: {
3190
- kind: "UnexpectedPrototype",
3191
- value: value,
3192
- },
3193
- });
3194
- }
3195
- return validateSetItems(value, validateElement, options, true);
3196
- };
3197
- const encodeSetCollection = (value, encodeElement) => {
3198
- let changed = false;
3199
- const output = new globalThis.Set();
3200
- for (const item of value) {
3201
- const encoded = encodeElement(item);
3202
- if (!globalThis.Object.is(encoded, item))
3203
- changed = true;
3204
- output.add(encoded);
3205
- }
3206
- return changed ? output : value;
3207
- };
3208
- const isSetCollection = (value, isElement) => {
3209
- if (!hasObjectTag(value, "Set"))
3210
- return false;
3211
- if (!hasDirectSetPrototype(value))
3212
- return false;
3213
- if (Reflect.ownKeys(value).length !== 0)
3214
- return false;
3215
- for (const item of value) {
3216
- if (!isElement(item))
3217
- return false;
3218
- }
3219
- return true;
3220
- };
3221
- const hasDirectSetPrototype = (value) => {
3222
- const prototype = globalThis.Object.getPrototypeOf(value);
3223
- if (prototype === null)
3224
- return false;
3225
- const objectPrototype = globalThis.Object.getPrototypeOf(prototype);
3226
- return (objectPrototype !== null &&
3227
- globalThis.Object.getPrototypeOf(objectPrototype) === null);
3228
- };
3229
3354
  const setRuntimeConfig = {
3230
3355
  name: "Set",
3231
3356
  typeByElement: setTypeByElement,
3232
- validate: validateSetCollection,
3357
+ validate: (value, validateElement, options) => {
3358
+ if (!hasObjectTag(value, "Set")) {
3359
+ return err({
3360
+ type: "Set",
3361
+ reason: { kind: "NotSet", value },
3362
+ });
3363
+ }
3364
+ return validateSetItems(value, validateElement, options, true);
3365
+ },
3233
3366
  validateItems: (value, validateElement, options) => validateSetItems(value, validateElement, options, false),
3234
- encode: encodeSetCollection,
3235
- is: isSetCollection,
3367
+ encode: (value, encodeElement) => {
3368
+ let changed = false;
3369
+ const output = new Set();
3370
+ for (const item of value) {
3371
+ const encoded = encodeElement(item);
3372
+ if (!globalThis.Object.is(encoded, item))
3373
+ changed = true;
3374
+ output.add(encoded);
3375
+ }
3376
+ return changed ? output : value;
3377
+ },
3378
+ is: (value, isElement) => {
3379
+ if (!hasObjectTag(value, "Set") ||
3380
+ Reflect.ownKeys(value).length !== 0) {
3381
+ return false;
3382
+ }
3383
+ for (const item of value) {
3384
+ if (!isElement(item))
3385
+ return false;
3386
+ }
3387
+ return true;
3388
+ },
3236
3389
  formatError: ((error) => {
3237
3390
  if (error.reason.kind === "NotSet")
3238
3391
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
3239
- if (error.reason.kind === "UnexpectedPrototype")
3240
- return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
3241
3392
  const issue = error.reason.issues[0];
3242
3393
  switch (issue.kind) {
3243
3394
  case "ExcessProperty":
@@ -3250,7 +3401,7 @@ const setRuntimeConfig = {
3250
3401
  const validateSetItems = (value, validate, options, checkStructure) => {
3251
3402
  let issues;
3252
3403
  let changed = false;
3253
- const output = new globalThis.Set();
3404
+ const output = new Set();
3254
3405
  if (checkStructure) {
3255
3406
  for (const key of Reflect.ownKeys(value)) {
3256
3407
  (issues ??= []).push({ kind: "ExcessProperty", key });
@@ -3283,6 +3434,208 @@ const validateSetItems = (value, validate, options, checkStructure) => {
3283
3434
  },
3284
3435
  });
3285
3436
  };
3437
+ /**
3438
+ * Map {@link Type} whose keys and values must match their respective Types.
3439
+ *
3440
+ * Maps from this or another realm are accepted. A Map must have no own
3441
+ * properties; its entries are validated in iteration order. When distinct input
3442
+ * keys decode to the same output key, validation fails instead of discarding
3443
+ * one associated value. Classification uses the realm-neutral object tag under
3444
+ * Evolu Type's trusted JavaScript policy.
3445
+ *
3446
+ * ### Example
3447
+ *
3448
+ * ```ts
3449
+ * import { assertOk, PositiveInt, String, map } from "@evolu/common";
3450
+ *
3451
+ * const Scores = map(String, PositiveInt);
3452
+ * const scores = new Map([
3453
+ * ["Ada", 10],
3454
+ * ["Grace", 20],
3455
+ * ]);
3456
+ *
3457
+ * assertOk(Scores.fromUnknown(scores), scores);
3458
+ * ```
3459
+ *
3460
+ * @group Collection
3461
+ */
3462
+ export const map = (key, value) => {
3463
+ const typeKey = key;
3464
+ const typeValue = value;
3465
+ let typeByValue = mapTypeByValueByKey.get(typeKey);
3466
+ const cached = typeByValue?.get(typeValue);
3467
+ if (cached)
3468
+ return cached;
3469
+ const validate = (input, validateKey, validateValue, options) => {
3470
+ if (!hasObjectTag(input, "Map")) {
3471
+ return err({
3472
+ type: "Map",
3473
+ reason: { kind: "NotMap", value: input },
3474
+ });
3475
+ }
3476
+ return validateMapEntries(input, validateKey, validateValue, options, true);
3477
+ };
3478
+ const fromUnknown = (input, options = firstValidationOptions) => validate(input, typeKey.fromUnknown, typeValue.fromUnknown, options);
3479
+ const validateOutput = (input, options = firstValidationOptions) => validate(input, typeKey[outputValidationSymbol], typeValue[outputValidationSymbol], options);
3480
+ const formatError = (error) => {
3481
+ if (error.reason.kind === "NotMap")
3482
+ return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Map.`;
3483
+ const issue = error.reason.issues[0];
3484
+ switch (issue.kind) {
3485
+ case "ExcessProperty":
3486
+ return `An excess Map property ${safelyStringifyUnknownValue(issue.key)} is not allowed.`;
3487
+ case "Collision":
3488
+ return `Map keys at indexes ${issue.previousIndex} and ${issue.index} decode to the same key ${safelyStringifyUnknownValue(issue.outputKey)}.`;
3489
+ }
3490
+ };
3491
+ const rootKey = getTerminalRuntimeNode(typeKey);
3492
+ const rootValue = getTerminalRuntimeNode(typeValue);
3493
+ const parent = rootKey !== typeKey || rootValue !== typeValue
3494
+ ? map(rootKey, rootValue)
3495
+ : null;
3496
+ const fromParent = parent
3497
+ ? (input, options = firstValidationOptions) => validateMapEntries(input, getTerminalRuntimeNode(typeKey[fromSymbol]), getTerminalRuntimeNode(typeValue[fromSymbol]), options, false)
3498
+ : undefined;
3499
+ const from = createFromOperation(fromParent);
3500
+ const encodeKey = typeKey[encoderSymbol];
3501
+ const encodeValue = typeValue[encoderSymbol];
3502
+ const to = encodeKey === identity && encodeValue === identity
3503
+ ? identity
3504
+ : (input) => {
3505
+ const output = new Map();
3506
+ let changed = false;
3507
+ for (const [inputKey, inputValue] of input) {
3508
+ const outputKey = encodeKey(inputKey);
3509
+ const outputValue = encodeValue(inputValue);
3510
+ assert(!output.has(outputKey), "Map key Type encoding must not produce duplicate keys.");
3511
+ output.set(outputKey, outputValue);
3512
+ if (!globalThis.Object.is(inputKey, outputKey) ||
3513
+ !globalThis.Object.is(inputValue, outputValue)) {
3514
+ changed = true;
3515
+ }
3516
+ }
3517
+ return changed ? output : input;
3518
+ };
3519
+ const is = (input) => {
3520
+ if (!hasObjectTag(input, "Map")) {
3521
+ return false;
3522
+ }
3523
+ if (Reflect.ownKeys(input).length !== 0)
3524
+ return false;
3525
+ for (const [inputKey, inputValue] of input) {
3526
+ if (!typeKey.is(inputKey) || !typeValue.is(inputValue))
3527
+ return false;
3528
+ }
3529
+ return true;
3530
+ };
3531
+ const getTypeIssues = (error, mode) => {
3532
+ const mapError = error;
3533
+ if (mapError.reason.kind !== "Entries") {
3534
+ return singleRuntimeTypeIssue("Map", error, formatError);
3535
+ }
3536
+ const allIssues = mapError.reason.issues;
3537
+ const issues = mode === "first" ? [allIssues[0]] : allIssues;
3538
+ return issues.flatMap((issue) => {
3539
+ if (issue.kind === "Key" || issue.kind === "Value") {
3540
+ return prependRuntimeTypeIssuePath(issue.index, prependRuntimeTypeIssuePath(issue.kind === "Key" ? "key" : "value", (issue.kind === "Key" ? typeKey : typeValue)[getRuntimeTypeIssuesSymbol](issue.error, mode)));
3541
+ }
3542
+ return singleRuntimeTypeIssue("Map", mode === "first"
3543
+ ? error
3544
+ : {
3545
+ type: "Map",
3546
+ reason: { kind: "Entries", issues: [issue] },
3547
+ }, formatError, [issue.kind === "ExcessProperty" ? issue.key : issue.index]);
3548
+ });
3549
+ };
3550
+ const type = createTypeNode("Map", parent, fromUnknown, is, validateOutput, from, to, getTypeIssues, { key: typeKey, value: typeValue });
3551
+ if (typeByValue === undefined) {
3552
+ typeByValue = new WeakMap();
3553
+ mapTypeByValueByKey.set(typeKey, typeByValue);
3554
+ }
3555
+ typeByValue.set(typeValue, type);
3556
+ return type;
3557
+ };
3558
+ const mapTypeByValueByKey = /*#__PURE__*/ new WeakMap();
3559
+ const validateMapEntries = (input, validateKey, validateValue, options, checkStructure) => {
3560
+ let issues;
3561
+ const output = new Map();
3562
+ const inputByOutputKey = new Map();
3563
+ let changed = false;
3564
+ if (checkStructure) {
3565
+ for (const key of Reflect.ownKeys(input)) {
3566
+ (issues ??= []).push({ kind: "ExcessProperty", key });
3567
+ if (options.errors === "first")
3568
+ break;
3569
+ }
3570
+ }
3571
+ let index = 0;
3572
+ for (const [inputKey, inputValue] of input) {
3573
+ if (issues !== undefined && options.errors === "first")
3574
+ break;
3575
+ const keyResult = validateKey(inputKey, options);
3576
+ if (!keyResult.ok) {
3577
+ (issues ??= []).push({
3578
+ kind: "Key",
3579
+ index,
3580
+ key: inputKey,
3581
+ error: keyResult.error,
3582
+ });
3583
+ if (options.errors === "first")
3584
+ break;
3585
+ }
3586
+ const valueResult = validateValue(inputValue, options);
3587
+ if (!valueResult.ok) {
3588
+ (issues ??= []).push({
3589
+ kind: "Value",
3590
+ index,
3591
+ key: inputKey,
3592
+ error: valueResult.error,
3593
+ });
3594
+ if (options.errors === "first")
3595
+ break;
3596
+ }
3597
+ if (!keyResult.ok) {
3598
+ index++;
3599
+ continue;
3600
+ }
3601
+ const outputKey = keyResult.value;
3602
+ const previous = inputByOutputKey.get(outputKey);
3603
+ if (previous !== undefined) {
3604
+ (issues ??= []).push({
3605
+ kind: "Collision",
3606
+ index,
3607
+ key: inputKey,
3608
+ previousIndex: previous.index,
3609
+ previousKey: previous.key,
3610
+ outputKey,
3611
+ });
3612
+ if (options.errors === "first")
3613
+ break;
3614
+ index++;
3615
+ continue;
3616
+ }
3617
+ inputByOutputKey.set(outputKey, { index, key: inputKey });
3618
+ if (!valueResult.ok) {
3619
+ index++;
3620
+ continue;
3621
+ }
3622
+ output.set(outputKey, valueResult.value);
3623
+ if (!globalThis.Object.is(inputKey, outputKey) ||
3624
+ !globalThis.Object.is(inputValue, valueResult.value)) {
3625
+ changed = true;
3626
+ }
3627
+ index++;
3628
+ }
3629
+ return issues === undefined
3630
+ ? ok(changed ? output : input)
3631
+ : err({
3632
+ type: "Map",
3633
+ reason: {
3634
+ kind: "Entries",
3635
+ issues: issues,
3636
+ },
3637
+ });
3638
+ };
3286
3639
  export function tuple(...elements) {
3287
3640
  return createTupleType(elements);
3288
3641
  }
@@ -3466,11 +3819,12 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
3466
3819
  * A {@link Type} for readonly plain objects with unknown property values.
3467
3820
  *
3468
3821
  * `Object` is the runtime counterpart of a `Readonly<Record<string, unknown>>`
3469
- * data boundary. Its realm-neutral prototype rule accepts a `null` prototype or
3470
- * an immediate root prototype whose own prototype is `null`. Every own property
3471
- * must have a string key and be an enumerable data property. It rejects
3472
- * ordinary class instances, deeper prototype chains, accessors, non-enumerable
3473
- * properties, and symbol properties without reading their values.
3822
+ * data boundary. Its prototype rule uses the realm-neutral structural heuristic
3823
+ * described by {@link isPlainObject}. A matching custom root prototype can be
3824
+ * classified as plain; other custom prototypes and class instances are
3825
+ * rejected. Every own property must have a string key and be an enumerable data
3826
+ * property. Accessors, non-enumerable properties, and symbol properties are
3827
+ * rejected without reading their values.
3474
3828
  *
3475
3829
  * Use {@link object} when property names are fixed, {@link record} when keys and
3476
3830
  * values have their own Types, and {@link instanceOf} when an instance belongs
@@ -3578,10 +3932,6 @@ const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstVa
3578
3932
  // https://github.com/react/metro/issues/1331
3579
3933
  // https://github.com/expo/expo/issues/31167
3580
3934
  export { _Object as Object };
3581
- const isPlainObject = (value) => {
3582
- const prototype = globalThis.Object.getPrototypeOf(value);
3583
- return (prototype === null || globalThis.Object.getPrototypeOf(prototype) === null);
3584
- };
3585
3935
  /**
3586
3936
  * Record {@link Type}.
3587
3937
  *
@@ -3598,12 +3948,13 @@ const isPlainObject = (value) => {
3598
3948
  * By default, validation returns the first issue. Pass `{ errors: "all" }` to
3599
3949
  * collect issues across the whole Record.
3600
3950
  *
3601
- * A Record must have a `null` prototype or an immediate root prototype whose
3602
- * own prototype is `null`. Ordinary class instances and deeper prototype chains
3603
- * are rejected. Every own property must be an enumerable data property whose
3604
- * key and value satisfy their Types. When decoding or encoding changes an
3605
- * entry, the constructed Record uses a `null` prototype so keys such as
3606
- * `__proto__` remain ordinary data.
3951
+ * A Record must satisfy the realm-neutral structural heuristic described by
3952
+ * {@link isPlainObject}. A matching custom root prototype can be classified as
3953
+ * plain; other custom prototypes and class instances are rejected. Every own
3954
+ * property must be an enumerable data property whose key and value satisfy
3955
+ * their Types. When decoding or encoding changes an entry, the constructed
3956
+ * Record uses a `null` prototype so keys such as `__proto__` remain ordinary
3957
+ * data.
3607
3958
  *
3608
3959
  * If transformed keys collide, validation fails instead of overwriting an
3609
3960
  * entry.
@@ -3612,6 +3963,8 @@ const isPlainObject = (value) => {
3612
3963
  *
3613
3964
  * ```ts
3614
3965
  * import {
3966
+ * assertOk,
3967
+ * assertType,
3615
3968
  * Int64FromInt64String,
3616
3969
  * String,
3617
3970
  * record,
@@ -3627,21 +3980,22 @@ const isPlainObject = (value) => {
3627
3980
  * grace: "20",
3628
3981
  * });
3629
3982
  *
3630
- * expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
3983
+ * assertOk(scoresFromUnknown, { ada: 10n, grace: 20n });
3631
3984
  *
3632
3985
  * // Validate keys and values with their root Types.
3633
3986
  * const scoresInput = ScoresByUser.parent.fromUnknown({
3634
3987
  * ada: "10",
3635
3988
  * grace: "20",
3636
3989
  * });
3637
- * expectOk(scoresInput, { ada: "10", grace: "20" });
3990
+ * assertOk(scoresInput, { ada: "10", grace: "20" });
3638
3991
  *
3639
3992
  * // Run the remaining key and value stages.
3640
3993
  * const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
3641
3994
  *
3642
- * expectOk(scoresFromInput, { ada: 10n, grace: 20n });
3643
- * expectTypeOf(scoresFromInput.value).toEqualTypeOf<
3644
- * Readonly<Partial<Record<string, Int64>>>
3995
+ * assertOk(scoresFromInput, { ada: 10n, grace: 20n });
3996
+ * assertType<
3997
+ * Readonly<Partial<Record<string, Int64>>>,
3998
+ * typeof scoresFromInput.value
3645
3999
  * >();
3646
4000
  * ```
3647
4001
  *
@@ -3650,34 +4004,64 @@ const isPlainObject = (value) => {
3650
4004
  * as a Record entry:
3651
4005
  *
3652
4006
  * ```ts
4007
+ * import {
4008
+ * assertEqual,
4009
+ * assertErr,
4010
+ * assertTrue,
4011
+ * trySync,
4012
+ * } from "@evolu/common";
4013
+ *
3653
4014
  * type Values = Partial<Record<"toString", number>>;
3654
4015
  * const values: Values = {};
3655
4016
  *
3656
4017
  * // TypeScript treats the inherited function as `number | undefined`.
3657
4018
  * const value: number | undefined = values.toString;
4019
+ * const valueType = typeof value;
3658
4020
  *
3659
- * expect(typeof value).toBe("function");
3660
- * expect(() => {
3661
- * if (value !== undefined) value.toFixed();
3662
- * }).toThrow(TypeError);
4021
+ * assertEqual(valueType, "function");
4022
+ * const called = trySync(
4023
+ * () => {
4024
+ * if (value !== undefined) value.toFixed(0);
4025
+ * },
4026
+ * (error) => error,
4027
+ * );
4028
+ * assertErr(called);
4029
+ * assertTrue(called.error instanceof TypeError);
3663
4030
  * ```
3664
4031
  *
3665
4032
  * Evolu Record Outputs use the same TypeScript Record representation.
3666
4033
  *
3667
4034
  * ```ts
3668
- * import { Number, literal, record } from "@evolu/common";
4035
+ * import {
4036
+ * assertEqual,
4037
+ * assertErr,
4038
+ * assertOk,
4039
+ * assertTrue,
4040
+ * trySync,
4041
+ * Number,
4042
+ * literal,
4043
+ * record,
4044
+ * type Data,
4045
+ * } from "@evolu/common";
3669
4046
  *
3670
4047
  * const Values = record(literal("toString"), Number);
3671
4048
  * const result = Values.fromUnknown({});
4049
+ * const emptyValues: Data = {};
3672
4050
  *
3673
- * expectOk(result, {});
4051
+ * assertOk(result, emptyValues);
3674
4052
  *
3675
4053
  * const value: number | undefined = result.value.toString;
4054
+ * const valueType = typeof value;
3676
4055
  *
3677
- * expect(typeof value).toBe("function");
3678
- * expect(() => {
3679
- * if (value !== undefined) value.toFixed();
3680
- * }).toThrow(TypeError);
4056
+ * assertEqual(valueType, "function");
4057
+ * const called = trySync(
4058
+ * () => {
4059
+ * if (value !== undefined) value.toFixed(0);
4060
+ * },
4061
+ * (error) => error,
4062
+ * );
4063
+ * assertErr(called);
4064
+ * assertTrue(called.error instanceof TypeError);
3681
4065
  * ```
3682
4066
  *
3683
4067
  * In other words, treat Record Outputs as string-keyed data rather than calling
@@ -3864,7 +4248,13 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
3864
4248
  * ### Example
3865
4249
  *
3866
4250
  * ```ts
3867
- * import { String, object, optional, undefinedOr } from "@evolu/common";
4251
+ * import {
4252
+ * assertOk,
4253
+ * String,
4254
+ * object,
4255
+ * optional,
4256
+ * undefinedOr,
4257
+ * } from "@evolu/common";
3868
4258
  *
3869
4259
  * const User = object({
3870
4260
  * name: String,
@@ -3872,8 +4262,8 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
3872
4262
  * preferredName: optional(undefinedOr(String)),
3873
4263
  * });
3874
4264
  *
3875
- * expectOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
3876
- * expectOk(User.fromUnknown({ name: "Ada", preferredName: undefined }), {
4265
+ * assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
4266
+ * assertOk(User.fromUnknown({ name: "Ada", preferredName: undefined }), {
3877
4267
  * name: "Ada",
3878
4268
  * preferredName: undefined,
3879
4269
  * });
@@ -4230,13 +4620,20 @@ const createRecordPropertyError = (issue) => ({
4230
4620
  * ### Example
4231
4621
  *
4232
4622
  * ```ts
4233
- * import { PositiveInt, String, partial } from "@evolu/common";
4623
+ * import {
4624
+ * assertFalse,
4625
+ * assertOk,
4626
+ * PositiveInt,
4627
+ * String,
4628
+ * partial,
4629
+ * } from "@evolu/common";
4234
4630
  *
4235
4631
  * const PartialUser = partial({ name: String, age: PositiveInt });
4632
+ * const emptyPartialUser: typeof PartialUser.Output = {};
4236
4633
  *
4237
- * expectOk(PartialUser.fromUnknown({}), {});
4238
- * expectOk(PartialUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4239
- * expect(PartialUser.fromUnknown({ age: -1 }).ok).toBe(false);
4634
+ * assertOk(PartialUser.fromUnknown({}), emptyPartialUser);
4635
+ * assertOk(PartialUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4636
+ * assertFalse(PartialUser.fromUnknown({ age: -1 }).ok);
4240
4637
  * ```
4241
4638
  *
4242
4639
  * @group Objects
@@ -4263,15 +4660,20 @@ export const partial = (props, ..._validation) => {
4263
4660
  * ### Example
4264
4661
  *
4265
4662
  * ```ts
4266
- * import { String, nullOr, nullableToOptional } from "@evolu/common";
4663
+ * import {
4664
+ * assertOk,
4665
+ * String,
4666
+ * nullOr,
4667
+ * nullableToOptional,
4668
+ * } from "@evolu/common";
4267
4669
  *
4268
4670
  * const User = nullableToOptional({
4269
4671
  * name: String,
4270
4672
  * nickname: nullOr(String),
4271
4673
  * });
4272
4674
  *
4273
- * expectOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
4274
- * expectOk(User.fromUnknown({ name: "Ada", nickname: null }), {
4675
+ * assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
4676
+ * assertOk(User.fromUnknown({ name: "Ada", nickname: null }), {
4275
4677
  * name: "Ada",
4276
4678
  * nickname: null,
4277
4679
  * });
@@ -4304,12 +4706,12 @@ export const nullableToOptional = (props, ..._validation) => {
4304
4706
  * ### Example
4305
4707
  *
4306
4708
  * ```ts
4307
- * import { String, object, omit } from "@evolu/common";
4709
+ * import { assertOk, String, object, omit } from "@evolu/common";
4308
4710
  *
4309
4711
  * const User = object({ name: String, password: String });
4310
4712
  * const PublicUser = omit(User, "password");
4311
4713
  *
4312
- * expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4714
+ * assertOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4313
4715
  * ```
4314
4716
  *
4315
4717
  * @group Objects
@@ -4564,7 +4966,7 @@ const lazyTypeNodes = /*#__PURE__*/ new WeakSet();
4564
4966
  const assertLazyReferencesAreGuarded = (type) => {
4565
4967
  for (;;) {
4566
4968
  assert(!lazyTypeNodes.has(type), "A Lazy Type definition must place every Lazy Type behind a structural boundary.");
4567
- if ("members" in type && globalThis.Array.isArray(type.members)) {
4969
+ if ("members" in type && Array.isArray(type.members)) {
4568
4970
  for (const member of type.members)
4569
4971
  assertLazyReferencesAreGuarded(member);
4570
4972
  }
@@ -4576,6 +4978,266 @@ const assertLazyReferencesAreGuarded = (type) => {
4576
4978
  type = type.parent;
4577
4979
  }
4578
4980
  };
4981
+ const emptyDataPath =
4982
+ /*#__PURE__*/ globalThis.Object.freeze([]);
4983
+ const dataPathToArray = (path) => {
4984
+ if (path === null)
4985
+ return emptyDataPath;
4986
+ const keys = [];
4987
+ let node = path;
4988
+ while (node !== null) {
4989
+ keys.push(node.key);
4990
+ node = node.parent;
4991
+ }
4992
+ keys.reverse();
4993
+ return globalThis.Object.freeze(keys);
4994
+ };
4995
+ const dataChildPath = (parent, key) => ({ parent, key });
4996
+ const validateData = (value, options = firstValidationOptions) => {
4997
+ const work = [{ value, path: null }];
4998
+ const visited = new WeakSet();
4999
+ let issues;
5000
+ const addIssue = (issue) => {
5001
+ (issues ??= []).push(issue);
5002
+ return options.errors === "first";
5003
+ };
5004
+ while (work.length > 0) {
5005
+ const current = work.pop();
5006
+ const { path, value } = current;
5007
+ if (value === null ||
5008
+ value === undefined ||
5009
+ typeof value === "string" ||
5010
+ typeof value === "number" ||
5011
+ typeof value === "bigint" ||
5012
+ typeof value === "boolean") {
5013
+ continue;
5014
+ }
5015
+ if (typeof value !== "object") {
5016
+ if (addIssue({
5017
+ kind: "InvalidType",
5018
+ path: dataPathToArray(path),
5019
+ value,
5020
+ })) {
5021
+ break;
5022
+ }
5023
+ continue;
5024
+ }
5025
+ if (visited.has(value))
5026
+ continue;
5027
+ const children = [];
5028
+ const kind = getObjectKind(value);
5029
+ if (kind === "Array") {
5030
+ const array = value;
5031
+ for (const key of Reflect.ownKeys(array)) {
5032
+ if (key === "length")
5033
+ continue;
5034
+ if (typeof key === "string") {
5035
+ const index = globalThis.Number(key) >>> 0;
5036
+ if (index < array.length && globalThis.String(index) === key) {
5037
+ continue;
5038
+ }
5039
+ }
5040
+ if (addIssue({
5041
+ kind: "ExcessProperty",
5042
+ path: dataPathToArray(dataChildPath(path, key)),
5043
+ container: "Array",
5044
+ })) {
5045
+ break;
5046
+ }
5047
+ }
5048
+ if (issues !== undefined && options.errors === "first")
5049
+ break;
5050
+ for (let index = 0; index < array.length; index++) {
5051
+ const childPath = dataChildPath(path, index);
5052
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(array, index);
5053
+ if (descriptor === undefined) {
5054
+ if (addIssue({ kind: "Hole", path: dataPathToArray(childPath) })) {
5055
+ break;
5056
+ }
5057
+ continue;
5058
+ }
5059
+ if (!("value" in descriptor)) {
5060
+ if (addIssue({ kind: "Accessor", path: dataPathToArray(childPath) })) {
5061
+ break;
5062
+ }
5063
+ continue;
5064
+ }
5065
+ children.push({ value: descriptor.value, path: childPath });
5066
+ }
5067
+ }
5068
+ else if (kind === "Object") {
5069
+ for (const key of Reflect.ownKeys(value)) {
5070
+ const childPath = dataChildPath(path, key);
5071
+ if (typeof key === "symbol") {
5072
+ if (addIssue({
5073
+ kind: "SymbolProperty",
5074
+ path: dataPathToArray(childPath),
5075
+ })) {
5076
+ break;
5077
+ }
5078
+ continue;
5079
+ }
5080
+ const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
5081
+ if (!("value" in descriptor)) {
5082
+ if (addIssue({ kind: "Accessor", path: dataPathToArray(childPath) })) {
5083
+ break;
5084
+ }
5085
+ continue;
5086
+ }
5087
+ if (!descriptor.enumerable) {
5088
+ if (addIssue({
5089
+ kind: "NonEnumerable",
5090
+ path: dataPathToArray(childPath),
5091
+ })) {
5092
+ break;
5093
+ }
5094
+ continue;
5095
+ }
5096
+ children.push({ value: descriptor.value, path: childPath });
5097
+ }
5098
+ }
5099
+ else if (kind === "Date") {
5100
+ visited.add(value);
5101
+ continue;
5102
+ }
5103
+ else if (kind === "Uint8Array") {
5104
+ try {
5105
+ void globalThis.Uint8Array.prototype.values.call(value);
5106
+ }
5107
+ catch {
5108
+ if (addIssue({
5109
+ kind: "InvalidUint8Array",
5110
+ path: dataPathToArray(path),
5111
+ value: value,
5112
+ })) {
5113
+ break;
5114
+ }
5115
+ }
5116
+ visited.add(value);
5117
+ continue;
5118
+ }
5119
+ else if (kind === "Set") {
5120
+ for (const key of Reflect.ownKeys(value)) {
5121
+ if (addIssue({
5122
+ kind: "ExcessProperty",
5123
+ path: dataPathToArray(dataChildPath(path, key)),
5124
+ container: "Set",
5125
+ })) {
5126
+ break;
5127
+ }
5128
+ }
5129
+ if (issues !== undefined && options.errors === "first")
5130
+ break;
5131
+ let index = 0;
5132
+ for (const item of value) {
5133
+ children.push({ value: item, path: dataChildPath(path, index++) });
5134
+ }
5135
+ }
5136
+ else if (kind === "Map") {
5137
+ for (const key of Reflect.ownKeys(value)) {
5138
+ if (addIssue({
5139
+ kind: "ExcessProperty",
5140
+ path: dataPathToArray(dataChildPath(path, key)),
5141
+ container: "Map",
5142
+ })) {
5143
+ break;
5144
+ }
5145
+ }
5146
+ if (issues !== undefined && options.errors === "first")
5147
+ break;
5148
+ let index = 0;
5149
+ for (const [key, item] of value) {
5150
+ const entryPath = dataChildPath(path, index++);
5151
+ children.push({ value: key, path: dataChildPath(entryPath, "key") });
5152
+ children.push({
5153
+ value: item,
5154
+ path: dataChildPath(entryPath, "value"),
5155
+ });
5156
+ }
5157
+ }
5158
+ else if (addIssue({
5159
+ kind: "UnexpectedPrototype",
5160
+ path: dataPathToArray(path),
5161
+ container: "Object",
5162
+ value,
5163
+ })) {
5164
+ break;
5165
+ }
5166
+ if (issues !== undefined && options.errors === "first")
5167
+ break;
5168
+ visited.add(value);
5169
+ for (let index = children.length - 1; index >= 0; index--) {
5170
+ work.push(children[index]);
5171
+ }
5172
+ }
5173
+ return issues === undefined
5174
+ ? ok(value)
5175
+ : err({
5176
+ type: "Data",
5177
+ reason: {
5178
+ kind: "Issues",
5179
+ issues: globalThis.Object.freeze(issues),
5180
+ },
5181
+ });
5182
+ };
5183
+ const getDataRuntimeTypeIssues = (error, mode) => {
5184
+ const dataError = error;
5185
+ const issues = mode === "first"
5186
+ ? [dataError.reason.issues[0]]
5187
+ : dataError.reason.issues;
5188
+ return issues.map((issue) => ({
5189
+ name: "Data",
5190
+ error: mode === "first"
5191
+ ? error
5192
+ : {
5193
+ type: "Data",
5194
+ reason: { kind: "Issues", issues: [issue] },
5195
+ },
5196
+ path: issue.path,
5197
+ formatError: ((error) => {
5198
+ const issue = error.reason.issues[0];
5199
+ switch (issue.kind) {
5200
+ case "InvalidType":
5201
+ return `A value ${safelyStringifyUnknownValue(issue.value)} is not Data.`;
5202
+ case "UnexpectedPrototype":
5203
+ return `A Data ${issue.container} has an unexpected prototype.`;
5204
+ case "Accessor":
5205
+ return "A Data property must be a data property. Materialize accessor values into plain data before using this Type or use a different Type.";
5206
+ case "NonEnumerable":
5207
+ return "A Data Object property must be enumerable. Remove it or use a different Type.";
5208
+ case "SymbolProperty":
5209
+ return "A Data Object property key must be a string. Remove the symbol property or use a different Type.";
5210
+ case "Hole":
5211
+ return "A Data Array element is missing.";
5212
+ case "InvalidUint8Array":
5213
+ return "A Data Uint8Array must have an attached, in-bounds ArrayBuffer.";
5214
+ case "ExcessProperty":
5215
+ return `A Data ${issue.container} must not have excess own properties. Remove the property or use a different Type.`;
5216
+ }
5217
+ }),
5218
+ }));
5219
+ };
5220
+ /**
5221
+ * Root Type for {@link Data} values.
5222
+ *
5223
+ * Validation is iterative and preserves the input identity. Cyclic and shared
5224
+ * data graphs are valid.
5225
+ *
5226
+ * ### Example
5227
+ *
5228
+ * ```ts
5229
+ * import { assertOk, assertTrue, Data } from "@evolu/common";
5230
+ *
5231
+ * const value = new Map([["count", 1]]);
5232
+ * const result = Data.fromUnknown(value);
5233
+ *
5234
+ * assertOk(result, value);
5235
+ * assertTrue(Data.is(result.value));
5236
+ * ```
5237
+ *
5238
+ * @group Base
5239
+ */
5240
+ export const Data = /*#__PURE__*/ createTypeNode("Data", null, validateData, (value) => validateData(value).ok, validateData, ok, identity, getDataRuntimeTypeIssues);
4579
5241
  const emptyJsonValuePath =
4580
5242
  /*#__PURE__*/ globalThis.Object.freeze([]);
4581
5243
  const jsonValuePathToArray = (path) => {
@@ -4613,6 +5275,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
4613
5275
  }
4614
5276
  if (typeof value === "number") {
4615
5277
  if (!globalThis.Number.isFinite(value)) {
5278
+ // oxlint-disable-next-line unicorn/no-lonely-if -- Keep issue construction separate from the finite-number guard.
4616
5279
  if (addIssue({
4617
5280
  kind: "NonFiniteNumber",
4618
5281
  path: jsonValuePathToArray(path),
@@ -4633,7 +5296,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
4633
5296
  }
4634
5297
  continue;
4635
5298
  }
4636
- const isArray = globalThis.Array.isArray(value);
5299
+ const isArray = Array.isArray(value);
4637
5300
  if (!isArray && !isPlainObject(value)) {
4638
5301
  if (addIssue({
4639
5302
  kind: "UnexpectedPrototype",
@@ -4798,7 +5461,7 @@ const getJsonValueRuntimeTypeIssues = (error, mode) => {
4798
5461
  }),
4799
5462
  }));
4800
5463
  };
4801
- const parseJson = (value) => globalThis.JSON.parse(value);
5464
+ const parseJson = (value) => JSON.parse(value);
4802
5465
  const jsonToJsonValueResult = (value) => {
4803
5466
  const result = trySync(() => parseJson(value));
4804
5467
  return result.ok && validateJsonValue(result.value).ok
@@ -4819,9 +5482,10 @@ const stringifyJsonValue = (value) => {
4819
5482
  chunks.push("null");
4820
5483
  continue;
4821
5484
  }
5485
+ // oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
4822
5486
  switch (typeof value) {
4823
5487
  case "string":
4824
- chunks.push(globalThis.JSON.stringify(value));
5488
+ chunks.push(JSON.stringify(value));
4825
5489
  break;
4826
5490
  case "number":
4827
5491
  chunks.push(globalThis.Object.is(value, -0) ? "-0" : globalThis.String(value));
@@ -4830,7 +5494,7 @@ const stringifyJsonValue = (value) => {
4830
5494
  chunks.push(value ? "true" : "false");
4831
5495
  break;
4832
5496
  case "object": {
4833
- if (globalThis.Array.isArray(value)) {
5497
+ if (Array.isArray(value)) {
4834
5498
  const array = value;
4835
5499
  chunks.push("[");
4836
5500
  work.push({ kind: "Text", value: "]" });
@@ -4851,7 +5515,7 @@ const stringifyJsonValue = (value) => {
4851
5515
  work.push({ kind: "Text", value: ":" });
4852
5516
  work.push({
4853
5517
  kind: "Text",
4854
- value: globalThis.JSON.stringify(key),
5518
+ value: JSON.stringify(key),
4855
5519
  });
4856
5520
  if (index > 0)
4857
5521
  work.push({ kind: "Text", value: "," });
@@ -4868,7 +5532,12 @@ const stringifyJsonValue = (value) => {
4868
5532
  * ### Example
4869
5533
  *
4870
5534
  * ```ts
4871
- * import { JsonValue, type JsonValueInput } from "@evolu/common";
5535
+ * import {
5536
+ * assertOk,
5537
+ * assertType,
5538
+ * JsonValue,
5539
+ * type JsonValueInput,
5540
+ * } from "@evolu/common";
4872
5541
  *
4873
5542
  * const input: JsonValueInput = {
4874
5543
  * name: "Ada",
@@ -4876,8 +5545,8 @@ const stringifyJsonValue = (value) => {
4876
5545
  * };
4877
5546
  * const result = JsonValue.fromUnknown(input);
4878
5547
  *
4879
- * expectOk(result, input);
4880
- * expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
5548
+ * assertOk(result, input);
5549
+ * assertType<JsonValue, typeof result.value>();
4881
5550
  * ```
4882
5551
  *
4883
5552
  * @group JSON
@@ -4915,11 +5584,11 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
4915
5584
  * ### Example
4916
5585
  *
4917
5586
  * ```ts
4918
- * import { Json, jsonToJsonValue } from "@evolu/common";
5587
+ * import { assertEqual, Json, jsonToJsonValue } from "@evolu/common";
4919
5588
  *
4920
5589
  * const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
4921
5590
  *
4922
- * expect(value).toEqual({ name: "Ada" });
5591
+ * assertEqual(value, { name: "Ada" });
4923
5592
  * ```
4924
5593
  *
4925
5594
  * @group JSON
@@ -4931,11 +5600,15 @@ export const jsonToJsonValue = (value) => parseJson(value);
4931
5600
  * ### Example
4932
5601
  *
4933
5602
  * ```ts
4934
- * import { jsonValueToJson, type JsonValue } from "@evolu/common";
5603
+ * import {
5604
+ * assertEqual,
5605
+ * jsonValueToJson,
5606
+ * type JsonValue,
5607
+ * } from "@evolu/common";
4935
5608
  *
4936
5609
  * const value: JsonValue = { name: "Ada" };
4937
5610
  *
4938
- * expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
5611
+ * assertEqual(jsonValueToJson(value), '{"name":"Ada"}');
4939
5612
  * ```
4940
5613
  *
4941
5614
  * @group JSON
@@ -4950,12 +5623,12 @@ export const jsonValueToJson = (value) => stringifyJsonValue(JsonValue.to(value)
4950
5623
  * ### Example
4951
5624
  *
4952
5625
  * ```ts
4953
- * import { JsonValueFromJson } from "@evolu/common";
5626
+ * import { assertEqual, assertOk, JsonValueFromJson } from "@evolu/common";
4954
5627
  *
4955
5628
  * const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
4956
5629
  *
4957
- * expectOk(result, { name: "Ada" });
4958
- * expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
5630
+ * assertOk(result, { name: "Ada" });
5631
+ * assertEqual(JsonValueFromJson.to(result.value), '{"name":"Ada"}');
4959
5632
  * ```
4960
5633
  *
4961
5634
  * @group JSON
@@ -4976,6 +5649,8 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
4976
5649
  *
4977
5650
  * ```ts
4978
5651
  * import {
5652
+ * assertEqual,
5653
+ * assertType,
4979
5654
  * Age,
4980
5655
  * NonEmptyTrimmedString100,
4981
5656
  * json,
@@ -4996,11 +5671,13 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
4996
5671
  * const user = User.orThrow({ name: "Ada", age: 37 });
4997
5672
  * const userJson = userToUserJson(user);
4998
5673
  *
4999
- * expectTypeOf(userJson).toEqualTypeOf<
5000
- * string & Brand<"Json"> & Brand<"UserJson">
5674
+ * assertType<
5675
+ * string & Brand<"Json"> & Brand<"UserJson">,
5676
+ * typeof userJson
5001
5677
  * >();
5002
- * expect(userJson).toBe('{"name":"Ada","age":37}');
5003
- * expect(userJsonToUser(userJson)).toEqual(user);
5678
+ * assertEqual(userJson, '{"name":"Ada","age":37}');
5679
+ * assertEqual(UserJson.orThrow(userJson), userJson);
5680
+ * assertEqual(userJsonToUser(userJson), user);
5004
5681
  * ```
5005
5682
  *
5006
5683
  * The supplied Type must have a JSON-compatible `CanonicalInput`. The branded