@evolu/common 8.5.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 (328) hide show
  1. package/dist/src/Array.d.ts +170 -97
  2. package/dist/src/Array.d.ts.map +1 -1
  3. package/dist/src/Array.js +67 -42
  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 +150 -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/LockManager.d.ts +12 -10
  35. package/dist/src/LockManager.d.ts.map +1 -1
  36. package/dist/src/Lookup.d.ts +4 -2
  37. package/dist/src/Lookup.d.ts.map +1 -1
  38. package/dist/src/Lookup.js +4 -2
  39. package/dist/src/Number.d.ts +28 -24
  40. package/dist/src/Number.d.ts.map +1 -1
  41. package/dist/src/Number.js +16 -17
  42. package/dist/src/Object.d.ts +89 -47
  43. package/dist/src/Object.d.ts.map +1 -1
  44. package/dist/src/Object.js +88 -41
  45. package/dist/src/Option.d.ts +14 -5
  46. package/dist/src/Option.d.ts.map +1 -1
  47. package/dist/src/Option.js +14 -5
  48. package/dist/src/Order.d.ts +12 -12
  49. package/dist/src/Order.js +12 -12
  50. package/dist/src/Platform.d.ts +2 -2
  51. package/dist/src/Random.d.ts +7 -4
  52. package/dist/src/Random.d.ts.map +1 -1
  53. package/dist/src/Redacted.d.ts +13 -6
  54. package/dist/src/Redacted.d.ts.map +1 -1
  55. package/dist/src/Redacted.js +4 -2
  56. package/dist/src/Ref.d.ts +4 -4
  57. package/dist/src/Relation.d.ts +5 -7
  58. package/dist/src/Relation.d.ts.map +1 -1
  59. package/dist/src/Resource.d.ts +27 -12
  60. package/dist/src/Resource.d.ts.map +1 -1
  61. package/dist/src/Result.d.ts +258 -114
  62. package/dist/src/Result.d.ts.map +1 -1
  63. package/dist/src/Result.js +71 -34
  64. package/dist/src/Schedule.d.ts +233 -116
  65. package/dist/src/Schedule.d.ts.map +1 -1
  66. package/dist/src/Schedule.js +204 -110
  67. package/dist/src/Set.d.ts +43 -24
  68. package/dist/src/Set.d.ts.map +1 -1
  69. package/dist/src/Set.js +25 -13
  70. package/dist/src/Sqlite.d.ts +4 -4
  71. package/dist/src/Sqlite.d.ts.map +1 -1
  72. package/dist/src/Sqlite.js +9 -8
  73. package/dist/src/Task.d.ts +441 -212
  74. package/dist/src/Task.d.ts.map +1 -1
  75. package/dist/src/Task.js +238 -115
  76. package/dist/src/Test.d.ts +10 -4
  77. package/dist/src/Test.d.ts.map +1 -1
  78. package/dist/src/Test.js +10 -4
  79. package/dist/src/Time.d.ts +27 -17
  80. package/dist/src/Time.d.ts.map +1 -1
  81. package/dist/src/Time.js +16 -6
  82. package/dist/src/Type.d.ts +1096 -387
  83. package/dist/src/Type.d.ts.map +1 -1
  84. package/dist/src/Type.js +1046 -372
  85. package/dist/src/Types.d.ts +95 -40
  86. package/dist/src/Types.d.ts.map +1 -1
  87. package/dist/src/Types.js +13 -4
  88. package/dist/src/WebSocket.d.ts +14 -6
  89. package/dist/src/WebSocket.d.ts.map +1 -1
  90. package/dist/src/Worker.d.ts +10 -6
  91. package/dist/src/Worker.d.ts.map +1 -1
  92. package/dist/src/intl/_en.d.ts +3 -1
  93. package/dist/src/intl/_en.d.ts.map +1 -1
  94. package/dist/src/intl/_en.js +17 -3
  95. package/dist/src/intl/ar.d.ts +3 -1
  96. package/dist/src/intl/ar.d.ts.map +1 -1
  97. package/dist/src/intl/ar.js +15 -2
  98. package/dist/src/intl/bn.d.ts +3 -1
  99. package/dist/src/intl/bn.d.ts.map +1 -1
  100. package/dist/src/intl/bn.js +16 -3
  101. package/dist/src/intl/ca.d.ts +3 -1
  102. package/dist/src/intl/ca.d.ts.map +1 -1
  103. package/dist/src/intl/ca.js +16 -3
  104. package/dist/src/intl/cs.d.ts +3 -1
  105. package/dist/src/intl/cs.d.ts.map +1 -1
  106. package/dist/src/intl/cs.js +16 -3
  107. package/dist/src/intl/da.d.ts +3 -1
  108. package/dist/src/intl/da.d.ts.map +1 -1
  109. package/dist/src/intl/da.js +16 -3
  110. package/dist/src/intl/de.d.ts +3 -1
  111. package/dist/src/intl/de.d.ts.map +1 -1
  112. package/dist/src/intl/de.js +16 -3
  113. package/dist/src/intl/el.d.ts +3 -1
  114. package/dist/src/intl/el.d.ts.map +1 -1
  115. package/dist/src/intl/el.js +16 -3
  116. package/dist/src/intl/es.d.ts +3 -1
  117. package/dist/src/intl/es.d.ts.map +1 -1
  118. package/dist/src/intl/es.js +16 -3
  119. package/dist/src/intl/fa.d.ts +3 -1
  120. package/dist/src/intl/fa.d.ts.map +1 -1
  121. package/dist/src/intl/fa.js +16 -3
  122. package/dist/src/intl/fi.d.ts +3 -1
  123. package/dist/src/intl/fi.d.ts.map +1 -1
  124. package/dist/src/intl/fi.js +16 -3
  125. package/dist/src/intl/fil.d.ts +3 -1
  126. package/dist/src/intl/fil.d.ts.map +1 -1
  127. package/dist/src/intl/fil.js +16 -3
  128. package/dist/src/intl/fr.d.ts +3 -1
  129. package/dist/src/intl/fr.d.ts.map +1 -1
  130. package/dist/src/intl/fr.js +16 -3
  131. package/dist/src/intl/he.d.ts +3 -1
  132. package/dist/src/intl/he.d.ts.map +1 -1
  133. package/dist/src/intl/he.js +16 -3
  134. package/dist/src/intl/hi.d.ts +3 -1
  135. package/dist/src/intl/hi.d.ts.map +1 -1
  136. package/dist/src/intl/hi.js +16 -3
  137. package/dist/src/intl/hr.d.ts +3 -1
  138. package/dist/src/intl/hr.d.ts.map +1 -1
  139. package/dist/src/intl/hr.js +16 -3
  140. package/dist/src/intl/hu.d.ts +2 -1
  141. package/dist/src/intl/hu.d.ts.map +1 -1
  142. package/dist/src/intl/hu.js +15 -3
  143. package/dist/src/intl/id.d.ts +3 -1
  144. package/dist/src/intl/id.d.ts.map +1 -1
  145. package/dist/src/intl/id.js +16 -3
  146. package/dist/src/intl/it.d.ts +3 -1
  147. package/dist/src/intl/it.d.ts.map +1 -1
  148. package/dist/src/intl/it.js +16 -3
  149. package/dist/src/intl/ja.d.ts +3 -1
  150. package/dist/src/intl/ja.d.ts.map +1 -1
  151. package/dist/src/intl/ja.js +16 -3
  152. package/dist/src/intl/ko.d.ts +3 -1
  153. package/dist/src/intl/ko.d.ts.map +1 -1
  154. package/dist/src/intl/ko.js +16 -3
  155. package/dist/src/intl/ml.d.ts +3 -1
  156. package/dist/src/intl/ml.d.ts.map +1 -1
  157. package/dist/src/intl/ml.js +16 -3
  158. package/dist/src/intl/mr.d.ts +3 -1
  159. package/dist/src/intl/mr.d.ts.map +1 -1
  160. package/dist/src/intl/mr.js +16 -3
  161. package/dist/src/intl/ms.d.ts +3 -1
  162. package/dist/src/intl/ms.d.ts.map +1 -1
  163. package/dist/src/intl/ms.js +15 -2
  164. package/dist/src/intl/nb.d.ts +2 -1
  165. package/dist/src/intl/nb.d.ts.map +1 -1
  166. package/dist/src/intl/nb.js +14 -2
  167. package/dist/src/intl/nl.d.ts +3 -1
  168. package/dist/src/intl/nl.d.ts.map +1 -1
  169. package/dist/src/intl/nl.js +16 -3
  170. package/dist/src/intl/pa.d.ts +3 -1
  171. package/dist/src/intl/pa.d.ts.map +1 -1
  172. package/dist/src/intl/pa.js +16 -3
  173. package/dist/src/intl/pl.d.ts +2 -0
  174. package/dist/src/intl/pl.d.ts.map +1 -1
  175. package/dist/src/intl/pl.js +15 -2
  176. package/dist/src/intl/pt-BR.d.ts +3 -1
  177. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  178. package/dist/src/intl/pt-BR.js +16 -3
  179. package/dist/src/intl/pt.d.ts +3 -1
  180. package/dist/src/intl/pt.d.ts.map +1 -1
  181. package/dist/src/intl/pt.js +15 -2
  182. package/dist/src/intl/ro.d.ts +3 -1
  183. package/dist/src/intl/ro.d.ts.map +1 -1
  184. package/dist/src/intl/ro.js +16 -3
  185. package/dist/src/intl/sk.d.ts +3 -1
  186. package/dist/src/intl/sk.d.ts.map +1 -1
  187. package/dist/src/intl/sk.js +15 -2
  188. package/dist/src/intl/sl.d.ts +3 -1
  189. package/dist/src/intl/sl.d.ts.map +1 -1
  190. package/dist/src/intl/sl.js +16 -3
  191. package/dist/src/intl/sv.d.ts +3 -1
  192. package/dist/src/intl/sv.d.ts.map +1 -1
  193. package/dist/src/intl/sv.js +16 -3
  194. package/dist/src/intl/sw.d.ts +1 -0
  195. package/dist/src/intl/sw.d.ts.map +1 -1
  196. package/dist/src/intl/sw.js +14 -2
  197. package/dist/src/intl/ta.d.ts +3 -1
  198. package/dist/src/intl/ta.d.ts.map +1 -1
  199. package/dist/src/intl/ta.js +16 -3
  200. package/dist/src/intl/te.d.ts +3 -1
  201. package/dist/src/intl/te.d.ts.map +1 -1
  202. package/dist/src/intl/te.js +16 -3
  203. package/dist/src/intl/th.d.ts +3 -1
  204. package/dist/src/intl/th.d.ts.map +1 -1
  205. package/dist/src/intl/th.js +16 -3
  206. package/dist/src/intl/tr.d.ts +3 -1
  207. package/dist/src/intl/tr.d.ts.map +1 -1
  208. package/dist/src/intl/tr.js +16 -3
  209. package/dist/src/intl/uk.d.ts +3 -1
  210. package/dist/src/intl/uk.d.ts.map +1 -1
  211. package/dist/src/intl/uk.js +16 -3
  212. package/dist/src/intl/ur.d.ts +3 -1
  213. package/dist/src/intl/ur.d.ts.map +1 -1
  214. package/dist/src/intl/ur.js +15 -2
  215. package/dist/src/intl/vi.d.ts +3 -1
  216. package/dist/src/intl/vi.d.ts.map +1 -1
  217. package/dist/src/intl/vi.js +16 -3
  218. package/dist/src/intl/zh-CN.d.ts +3 -1
  219. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  220. package/dist/src/intl/zh-CN.js +16 -3
  221. package/dist/src/intl/zh-TW.d.ts +3 -1
  222. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  223. package/dist/src/intl/zh-TW.js +16 -3
  224. package/dist/src/local-first/Evolu.d.ts +47 -24
  225. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  226. package/dist/src/local-first/Owner.d.ts +10 -7
  227. package/dist/src/local-first/Owner.d.ts.map +1 -1
  228. package/dist/src/local-first/Owner.js +10 -7
  229. package/dist/src/local-first/Protocol.d.ts +5 -5
  230. package/dist/src/local-first/Protocol.js +5 -5
  231. package/dist/src/local-first/Query.d.ts +34 -21
  232. package/dist/src/local-first/Query.d.ts.map +1 -1
  233. package/dist/src/local-first/Query.js +25 -13
  234. package/dist/src/local-first/Relay.d.ts +12 -5
  235. package/dist/src/local-first/Relay.d.ts.map +1 -1
  236. package/dist/src/local-first/Schema.d.ts +19 -11
  237. package/dist/src/local-first/Schema.d.ts.map +1 -1
  238. package/dist/src/local-first/Schema.js +11 -6
  239. package/dist/src/local-first/Storage.d.ts +4 -2
  240. package/dist/src/local-first/Storage.d.ts.map +1 -1
  241. package/package.json +3 -4
  242. package/src/Array.ts +170 -97
  243. package/src/Assert.ts +336 -31
  244. package/src/BigInt.ts +3 -3
  245. package/src/Brand.ts +7 -7
  246. package/src/Buffer.ts +12 -4
  247. package/src/Cache.ts +8 -3
  248. package/src/Callbacks.ts +8 -4
  249. package/src/Console.ts +45 -30
  250. package/src/Crypto.ts +13 -8
  251. package/src/Eq.ts +450 -122
  252. package/src/Function.ts +54 -24
  253. package/src/Http.ts +52 -19
  254. package/src/Identicon.ts +9 -4
  255. package/src/LockManager.ts +12 -10
  256. package/src/Lookup.ts +4 -2
  257. package/src/Number.ts +28 -24
  258. package/src/Object.ts +115 -49
  259. package/src/Option.ts +14 -5
  260. package/src/Order.ts +12 -12
  261. package/src/Platform.ts +2 -2
  262. package/src/Random.ts +7 -4
  263. package/src/Redacted.ts +13 -6
  264. package/src/Ref.ts +4 -4
  265. package/src/Relation.ts +5 -7
  266. package/src/Resource.ts +27 -12
  267. package/src/Result.ts +258 -114
  268. package/src/Schedule.ts +233 -116
  269. package/src/Set.ts +43 -24
  270. package/src/Sqlite.ts +9 -8
  271. package/src/Task.ts +441 -212
  272. package/src/Test.ts +10 -4
  273. package/src/Time.ts +27 -17
  274. package/src/Type.ts +2077 -620
  275. package/src/Types.ts +108 -40
  276. package/src/WebSocket.ts +14 -6
  277. package/src/Worker.ts +10 -6
  278. package/src/intl/_en.ts +20 -4
  279. package/src/intl/ar.ts +17 -2
  280. package/src/intl/bn.ts +19 -4
  281. package/src/intl/ca.ts +19 -4
  282. package/src/intl/cs.ts +19 -4
  283. package/src/intl/da.ts +19 -4
  284. package/src/intl/de.ts +19 -4
  285. package/src/intl/el.ts +19 -4
  286. package/src/intl/es.ts +19 -4
  287. package/src/intl/fa.ts +19 -4
  288. package/src/intl/fi.ts +19 -4
  289. package/src/intl/fil.ts +19 -4
  290. package/src/intl/fr.ts +19 -4
  291. package/src/intl/he.ts +19 -4
  292. package/src/intl/hi.ts +19 -4
  293. package/src/intl/hr.ts +19 -4
  294. package/src/intl/hu.ts +18 -4
  295. package/src/intl/id.ts +19 -4
  296. package/src/intl/it.ts +19 -4
  297. package/src/intl/ja.ts +19 -4
  298. package/src/intl/ko.ts +19 -4
  299. package/src/intl/ml.ts +19 -4
  300. package/src/intl/mr.ts +19 -4
  301. package/src/intl/ms.ts +17 -2
  302. package/src/intl/nb.ts +16 -2
  303. package/src/intl/nl.ts +19 -4
  304. package/src/intl/pa.ts +19 -4
  305. package/src/intl/pl.ts +18 -2
  306. package/src/intl/pt-BR.ts +19 -4
  307. package/src/intl/pt.ts +17 -2
  308. package/src/intl/ro.ts +19 -4
  309. package/src/intl/sk.ts +17 -2
  310. package/src/intl/sl.ts +19 -4
  311. package/src/intl/sv.ts +19 -4
  312. package/src/intl/sw.ts +15 -2
  313. package/src/intl/ta.ts +19 -4
  314. package/src/intl/te.ts +19 -4
  315. package/src/intl/th.ts +19 -4
  316. package/src/intl/tr.ts +19 -4
  317. package/src/intl/uk.ts +19 -4
  318. package/src/intl/ur.ts +17 -2
  319. package/src/intl/vi.ts +19 -3
  320. package/src/intl/zh-CN.ts +19 -4
  321. package/src/intl/zh-TW.ts +19 -4
  322. package/src/local-first/Evolu.ts +47 -24
  323. package/src/local-first/Owner.ts +10 -7
  324. package/src/local-first/Protocol.ts +5 -5
  325. package/src/local-first/Query.ts +34 -21
  326. package/src/local-first/Relay.ts +12 -5
  327. package/src/local-first/Schema.ts +19 -11
  328. package/src/local-first/Storage.ts +4 -2
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
  *
@@ -434,7 +456,7 @@ import { wordlist } from "@scure/bip39/wordlists/english.js";
434
456
  import { createMutableArray, } from "./Array.js";
435
457
  import { assert, assertNonNullable } from "./Assert.js";
436
458
  import { identity } from "./Function.js";
437
- import { createMutableRecord } from "./Object.js";
459
+ import { createMutableRecord, getObjectKind, isPlainObject } from "./Object.js";
438
460
  import { hasNodeBuffer } from "./Platform.js";
439
461
  import { err, flatMapResult, getOk, getOrNull, getOrThrow, ok, trySync, } from "./Result.js";
440
462
  import { safelyStringifyUnknownValue } from "./String.js";
@@ -471,33 +493,9 @@ const createCollectionRuntimeTypeIssues = (name, issuesKind, defaultFormatter, g
471
493
  });
472
494
  };
473
495
  const formatDefaultRuntimeTypeIssue = (issue) => issue.formatError(issue.error);
474
- /**
475
- * Asserts that a value belongs to a {@link Type} Output domain.
476
- *
477
- * Use this for internal invariants, not external input. Validate external input
478
- * with `Type.fromUnknown` so validation failures remain typed values. A failed
479
- * assertion uses the Type name for its message and preserves the exact Output
480
- * validation error as the thrown Error's cause.
481
- *
482
- * ### Example
483
- *
484
- * ```ts
485
- * import {
486
- * NonEmptyTrimmedString100,
487
- * assertType,
488
- * type Brand,
489
- * } from "@evolu/common";
490
- *
491
- * const value: unknown = "Evolu";
492
- * assertType(NonEmptyTrimmedString100, value);
493
- * expectTypeOf(value).toEqualTypeOf<
494
- * string & Brand<"Trimmed"> & Brand<"MinLength1"> & Brand<"MaxLength100">
495
- * >();
496
- * ```
497
- *
498
- * @group Core
499
- */
500
- export const assertType = (type, value) => {
496
+ export function assertType(type, value) {
497
+ if (type === undefined)
498
+ return;
501
499
  // TODO: Make assert prepend "Expected " and accept an optional third cause
502
500
  // argument. Then use it here and migrate every other assertion to pass an
503
501
  // expectation fragment.
@@ -506,7 +504,7 @@ export const assertType = (type, value) => {
506
504
  if (!result.ok) {
507
505
  throw new Error(`Expected ${runtimeType.name}.`, { cause: result.error });
508
506
  }
509
- };
507
+ }
510
508
  const assertTypeOutput = (name, is, validateOutput, value, options = firstValidationOptions) => {
511
509
  if (is(value))
512
510
  return;
@@ -539,13 +537,22 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
539
537
  * every locale an app supports also allows language changes without a network
540
538
  * connection.
541
539
  *
542
- * The selected Type map, locale map, and formatter maps must be plain objects
543
- * 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.
544
543
  *
545
544
  * ### Example
546
545
  *
547
546
  * ```ts
548
- * 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";
549
556
  * import { cs } from "@evolu/common/intl";
550
557
  *
551
558
  * const Label = minLength(1)(String);
@@ -560,13 +567,14 @@ const assertTypeOutput = (name, is, validateOutput, value, options = firstValida
560
567
  * },
561
568
  * );
562
569
  *
563
- * expectTypeOf<typeof typesByLocale.cs.Label>().toEqualTypeOf<
564
- * typeof Label
565
- * >();
570
+ * assertType<typeof Label, typeof typesByLocale.cs.Label>();
566
571
  *
567
572
  * const result = typesByLocale.cs.Label.fromUnknown("");
568
- * expectErr(result, { type: "MinLength1", min: 1, value: "" });
569
- * 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),
570
578
  * "Text nesmí být prázdný.",
571
579
  * );
572
580
  * ```
@@ -648,9 +656,7 @@ export const localizeTypes = ((typesByName, formatErrorByTypeByLocale) => {
648
656
  });
649
657
  const getLocalizationMapKeys = (value) => {
650
658
  const errorMessage = "localizeTypes maps must be plain objects with own enumerable string-keyed data properties.";
651
- assert(value !== null && typeof value === "object", errorMessage);
652
- const prototype = globalThis.Object.getPrototypeOf(value);
653
- assert(prototype === null || globalThis.Object.getPrototypeOf(prototype) === null, errorMessage);
659
+ assert(isPlainObject(value), errorMessage);
654
660
  const keys = Reflect.ownKeys(value);
655
661
  for (const key of keys) {
656
662
  const descriptor = globalThis.Object.getOwnPropertyDescriptor(value, key);
@@ -700,12 +706,11 @@ const localizeTypeReflection = (value, formatIssue, localizedTypeBySource) => {
700
706
  value["~evolu/instance"] === "Type") {
701
707
  return withFormatError(value, formatIssue, localizedTypeBySource);
702
708
  }
703
- if (globalThis.Array.isArray(value)) {
709
+ if (Array.isArray(value)) {
704
710
  return value.map((value) => localizeTypeReflection(value, formatIssue, localizedTypeBySource));
705
711
  }
706
- if (value === null || typeof value !== "object" || !isPlainObject(value)) {
712
+ if (!isPlainObject(value))
707
713
  return value;
708
- }
709
714
  const localized = globalThis.Object.create(globalThis.Object.getPrototypeOf(value));
710
715
  for (const key of Reflect.ownKeys(value)) {
711
716
  localized[key] = localizeTypeReflection(value[key], formatIssue, localizedTypeBySource);
@@ -946,16 +951,20 @@ const createTypeOfType = (name) => {
946
951
  * and significant whitespace:
947
952
  *
948
953
  * ```ts
949
- * 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";
950
961
  *
951
962
  * const WireValue100 = maxLength(100)(String);
952
963
  * type WireValue100 = typeof WireValue100.Output;
953
964
  *
954
- * expectTypeOf<WireValue100>().toEqualTypeOf<
955
- * string & Brand<"MaxLength100">
956
- * >();
957
- * expectOk(WireValue100.fromUnknown(""), "");
958
- * expectOk(WireValue100.fromUnknown(" value "), " value ");
965
+ * assertType<string & Brand<"MaxLength100">, WireValue100>();
966
+ * assertOk(WireValue100.fromUnknown(""), "");
967
+ * assertOk(WireValue100.fromUnknown(" value "), " value ");
959
968
  * ```
960
969
  *
961
970
  * @group String
@@ -981,7 +990,12 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
981
990
  *
982
991
  * ```ts
983
992
  * import {
993
+ * assertEqual,
994
+ * assertErr,
995
+ * assertOk,
996
+ * assertType,
984
997
  * Age,
998
+ * Data,
985
999
  * FiniteNumber,
986
1000
  * Int,
987
1001
  * NonNaNNumber,
@@ -991,36 +1005,41 @@ export const String = /*#__PURE__*/ createTypeOfType("String");
991
1005
  * } from "@evolu/common";
992
1006
  *
993
1007
  * // Note how every additional constraint accumulates its Brand.
994
- * expectTypeOf<typeof Number.Output>().toEqualTypeOf<number>();
995
- * expectTypeOf<typeof NonNaNNumber.Output>().toEqualTypeOf<
996
- * number & Brand<"NonNaN">
997
- * >();
998
- * expectTypeOf<typeof FiniteNumber.Output>().toEqualTypeOf<
999
- * 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
1000
1013
  * >();
1001
- * expectTypeOf<typeof Int.Output>().toEqualTypeOf<
1002
- * number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int">
1014
+ * assertType<
1015
+ * number & Brand<"NonNaN"> & Brand<"Finite"> & Brand<"Int">,
1016
+ * typeof Int.Output
1003
1017
  * >();
1004
- * expectTypeOf<typeof NonNegativeInt.Output>().toEqualTypeOf<
1018
+ * assertType<
1005
1019
  * number &
1006
1020
  * Brand<"NonNaN"> &
1007
1021
  * Brand<"Finite"> &
1008
1022
  * Brand<"Int"> &
1009
- * Brand<"NonNegative">
1023
+ * Brand<"NonNegative">,
1024
+ * typeof NonNegativeInt.Output
1010
1025
  * >();
1011
1026
  *
1012
- * expectTypeOf<typeof Age.Output>().toEqualTypeOf<
1027
+ * assertType<
1013
1028
  * number &
1014
1029
  * Brand<"NonNaN"> &
1015
1030
  * Brand<"Finite"> &
1016
1031
  * Brand<"Int"> &
1017
1032
  * Brand<"NonNegative"> &
1018
1033
  * Brand<"LessThan200"> &
1019
- * Brand<"Age">
1034
+ * Brand<"Age">,
1035
+ * typeof Age.Output
1020
1036
  * >();
1021
1037
  *
1022
- * expectOk(Age.fromUnknown(122), 122);
1023
- * 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, {
1024
1043
  * type: "LessThan200",
1025
1044
  * value: 200,
1026
1045
  * max: 200,
@@ -1117,7 +1136,7 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
1117
1136
  * ### Example
1118
1137
  *
1119
1138
  * ```ts
1120
- * import { instanceOf } from "@evolu/common";
1139
+ * import { assertFalse, assertTrue, instanceOf } from "@evolu/common";
1121
1140
  *
1122
1141
  * class User {
1123
1142
  * readonly name: string;
@@ -1129,8 +1148,8 @@ export const ArrayBuffer = /*#__PURE__*/ objectTag("ArrayBuffer");
1129
1148
  *
1130
1149
  * const UserInstance = instanceOf(User);
1131
1150
  *
1132
- * assert(UserInstance.is(new User("Ada")));
1133
- * assert(!UserInstance.is({ name: "Ada" }));
1151
+ * assertTrue(UserInstance.is(new User("Ada")));
1152
+ * assertFalse(UserInstance.is({ name: "Ada" }));
1134
1153
  * ```
1135
1154
  *
1136
1155
  * @group Base
@@ -1157,13 +1176,23 @@ export const instanceOf = (constructor) => {
1157
1176
  * ### Example
1158
1177
  *
1159
1178
  * ```ts
1160
- * import { literal } from "@evolu/common";
1179
+ * import {
1180
+ * assertEqual,
1181
+ * assertErr,
1182
+ * assertOk,
1183
+ * assertType,
1184
+ * Data,
1185
+ * literal,
1186
+ * } from "@evolu/common";
1161
1187
  *
1162
1188
  * const Ready = literal("ready");
1163
1189
  *
1164
- * expectTypeOf<typeof Ready.Output>().toEqualTypeOf<"ready">();
1165
- * expectOk(Ready.fromUnknown("ready"), "ready");
1166
- * 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, {
1167
1196
  * type: "Literal",
1168
1197
  * expected: "ready",
1169
1198
  * value: "pending",
@@ -1258,11 +1287,11 @@ const createUnionValidation = (members, validateMember) => (value, options = fir
1258
1287
  * ### Example
1259
1288
  *
1260
1289
  * ```ts
1261
- * import { String, undefinedOr } from "@evolu/common";
1290
+ * import { assertOk, String, undefinedOr } from "@evolu/common";
1262
1291
  *
1263
1292
  * const StringOrUndefined = undefinedOr(String);
1264
1293
  *
1265
- * expectOk(StringOrUndefined.fromUnknown(undefined), undefined);
1294
+ * assertOk(StringOrUndefined.fromUnknown(undefined), undefined);
1266
1295
  * ```
1267
1296
  *
1268
1297
  * @group Unions
@@ -1274,11 +1303,11 @@ export const undefinedOr = (type) => union(type, Undefined);
1274
1303
  * ### Example
1275
1304
  *
1276
1305
  * ```ts
1277
- * import { String, nullOr } from "@evolu/common";
1306
+ * import { assertOk, String, nullOr } from "@evolu/common";
1278
1307
  *
1279
1308
  * const NullableString = nullOr(String);
1280
1309
  *
1281
- * expectOk(NullableString.fromUnknown(null), null);
1310
+ * assertOk(NullableString.fromUnknown(null), null);
1282
1311
  * ```
1283
1312
  *
1284
1313
  * @group Unions
@@ -1290,20 +1319,18 @@ export const nullOr = (type) => union(type, Null);
1290
1319
  * ### Example
1291
1320
  *
1292
1321
  * ```ts
1293
- * import { String, nullishOr } from "@evolu/common";
1322
+ * import { assertOk, String, nullishOr } from "@evolu/common";
1294
1323
  *
1295
1324
  * const NullishString = nullishOr(String);
1296
1325
  *
1297
- * expectOk(NullishString.fromUnknown(undefined), undefined);
1298
- * expectOk(NullishString.fromUnknown(null), null);
1326
+ * assertOk(NullishString.fromUnknown(undefined), undefined);
1327
+ * assertOk(NullishString.fromUnknown(null), null);
1299
1328
  * ```
1300
1329
  *
1301
1330
  * @group Unions
1302
1331
  */
1303
1332
  export const nullishOr = (type) => union(type, Null, Undefined);
1304
- const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1305
- "members" in type &&
1306
- globalThis.Array.isArray(type.members);
1333
+ const isRuntimeUnionTypeNode = (type) => type.name === "Union" && "members" in type && Array.isArray(type.members);
1307
1334
  /**
1308
1335
  * Template literal {@link Type} that parses canonical strings into Tuples.
1309
1336
  *
@@ -1339,7 +1366,16 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1339
1366
  * the structured data decoded from it:
1340
1367
  *
1341
1368
  * ```ts
1342
- * 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";
1343
1379
  *
1344
1380
  * const Language = union("en", "cs");
1345
1381
  * const Region = union("US", "CZ");
@@ -1349,36 +1385,39 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1349
1385
  *
1350
1386
  * // Output is the decoded language and region.
1351
1387
  * type SupportedLocale = typeof SupportedLocale.Output;
1352
- * expectTypeOf<SupportedLocale>().toEqualTypeOf<
1353
- * readonly ["en" | "cs", "US" | "CZ"]
1354
- * >();
1388
+ * assertType<readonly ["en" | "cs", "US" | "CZ"], SupportedLocale>();
1355
1389
  *
1356
1390
  * // The parent Output is the canonical locale string.
1357
1391
  * type SupportedLocaleLiteral = typeof SupportedLocale.parent.Output;
1358
- * expectTypeOf<SupportedLocaleLiteral>().toEqualTypeOf<
1359
- * "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
1392
+ * assertType<
1393
+ * "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
1394
+ * SupportedLocaleLiteral
1360
1395
  * >();
1361
1396
  *
1362
1397
  * // Parse an unknown string into structured data.
1363
1398
  * const result = SupportedLocale.fromUnknown("cs-CZ");
1364
- * expectOk(result, ["cs", "CZ"]);
1399
+ * assertOk(result, ["cs", "CZ"]);
1365
1400
  * const locale = result.value;
1366
- * expectTypeOf(locale).toEqualTypeOf<SupportedLocale>();
1367
- * 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, {
1368
1407
  * type: "TemplateLiteral",
1369
1408
  * value: "cs/CZ",
1370
1409
  * });
1371
1410
  *
1372
1411
  * // Encode structured data into its canonical string.
1373
1412
  * const localeLiteral = SupportedLocale.to(locale);
1374
- * expectTypeOf(localeLiteral).toEqualTypeOf<SupportedLocaleLiteral>();
1375
- * expect(localeLiteral).toBe("cs-CZ");
1413
+ * assertType<SupportedLocaleLiteral, typeof localeLiteral>();
1414
+ * assertEqual(localeLiteral, "cs-CZ");
1376
1415
  *
1377
1416
  * // Validate a string configuration value.
1378
1417
  * const configValue: unknown = "cs-CZ";
1379
- * assert(SupportedLocale.parent.is(configValue));
1380
- * expectTypeOf(configValue).toEqualTypeOf<SupportedLocaleLiteral>();
1381
- * 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"));
1382
1421
  * ```
1383
1422
  *
1384
1423
  * `SupportedLocale` is structured data for application code.
@@ -1391,6 +1430,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1391
1430
  *
1392
1431
  * ```ts
1393
1432
  * import {
1433
+ * assertEqual,
1434
+ * assertOk,
1394
1435
  * NonNegativeDecimalString,
1395
1436
  * templateLiteralParser,
1396
1437
  * } from "@evolu/common";
@@ -1403,8 +1444,8 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1403
1444
  * // DecimalText.to requires a validated NonNegativeDecimalString.
1404
1445
  * const zero = NonNegativeDecimalString.orThrow("0");
1405
1446
  *
1406
- * expectOk(DecimalText.fromUnknown("decimal:0"), [zero]);
1407
- * expect(DecimalText.to([zero])).toBe("decimal:0");
1447
+ * assertOk(DecimalText.fromUnknown("decimal:0"), [zero]);
1448
+ * assertEqual(DecimalText.to([zero]), "decimal:0");
1408
1449
  * ```
1409
1450
  *
1410
1451
  * Capture Types (the Type arguments passed to `templateLiteralParser`) can use
@@ -1412,6 +1453,9 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1412
1453
  *
1413
1454
  * ```ts
1414
1455
  * import {
1456
+ * assertEqual,
1457
+ * assertOk,
1458
+ * assertType,
1415
1459
  * Int64FromInt64String,
1416
1460
  * templateLiteralParser,
1417
1461
  * } from "@evolu/common";
@@ -1422,24 +1466,29 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1422
1466
  *
1423
1467
  * // Decode the string into structured data.
1424
1468
  * const result = ItemId.fromUnknown("item-42");
1425
- * expectOk(result, [42n]);
1469
+ * assertOk(result, [42n]);
1426
1470
  * const itemId = result.value;
1427
- * expectTypeOf(itemId).toEqualTypeOf<ItemId>();
1471
+ * assertType<ItemId, typeof itemId>();
1428
1472
  *
1429
1473
  * // Encode the structured data into its canonical string.
1430
1474
  * const itemIdLiteral = ItemId.to(itemId);
1431
- * expectTypeOf(itemIdLiteral).toEqualTypeOf<ItemIdLiteral>();
1432
- * expect(itemIdLiteral).toBe("item-42");
1475
+ * assertType<ItemIdLiteral, typeof itemIdLiteral>();
1476
+ * assertEqual(itemIdLiteral, "item-42");
1433
1477
  *
1434
1478
  * // TypeScript cannot prove from the literal alone that "42" is a valid Int64 encoding.
1435
1479
  * // @ts-expect-error Validate it with ItemId.parent or create it with ItemId.to.
1436
- * const invalidItemIdLiteral: ItemIdLiteral = "item-42";
1480
+ * const _invalidItemIdLiteral: ItemIdLiteral = "item-42";
1437
1481
  * ```
1438
1482
  *
1439
1483
  * Fixed-width captures can be adjacent:
1440
1484
  *
1441
1485
  * ```ts
1442
- * import { templateLiteralParser, union } from "@evolu/common";
1486
+ * import {
1487
+ * assertEqual,
1488
+ * assertOk,
1489
+ * templateLiteralParser,
1490
+ * union,
1491
+ * } from "@evolu/common";
1443
1492
  *
1444
1493
  * const Digit = union("0", "1", "2", "3", "4", "5", "6", "7", "8", "9");
1445
1494
  * const TwoDigits = templateLiteralParser(Digit, Digit);
@@ -1449,10 +1498,10 @@ const isRuntimeUnionTypeNode = (type) => type.name === "Union" &&
1449
1498
  * const twoDigits: TwoDigits = ["4", "2"];
1450
1499
  * const twoDigitsLiteral: TwoDigitsLiteral = "42";
1451
1500
  * // @ts-expect-error TwoDigitsLiteral requires exactly two digits.
1452
- * const threeDigitsLiteral: TwoDigitsLiteral = "123";
1501
+ * const _threeDigitsLiteral: TwoDigitsLiteral = "123";
1453
1502
  *
1454
- * expectOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
1455
- * expect(TwoDigits.to(twoDigits)).toBe(twoDigitsLiteral);
1503
+ * assertOk(TwoDigits.from.parent(twoDigitsLiteral), twoDigits);
1504
+ * assertEqual(TwoDigits.to(twoDigits), twoDigitsLiteral);
1456
1505
  * ```
1457
1506
  *
1458
1507
  * TypeScript rejects multiple variable-width captures because their encoded
@@ -1583,17 +1632,24 @@ const createTemplateLiteralParserType = (templateParts) => {
1583
1632
  * ### Example
1584
1633
  *
1585
1634
  * ```ts
1586
- * import { templateLiteral, union } from "@evolu/common";
1635
+ * import {
1636
+ * assertFalse,
1637
+ * assertOk,
1638
+ * assertType,
1639
+ * templateLiteral,
1640
+ * union,
1641
+ * } from "@evolu/common";
1587
1642
  *
1588
1643
  * const Language = union("en", "cs");
1589
1644
  * const Region = union("US", "CZ");
1590
1645
  * const Locale = templateLiteral(Language, "-", Region);
1591
1646
  *
1592
- * expectTypeOf<typeof Locale.Output>().toEqualTypeOf<
1593
- * "en-US" | "en-CZ" | "cs-US" | "cs-CZ"
1647
+ * assertType<
1648
+ * "en-US" | "en-CZ" | "cs-US" | "cs-CZ",
1649
+ * typeof Locale.Output
1594
1650
  * >();
1595
- * expectOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
1596
- * expect(Locale.is("fr-CZ")).toBe(false);
1651
+ * assertOk(Locale.fromUnknown("cs-CZ"), "cs-CZ");
1652
+ * assertFalse(Locale.is("fr-CZ"));
1597
1653
  * ```
1598
1654
  *
1599
1655
  * @group Template literals
@@ -1609,7 +1665,7 @@ const compileTemplateLiteralParser = (parts) => {
1609
1665
  return [part, partFraming.width];
1610
1666
  });
1611
1667
  return (input) => {
1612
- const inputCodePoints = globalThis.Array.from(input);
1668
+ const inputCodePoints = Array.from(input);
1613
1669
  const variableWidth = inputCodePoints.length - fixedPartsWidth;
1614
1670
  if (variableWidth < 0) {
1615
1671
  return err({ type: "TemplateLiteral", value: input });
@@ -1665,7 +1721,7 @@ const getStringTemplateLiteralFraming = (value) => {
1665
1721
  const firstCodeUnit = value.charCodeAt(0);
1666
1722
  const lastCodeUnit = value.charCodeAt(value.length - 1);
1667
1723
  return {
1668
- width: globalThis.Array.from(value).length,
1724
+ width: Array.from(value).length,
1669
1725
  canBeEmpty: value.length === 0,
1670
1726
  canStartWithLowSurrogate: firstCodeUnit >= 0xdc00 && firstCodeUnit <= 0xdfff,
1671
1727
  canEndWithHighSurrogate: lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff,
@@ -1719,13 +1775,21 @@ export function brand(name, parent, validate, formatError) {
1719
1775
  * ### Example
1720
1776
  *
1721
1777
  * ```ts
1722
- * import { DateIso } from "@evolu/common";
1778
+ * import {
1779
+ * assertEqual,
1780
+ * assertErr,
1781
+ * assertOk,
1782
+ * assertType,
1783
+ * Data,
1784
+ * DateIso,
1785
+ * } from "@evolu/common";
1723
1786
  *
1724
- * expectOk(
1725
- * DateIso.fromUnknown("2023-01-01T12:00:00.000Z"),
1726
- * "2023-01-01T12:00:00.000Z",
1727
- * );
1728
- * 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, {
1729
1793
  * type: "DateIso",
1730
1794
  * value: "2023-01-01",
1731
1795
  * });
@@ -1742,13 +1806,13 @@ export const DateIso = /*#__PURE__*/ brand("DateIso", String, (value) => value.l
1742
1806
  * ### Example
1743
1807
  *
1744
1808
  * ```ts
1745
- * import { DateIsoFromDate } from "@evolu/common";
1809
+ * import { assertEqual, assertOk, DateIsoFromDate } from "@evolu/common";
1746
1810
  *
1747
- * const date = new globalThis.Date("2025-01-01T12:00:00.000Z");
1811
+ * const date = new Date("2025-01-01T12:00:00.000Z");
1748
1812
  * const result = DateIsoFromDate.fromUnknown(date);
1749
1813
  *
1750
- * expectOk(result, "2025-01-01T12:00:00.000Z");
1751
- * expect(DateIsoFromDate.to(result.value)).toEqual(date);
1814
+ * assertOk(result, "2025-01-01T12:00:00.000Z");
1815
+ * assertEqual(DateIsoFromDate.to(result.value), date);
1752
1816
  * ```
1753
1817
  *
1754
1818
  * @group String
@@ -1781,17 +1845,27 @@ export const UInt64 = /*#__PURE__*/ brand("UInt64", BigInt, (value) => globalThi
1781
1845
  * ### Example
1782
1846
  *
1783
1847
  * ```ts
1784
- * 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";
1785
1858
  *
1786
1859
  * const CapitalizedString = capitalized(String);
1787
1860
  * type CapitalizedString = typeof CapitalizedString.Output;
1788
1861
  *
1789
- * expectTypeOf<CapitalizedString>().toEqualTypeOf<
1790
- * string & Brand<"Capitalized">
1791
- * >();
1862
+ * assertType<string & Brand<"Capitalized">, CapitalizedString>();
1792
1863
  *
1793
- * expectOk(CapitalizedString.fromUnknown("Evolu"), "Evolu");
1794
- * 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, {
1795
1869
  * type: "Capitalized",
1796
1870
  * value: "evolu",
1797
1871
  * });
@@ -1817,11 +1891,11 @@ export const CapitalizedString = /*#__PURE__*/ capitalized(String);
1817
1891
  * ### Example
1818
1892
  *
1819
1893
  * ```ts
1820
- * import { String, trimmed } from "@evolu/common";
1894
+ * import { assertOk, String, trimmed } from "@evolu/common";
1821
1895
  *
1822
1896
  * const Trimmed = trimmed(String);
1823
1897
  *
1824
- * expectOk(Trimmed.fromUnknown("Evolu"), "Evolu");
1898
+ * assertOk(Trimmed.fromUnknown("Evolu"), "Evolu");
1825
1899
  * ```
1826
1900
  *
1827
1901
  * @group String
@@ -1847,9 +1921,9 @@ export const TrimmedString = /*#__PURE__*/ trimmed(String);
1847
1921
  * ### Example
1848
1922
  *
1849
1923
  * ```ts
1850
- * import { trim } from "@evolu/common";
1924
+ * import { assertEqual, trim } from "@evolu/common";
1851
1925
  *
1852
- * expect(trim(" Evolu ")).toBe("Evolu");
1926
+ * assertEqual(trim(" Evolu "), "Evolu");
1853
1927
  * ```
1854
1928
  *
1855
1929
  * @group String
@@ -1861,13 +1935,13 @@ export const trim = (value) => value.trim();
1861
1935
  * ### Example
1862
1936
  *
1863
1937
  * ```ts
1864
- * import { String, array, minLength } from "@evolu/common";
1938
+ * import { assertOk, String, array, minLength } from "@evolu/common";
1865
1939
  *
1866
1940
  * const AtLeastThreeCharacters = minLength(3)(String);
1867
1941
  * const AtLeastTwoItems = minLength(2)(array(String));
1868
1942
  *
1869
- * expectOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
1870
- * expectOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1943
+ * assertOk(AtLeastThreeCharacters.fromUnknown("abc"), "abc");
1944
+ * assertOk(AtLeastTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1871
1945
  * ```
1872
1946
  *
1873
1947
  * @group String
@@ -1900,13 +1974,13 @@ export const NonEmptyTrimmedString = /*#__PURE__*/ minLength(1)(TrimmedString);
1900
1974
  * ### Example
1901
1975
  *
1902
1976
  * ```ts
1903
- * import { String, array, maxLength } from "@evolu/common";
1977
+ * import { assertOk, String, array, maxLength } from "@evolu/common";
1904
1978
  *
1905
1979
  * const AtMostThreeCharacters = maxLength(3)(String);
1906
1980
  * const AtMostTwoItems = maxLength(2)(array(String));
1907
1981
  *
1908
- * expectOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
1909
- * expectOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1982
+ * assertOk(AtMostThreeCharacters.fromUnknown("abc"), "abc");
1983
+ * assertOk(AtMostTwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1910
1984
  * ```
1911
1985
  *
1912
1986
  * @group String
@@ -1936,13 +2010,13 @@ export const NonEmptyTrimmedString1000 = /*#__PURE__*/ maxLength(1000)(NonEmptyT
1936
2010
  * ### Example
1937
2011
  *
1938
2012
  * ```ts
1939
- * import { String, array, length } from "@evolu/common";
2013
+ * import { assertOk, String, array, length } from "@evolu/common";
1940
2014
  *
1941
2015
  * const ThreeCharacters = length(3)(String);
1942
2016
  * const TwoItems = length(2)(array(String));
1943
2017
  *
1944
- * expectOk(ThreeCharacters.fromUnknown("abc"), "abc");
1945
- * expectOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
2018
+ * assertOk(ThreeCharacters.fromUnknown("abc"), "abc");
2019
+ * assertOk(TwoItems.fromUnknown(["a", "b"]), ["a", "b"]);
1946
2020
  * ```
1947
2021
  *
1948
2022
  * @group String
@@ -1962,21 +2036,34 @@ export const length = (exact) => (parent) => {
1962
2036
  * A non-empty string using the URL-safe alphabet:
1963
2037
  *
1964
2038
  * ```ts
1965
- * 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";
1966
2049
  *
1967
- * const UrlSafeString = regex("UrlSafeString", /^[A-Za-z0-9_-]+$/)(String);
2050
+ * const UrlSafeString = regex(
2051
+ * "UrlSafeString",
2052
+ * /^[A-Za-z0-9_-]+$/u,
2053
+ * )(String);
1968
2054
  * type UrlSafeString = typeof UrlSafeString.Output;
1969
2055
  *
1970
- * expectTypeOf<UrlSafeString>().toEqualTypeOf<
1971
- * string & Brand<"UrlSafeString">
1972
- * >();
2056
+ * assertType<string & Brand<"UrlSafeString">, UrlSafeString>();
1973
2057
  *
1974
- * expectOk(UrlSafeString.fromUnknown("abc-123_DEF"), "abc-123_DEF");
1975
- * 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, {
1976
2063
  * type: "UrlSafeString",
1977
2064
  * value: "not safe",
1978
2065
  * source: "^[A-Za-z0-9_-]+$",
1979
- * flags: "",
2066
+ * flags: "u",
1980
2067
  * });
1981
2068
  * ```
1982
2069
  *
@@ -2021,8 +2108,8 @@ const uint8ArrayToBase64UrlString = (bytes) => {
2021
2108
  if ("toBase64" in uint8ArrayPrototype) {
2022
2109
  return bytes.toBase64(base64UrlOptions);
2023
2110
  }
2024
- const binaryString = globalThis.Array.from(bytes, (byte) => globalThis.String.fromCodePoint(byte)).join("");
2025
- const base64 = globalThis.btoa(binaryString);
2111
+ const binaryString = Array.from(bytes, (byte) => globalThis.String.fromCodePoint(byte)).join("");
2112
+ const base64 = btoa(binaryString);
2026
2113
  return base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
2027
2114
  };
2028
2115
  const base64UrlStringToUint8Array = (value) => {
@@ -2037,7 +2124,7 @@ const base64UrlStringToUint8Array = (value) => {
2037
2124
  let base64 = value.replaceAll("-", "+").replaceAll("_", "/");
2038
2125
  while (base64.length % 4 !== 0)
2039
2126
  base64 += "=";
2040
- const binaryString = globalThis.atob(base64);
2127
+ const binaryString = atob(base64);
2041
2128
  return globalThis.Uint8Array.from(binaryString, (character) => character.charCodeAt(0));
2042
2129
  };
2043
2130
  /**
@@ -2060,9 +2147,10 @@ export const Base64Url = /*#__PURE__*/ brand("Base64Url", String, (value) => {
2060
2147
  * ### Example
2061
2148
  *
2062
2149
  * ```ts
2063
- * import { uint8ArrayToBase64Url } from "@evolu/common";
2150
+ * import { assertEqual, uint8ArrayToBase64Url } from "@evolu/common";
2064
2151
  *
2065
- * expect(uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255]))).toBe(
2152
+ * assertEqual(
2153
+ * uint8ArrayToBase64Url(new Uint8Array([0, 1, 2, 255])),
2066
2154
  * "AAEC_w",
2067
2155
  * );
2068
2156
  * ```
@@ -2076,11 +2164,16 @@ export const uint8ArrayToBase64Url = (bytes) => uint8ArrayToBase64UrlString(byte
2076
2164
  * ### Example
2077
2165
  *
2078
2166
  * ```ts
2079
- * import { Base64Url, base64UrlToUint8Array } from "@evolu/common";
2167
+ * import {
2168
+ * assertEqual,
2169
+ * Base64Url,
2170
+ * base64UrlToUint8Array,
2171
+ * } from "@evolu/common";
2080
2172
  *
2081
2173
  * const value = Base64Url.orThrow("AAEC_w");
2082
2174
  *
2083
- * expect(base64UrlToUint8Array(value)).toEqual(
2175
+ * assertEqual(
2176
+ * base64UrlToUint8Array(value),
2084
2177
  * new Uint8Array([0, 1, 2, 255]),
2085
2178
  * );
2086
2179
  * ```
@@ -2138,6 +2231,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
2138
2231
  *
2139
2232
  * ```ts
2140
2233
  * import {
2234
+ * assertTrue,
2235
+ * assertType,
2141
2236
  * Id,
2142
2237
  * createId,
2143
2238
  * createRandomBytes,
@@ -2146,8 +2241,8 @@ export const Id = /*#__PURE__*/ brand("Id", String, (value) => value.length ===
2146
2241
  *
2147
2242
  * const userId = createId<"User">({ randomBytes: createRandomBytes() });
2148
2243
  *
2149
- * expect(Id.is(userId)).toBe(true);
2150
- * expectTypeOf(userId).toEqualTypeOf<Id & Brand<"User">>();
2244
+ * assertTrue(Id.is(userId));
2245
+ * assertType<Id & Brand<"User">, typeof userId>();
2151
2246
  * ```
2152
2247
  *
2153
2248
  * @group String
@@ -2163,14 +2258,20 @@ export const createId = (deps, ..._validation) => uint8ArrayToBase64Url(deps.ran
2163
2258
  * ### Example
2164
2259
  *
2165
2260
  * ```ts
2166
- * 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";
2167
2268
  *
2168
2269
  * const first = createIdFromString("external-user-123");
2169
2270
  * const second = createIdFromString("external-user-123");
2170
2271
  * const todoId = createIdFromString<"Todo">("external-todo-456");
2171
2272
  *
2172
- * expect(first).toBe(second);
2173
- * expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
2273
+ * assertEqual(first, second);
2274
+ * assertType<Id & Brand<"Todo">, typeof todoId>();
2174
2275
  * ```
2175
2276
  *
2176
2277
  * @group String
@@ -2188,6 +2289,7 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
2188
2289
  *
2189
2290
  * ```ts
2190
2291
  * import {
2292
+ * assertEqual,
2191
2293
  * createIdAsUuidv7,
2192
2294
  * createRandomBytes,
2193
2295
  * createTime,
@@ -2200,8 +2302,8 @@ export const createIdFromString = (value, ..._validation) => idBytesToId(sha256(
2200
2302
  * });
2201
2303
  * const bytes = idToIdBytes(value);
2202
2304
  *
2203
- * expect(bytes[6] >> 4).toBe(0x7);
2204
- * expect(bytes[8] & 0xc0).toBe(0x80);
2305
+ * assertEqual(bytes[6] >> 4, 0x7);
2306
+ * assertEqual(bytes[8] & 0xc0, 0x80);
2205
2307
  * ```
2206
2308
  *
2207
2309
  * @group String
@@ -2226,6 +2328,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
2226
2328
  *
2227
2329
  * ```ts
2228
2330
  * import {
2331
+ * assertType,
2229
2332
  * createIdFromString,
2230
2333
  * id,
2231
2334
  * type Brand,
@@ -2235,7 +2338,7 @@ export const createIdAsUuidv7 = (deps, ..._validation) => {
2235
2338
  * const TodoId = id("Todo");
2236
2339
  * const todoId = TodoId.orThrow(createIdFromString("todo"));
2237
2340
  *
2238
- * expectTypeOf(todoId).toEqualTypeOf<Id & Brand<"Todo">>();
2341
+ * assertType<Id & Brand<"Todo">, typeof todoId>();
2239
2342
  * ```
2240
2343
  *
2241
2344
  * @group String
@@ -2265,11 +2368,15 @@ export const idBytesTypeValueLength = 16;
2265
2368
  * ### Example
2266
2369
  *
2267
2370
  * ```ts
2268
- * import { createIdFromString, idToIdBytes } from "@evolu/common";
2371
+ * import {
2372
+ * assertEqual,
2373
+ * createIdFromString,
2374
+ * idToIdBytes,
2375
+ * } from "@evolu/common";
2269
2376
  *
2270
2377
  * const bytes = idToIdBytes(createIdFromString("todo"));
2271
2378
  *
2272
- * expect(bytes).toHaveLength(16);
2379
+ * assertEqual(bytes.length, 16);
2273
2380
  * ```
2274
2381
  *
2275
2382
  * @group String
@@ -2282,6 +2389,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
2282
2389
  *
2283
2390
  * ```ts
2284
2391
  * import {
2392
+ * assertEqual,
2285
2393
  * createIdFromString,
2286
2394
  * idBytesToId,
2287
2395
  * idToIdBytes,
@@ -2289,7 +2397,7 @@ export const idToIdBytes = (value) => base64UrlToUint8Array(value);
2289
2397
  *
2290
2398
  * const value = createIdFromString("todo");
2291
2399
  *
2292
- * expect(idBytesToId(idToIdBytes(value))).toBe(value);
2400
+ * assertEqual(idBytesToId(idToIdBytes(value)), value);
2293
2401
  * ```
2294
2402
  *
2295
2403
  * @group String
@@ -2322,12 +2430,17 @@ export const Int64String = /*#__PURE__*/ brand("Int64String", NonEmptyTrimmedStr
2322
2430
  * ### Example
2323
2431
  *
2324
2432
  * ```ts
2325
- * import { Int64FromInt64String } from "@evolu/common";
2433
+ * import {
2434
+ * assertEqual,
2435
+ * assertOk,
2436
+ * Int64FromInt64String,
2437
+ * } from "@evolu/common";
2326
2438
  *
2327
2439
  * const result = Int64FromInt64String.fromUnknown("9223372036854775807");
2328
2440
  *
2329
- * expectOk(result, 9223372036854775807n);
2330
- * expect(Int64FromInt64String.to(result.value)).toBe(
2441
+ * assertOk(result, 9223372036854775807n);
2442
+ * assertEqual(
2443
+ * Int64FromInt64String.to(result.value),
2331
2444
  * "9223372036854775807",
2332
2445
  * );
2333
2446
  * ```
@@ -2344,11 +2457,11 @@ export const Int64FromInt64String = /*#__PURE__*/ transform("Int64FromInt64Strin
2344
2457
  * ### Example
2345
2458
  *
2346
2459
  * ```ts
2347
- * import { Number, nonNegative } from "@evolu/common";
2460
+ * import { assertOk, Number, nonNegative } from "@evolu/common";
2348
2461
  *
2349
2462
  * const NonNegative = nonNegative(Number);
2350
2463
  *
2351
- * expectOk(NonNegative.fromUnknown(0), 0);
2464
+ * assertOk(NonNegative.fromUnknown(0), 0);
2352
2465
  * ```
2353
2466
  *
2354
2467
  * @group Number
@@ -2366,11 +2479,11 @@ export const NonNegativeNumber = /*#__PURE__*/ nonNegative(Number);
2366
2479
  * ### Example
2367
2480
  *
2368
2481
  * ```ts
2369
- * import { Number, positive } from "@evolu/common";
2482
+ * import { assertOk, Number, positive } from "@evolu/common";
2370
2483
  *
2371
2484
  * const Positive = positive(Number);
2372
2485
  *
2373
- * expectOk(Positive.fromUnknown(1), 1);
2486
+ * assertOk(Positive.fromUnknown(1), 1);
2374
2487
  * ```
2375
2488
  *
2376
2489
  * @group Number
@@ -2391,11 +2504,11 @@ export const PositiveNumber = /*#__PURE__*/ positive(NonNegativeNumber);
2391
2504
  * ### Example
2392
2505
  *
2393
2506
  * ```ts
2394
- * import { Number, nonPositive } from "@evolu/common";
2507
+ * import { assertOk, Number, nonPositive } from "@evolu/common";
2395
2508
  *
2396
2509
  * const NonPositive = nonPositive(Number);
2397
2510
  *
2398
- * expectOk(NonPositive.fromUnknown(0), 0);
2511
+ * assertOk(NonPositive.fromUnknown(0), 0);
2399
2512
  * ```
2400
2513
  *
2401
2514
  * @group Number
@@ -2413,11 +2526,11 @@ export const NonPositiveNumber = /*#__PURE__*/ nonPositive(Number);
2413
2526
  * ### Example
2414
2527
  *
2415
2528
  * ```ts
2416
- * import { Number, negative } from "@evolu/common";
2529
+ * import { assertOk, Number, negative } from "@evolu/common";
2417
2530
  *
2418
2531
  * const Negative = negative(Number);
2419
2532
  *
2420
- * expectOk(Negative.fromUnknown(-1), -1);
2533
+ * assertOk(Negative.fromUnknown(-1), -1);
2421
2534
  * ```
2422
2535
  *
2423
2536
  * @group Number
@@ -2438,11 +2551,11 @@ export const NegativeNumber = /*#__PURE__*/ negative(NonPositiveNumber);
2438
2551
  * ### Example
2439
2552
  *
2440
2553
  * ```ts
2441
- * import { Number, nonNaN } from "@evolu/common";
2554
+ * import { assertOk, Number, nonNaN } from "@evolu/common";
2442
2555
  *
2443
2556
  * const NonNaN = nonNaN(Number);
2444
2557
  *
2445
- * expectOk(NonNaN.fromUnknown(Infinity), Infinity);
2558
+ * assertOk(NonNaN.fromUnknown(Infinity), Infinity);
2446
2559
  * ```
2447
2560
  *
2448
2561
  * @group Number
@@ -2467,11 +2580,11 @@ export const NonNaNNumber = /*#__PURE__*/ nonNaN(Number);
2467
2580
  * ### Example
2468
2581
  *
2469
2582
  * ```ts
2470
- * import { Number, finite } from "@evolu/common";
2583
+ * import { assertOk, Number, finite } from "@evolu/common";
2471
2584
  *
2472
2585
  * const Finite = finite(Number);
2473
2586
  *
2474
- * expectOk(Finite.fromUnknown(42), 42);
2587
+ * assertOk(Finite.fromUnknown(42), 42);
2475
2588
  * ```
2476
2589
  *
2477
2590
  * @group Number
@@ -2506,15 +2619,28 @@ export const PositiveFiniteNumber = /*#__PURE__*/ positive(NonNegativeFiniteNumb
2506
2619
  * ### Example
2507
2620
  *
2508
2621
  * ```ts
2509
- * 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";
2510
2632
  *
2511
2633
  * const Int = int(Number);
2512
2634
  * type Int = typeof Int.Output;
2513
2635
  *
2514
- * expectTypeOf<Int>().toEqualTypeOf<number & Brand<"Int">>();
2636
+ * assertType<number & Brand<"Int">, Int>();
2515
2637
  *
2516
- * expectOk(Int.fromUnknown(42), 42);
2517
- * 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 });
2518
2644
  * ```
2519
2645
  *
2520
2646
  * @group Number
@@ -2583,11 +2709,11 @@ export const NegativeInt = /*#__PURE__*/ negative(NonPositiveInt);
2583
2709
  * ### Example
2584
2710
  *
2585
2711
  * ```ts
2586
- * import { Number, greaterThan } from "@evolu/common";
2712
+ * import { assertOk, Number, greaterThan } from "@evolu/common";
2587
2713
  *
2588
2714
  * const GreaterThanTen = greaterThan(10)(Number);
2589
2715
  *
2590
- * expectOk(GreaterThanTen.fromUnknown(11), 11);
2716
+ * assertOk(GreaterThanTen.fromUnknown(11), 11);
2591
2717
  * ```
2592
2718
  *
2593
2719
  * @group Number
@@ -2604,11 +2730,11 @@ export const greaterThan = (min) => (parent) => {
2604
2730
  * ### Example
2605
2731
  *
2606
2732
  * ```ts
2607
- * import { Number, greaterThanOrEqualTo } from "@evolu/common";
2733
+ * import { assertOk, Number, greaterThanOrEqualTo } from "@evolu/common";
2608
2734
  *
2609
2735
  * const AtLeastTen = greaterThanOrEqualTo(10)(Number);
2610
2736
  *
2611
- * expectOk(AtLeastTen.fromUnknown(10), 10);
2737
+ * assertOk(AtLeastTen.fromUnknown(10), 10);
2612
2738
  * ```
2613
2739
  *
2614
2740
  * @group Number
@@ -2629,11 +2755,11 @@ export const greaterThanOrEqualTo = (min) => (parent) => {
2629
2755
  * ### Example
2630
2756
  *
2631
2757
  * ```ts
2632
- * import { Number, lessThan } from "@evolu/common";
2758
+ * import { assertOk, Number, lessThan } from "@evolu/common";
2633
2759
  *
2634
2760
  * const LessThanTen = lessThan(10)(Number);
2635
2761
  *
2636
- * expectOk(LessThanTen.fromUnknown(9), 9);
2762
+ * assertOk(LessThanTen.fromUnknown(9), 9);
2637
2763
  * ```
2638
2764
  *
2639
2765
  * @group Number
@@ -2657,11 +2783,11 @@ export const Age = /*#__PURE__*/ brand("Age",
2657
2783
  * ### Example
2658
2784
  *
2659
2785
  * ```ts
2660
- * import { Number, lessThanOrEqualTo } from "@evolu/common";
2786
+ * import { assertOk, Number, lessThanOrEqualTo } from "@evolu/common";
2661
2787
  *
2662
2788
  * const AtMostTen = lessThanOrEqualTo(10)(Number);
2663
2789
  *
2664
- * expectOk(AtMostTen.fromUnknown(10), 10);
2790
+ * assertOk(AtMostTen.fromUnknown(10), 10);
2665
2791
  * ```
2666
2792
  *
2667
2793
  * @group Number
@@ -2708,13 +2834,23 @@ export const Ratio = /*#__PURE__*/ brand("Ratio",
2708
2834
  * ### Example
2709
2835
  *
2710
2836
  * ```ts
2711
- * import { DecimalString } from "@evolu/common";
2837
+ * import {
2838
+ * assertEqual,
2839
+ * assertErr,
2840
+ * assertOk,
2841
+ * assertType,
2842
+ * Data,
2843
+ * DecimalString,
2844
+ * } from "@evolu/common";
2712
2845
  *
2713
- * expectOk(DecimalString.fromUnknown("-10.25"), "-10.25");
2714
- * expectOk(DecimalString.fromUnknown("0"), "0");
2715
- * 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");
2716
2849
  *
2717
- * expectErr(DecimalString.fromUnknown("10.250"), {
2850
+ * const invalid = DecimalString.fromUnknown("10.250");
2851
+ * assertErr(invalid);
2852
+ * assertType(Data, invalid.error);
2853
+ * assertEqual(invalid.error, {
2718
2854
  * type: "DecimalString",
2719
2855
  * value: "10.250",
2720
2856
  * });
@@ -2731,11 +2867,15 @@ export const DecimalString = /*#__PURE__*/ brand("DecimalString", String, (value
2731
2867
  * ### Example
2732
2868
  *
2733
2869
  * ```ts
2734
- * import { DecimalString, nonNegativeDecimalString } from "@evolu/common";
2870
+ * import {
2871
+ * assertOk,
2872
+ * DecimalString,
2873
+ * nonNegativeDecimalString,
2874
+ * } from "@evolu/common";
2735
2875
  *
2736
2876
  * const NonNegative = nonNegativeDecimalString(DecimalString);
2737
2877
  *
2738
- * expectOk(NonNegative.fromUnknown("0.5"), "0.5");
2878
+ * assertOk(NonNegative.fromUnknown("0.5"), "0.5");
2739
2879
  * ```
2740
2880
  *
2741
2881
  * @group Number
@@ -2759,11 +2899,15 @@ export const NonNegativeDecimalString =
2759
2899
  * ### Example
2760
2900
  *
2761
2901
  * ```ts
2762
- * import { DecimalString, positiveDecimalString } from "@evolu/common";
2902
+ * import {
2903
+ * assertOk,
2904
+ * DecimalString,
2905
+ * positiveDecimalString,
2906
+ * } from "@evolu/common";
2763
2907
  *
2764
2908
  * const Positive = positiveDecimalString(DecimalString);
2765
2909
  *
2766
- * expectOk(Positive.fromUnknown("0.5"), "0.5");
2910
+ * assertOk(Positive.fromUnknown("0.5"), "0.5");
2767
2911
  * ```
2768
2912
  *
2769
2913
  * @group Number
@@ -2789,11 +2933,15 @@ export const PositiveDecimalString = /*#__PURE__*/ positiveDecimalString(NonNega
2789
2933
  * ### Example
2790
2934
  *
2791
2935
  * ```ts
2792
- * import { DecimalString, nonPositiveDecimalString } from "@evolu/common";
2936
+ * import {
2937
+ * assertOk,
2938
+ * DecimalString,
2939
+ * nonPositiveDecimalString,
2940
+ * } from "@evolu/common";
2793
2941
  *
2794
2942
  * const NonPositive = nonPositiveDecimalString(DecimalString);
2795
2943
  *
2796
- * expectOk(NonPositive.fromUnknown("-0.5"), "-0.5");
2944
+ * assertOk(NonPositive.fromUnknown("-0.5"), "-0.5");
2797
2945
  * ```
2798
2946
  *
2799
2947
  * @group Number
@@ -2817,11 +2965,15 @@ export const NonPositiveDecimalString =
2817
2965
  * ### Example
2818
2966
  *
2819
2967
  * ```ts
2820
- * import { DecimalString, negativeDecimalString } from "@evolu/common";
2968
+ * import {
2969
+ * assertOk,
2970
+ * DecimalString,
2971
+ * negativeDecimalString,
2972
+ * } from "@evolu/common";
2821
2973
  *
2822
2974
  * const Negative = negativeDecimalString(DecimalString);
2823
2975
  *
2824
- * expectOk(Negative.fromUnknown("-0.5"), "-0.5");
2976
+ * assertOk(Negative.fromUnknown("-0.5"), "-0.5");
2825
2977
  * ```
2826
2978
  *
2827
2979
  * @group Number
@@ -2855,16 +3007,29 @@ export const NegativeDecimalString = /*#__PURE__*/ negativeDecimalString(NonPosi
2855
3007
  * ### Example
2856
3008
  *
2857
3009
  * ```ts
2858
- * 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";
2859
3020
  *
2860
3021
  * const Tenths = multipleOf("0.1")(FiniteNumber);
2861
3022
  *
2862
- * expectTypeOf<typeof Tenths.Output>().toEqualTypeOf<
2863
- * FiniteNumber & Brand<"MultipleOf0.1">
3023
+ * assertType<
3024
+ * FiniteNumber & Brand<"MultipleOf0.1">,
3025
+ * typeof Tenths.Output
2864
3026
  * >();
2865
3027
  *
2866
- * expectOk(Tenths.fromUnknown(0.3), 0.3);
2867
- * 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, {
2868
3033
  * type: "MultipleOf0.1",
2869
3034
  * value: 0.31,
2870
3035
  * divisor: "0.1",
@@ -2920,11 +3085,11 @@ const decimalStringToParts = (value) => {
2920
3085
  * ### Example
2921
3086
  *
2922
3087
  * ```ts
2923
- * import { Number, between } from "@evolu/common";
3088
+ * import { assertOk, Number, between } from "@evolu/common";
2924
3089
  *
2925
3090
  * const Percentage = between(0, 100)(Number);
2926
3091
  *
2927
- * expectOk(Percentage.fromUnknown(75), 75);
3092
+ * assertOk(Percentage.fromUnknown(75), 75);
2928
3093
  * ```
2929
3094
  *
2930
3095
  * @group Number
@@ -2966,6 +3131,11 @@ export const between = (min, max) => (parent) => {
2966
3131
  *
2967
3132
  * ```ts
2968
3133
  * import {
3134
+ * assertEqual,
3135
+ * assertErr,
3136
+ * assertOk,
3137
+ * assertType,
3138
+ * Data,
2969
3139
  * String,
2970
3140
  * array,
2971
3141
  * brand,
@@ -2977,12 +3147,16 @@ export const between = (min, max) => (parent) => {
2977
3147
  * const UserIds = array(UserId);
2978
3148
  * const result = UserIds.from.parent(["ada", "grace"]);
2979
3149
  *
2980
- * expectTypeOf(result).toEqualTypeOf<
2981
- * Result<ReadonlyArray<string & Brand<"UserId">>, never>
3150
+ * assertType<
3151
+ * Result<ReadonlyArray<string & Brand<"UserId">>>,
3152
+ * typeof result
2982
3153
  * >();
2983
- * expectOk(result, ["ada", "grace"]);
2984
- * expectOk(UserIds.fromUnknown(["ada", "grace"]), ["ada", "grace"]);
2985
- * 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, {
2986
3160
  * type: "Array",
2987
3161
  * reason: { kind: "NotArray", value: "ada" },
2988
3162
  * });
@@ -3130,20 +3304,20 @@ const copyArrayPrefix = (value, endIndex) => {
3130
3304
  /**
3131
3305
  * Set {@link Type} whose every element must match one Type.
3132
3306
  *
3133
- * Direct Sets from this or another realm are accepted. Set subclasses are
3134
- * rejected. A Set must have no own properties; its elements are validated in
3135
- * iteration order. Classification uses the realm-neutral object tag and
3136
- * 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.
3137
3311
  *
3138
3312
  * ### Example
3139
3313
  *
3140
3314
  * ```ts
3141
- * import { String, set } from "@evolu/common";
3315
+ * import { assertOk, String, set } from "@evolu/common";
3142
3316
  *
3143
3317
  * const Tags = set(String);
3144
3318
  * const tags = new Set(["local-first", "offline"]);
3145
3319
  *
3146
- * expectOk(Tags.fromUnknown(tags), tags);
3320
+ * assertOk(Tags.fromUnknown(tags), tags);
3147
3321
  * ```
3148
3322
  *
3149
3323
  * @group Collection
@@ -3177,68 +3351,44 @@ const createHomogeneousCollectionType = (typeElement, config) => {
3177
3351
  return type;
3178
3352
  };
3179
3353
  const setTypeByElement = /*#__PURE__*/ new WeakMap();
3180
- const validateSetCollection = (value, validateElement, options) => {
3181
- if (!hasObjectTag(value, "Set")) {
3182
- return err({
3183
- type: "Set",
3184
- reason: { kind: "NotSet", value },
3185
- });
3186
- }
3187
- if (!hasDirectSetPrototype(value)) {
3188
- return err({
3189
- type: "Set",
3190
- reason: {
3191
- kind: "UnexpectedPrototype",
3192
- value: value,
3193
- },
3194
- });
3195
- }
3196
- return validateSetItems(value, validateElement, options, true);
3197
- };
3198
- const encodeSetCollection = (value, encodeElement) => {
3199
- let changed = false;
3200
- const output = new globalThis.Set();
3201
- for (const item of value) {
3202
- const encoded = encodeElement(item);
3203
- if (!globalThis.Object.is(encoded, item))
3204
- changed = true;
3205
- output.add(encoded);
3206
- }
3207
- return changed ? output : value;
3208
- };
3209
- const isSetCollection = (value, isElement) => {
3210
- if (!hasObjectTag(value, "Set"))
3211
- return false;
3212
- if (!hasDirectSetPrototype(value))
3213
- return false;
3214
- if (Reflect.ownKeys(value).length !== 0)
3215
- return false;
3216
- for (const item of value) {
3217
- if (!isElement(item))
3218
- return false;
3219
- }
3220
- return true;
3221
- };
3222
- const hasDirectSetPrototype = (value) => {
3223
- const prototype = globalThis.Object.getPrototypeOf(value);
3224
- if (prototype === null)
3225
- return false;
3226
- const objectPrototype = globalThis.Object.getPrototypeOf(prototype);
3227
- return (objectPrototype !== null &&
3228
- globalThis.Object.getPrototypeOf(objectPrototype) === null);
3229
- };
3230
3354
  const setRuntimeConfig = {
3231
3355
  name: "Set",
3232
3356
  typeByElement: setTypeByElement,
3233
- 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
+ },
3234
3366
  validateItems: (value, validateElement, options) => validateSetItems(value, validateElement, options, false),
3235
- encode: encodeSetCollection,
3236
- 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
+ },
3237
3389
  formatError: ((error) => {
3238
3390
  if (error.reason.kind === "NotSet")
3239
3391
  return `A value ${safelyStringifyUnknownValue(error.reason.value)} is not a Set.`;
3240
- if (error.reason.kind === "UnexpectedPrototype")
3241
- return "The value is an instance of a Set subclass, but a Set Output must be a direct Set instance.";
3242
3392
  const issue = error.reason.issues[0];
3243
3393
  switch (issue.kind) {
3244
3394
  case "ExcessProperty":
@@ -3251,7 +3401,7 @@ const setRuntimeConfig = {
3251
3401
  const validateSetItems = (value, validate, options, checkStructure) => {
3252
3402
  let issues;
3253
3403
  let changed = false;
3254
- const output = new globalThis.Set();
3404
+ const output = new Set();
3255
3405
  if (checkStructure) {
3256
3406
  for (const key of Reflect.ownKeys(value)) {
3257
3407
  (issues ??= []).push({ kind: "ExcessProperty", key });
@@ -3284,6 +3434,208 @@ const validateSetItems = (value, validate, options, checkStructure) => {
3284
3434
  },
3285
3435
  });
3286
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
+ };
3287
3639
  export function tuple(...elements) {
3288
3640
  return createTupleType(elements);
3289
3641
  }
@@ -3467,11 +3819,12 @@ const formatPlainObjectRootError = (reason) => reason.kind === "NotObject"
3467
3819
  * A {@link Type} for readonly plain objects with unknown property values.
3468
3820
  *
3469
3821
  * `Object` is the runtime counterpart of a `Readonly<Record<string, unknown>>`
3470
- * data boundary. Its realm-neutral prototype rule accepts a `null` prototype or
3471
- * an immediate root prototype whose own prototype is `null`. Every own property
3472
- * must have a string key and be an enumerable data property. It rejects
3473
- * ordinary class instances, deeper prototype chains, accessors, non-enumerable
3474
- * 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.
3475
3828
  *
3476
3829
  * Use {@link object} when property names are fixed, {@link record} when keys and
3477
3830
  * values have their own Types, and {@link instanceOf} when an instance belongs
@@ -3579,10 +3932,6 @@ const _Object = /*#__PURE__*/ createRootType("Object", (value, options = firstVa
3579
3932
  // https://github.com/react/metro/issues/1331
3580
3933
  // https://github.com/expo/expo/issues/31167
3581
3934
  export { _Object as Object };
3582
- const isPlainObject = (value) => {
3583
- const prototype = globalThis.Object.getPrototypeOf(value);
3584
- return (prototype === null || globalThis.Object.getPrototypeOf(prototype) === null);
3585
- };
3586
3935
  /**
3587
3936
  * Record {@link Type}.
3588
3937
  *
@@ -3599,12 +3948,13 @@ const isPlainObject = (value) => {
3599
3948
  * By default, validation returns the first issue. Pass `{ errors: "all" }` to
3600
3949
  * collect issues across the whole Record.
3601
3950
  *
3602
- * A Record must have a `null` prototype or an immediate root prototype whose
3603
- * own prototype is `null`. Ordinary class instances and deeper prototype chains
3604
- * are rejected. Every own property must be an enumerable data property whose
3605
- * key and value satisfy their Types. When decoding or encoding changes an
3606
- * entry, the constructed Record uses a `null` prototype so keys such as
3607
- * `__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.
3608
3958
  *
3609
3959
  * If transformed keys collide, validation fails instead of overwriting an
3610
3960
  * entry.
@@ -3613,6 +3963,8 @@ const isPlainObject = (value) => {
3613
3963
  *
3614
3964
  * ```ts
3615
3965
  * import {
3966
+ * assertOk,
3967
+ * assertType,
3616
3968
  * Int64FromInt64String,
3617
3969
  * String,
3618
3970
  * record,
@@ -3628,21 +3980,22 @@ const isPlainObject = (value) => {
3628
3980
  * grace: "20",
3629
3981
  * });
3630
3982
  *
3631
- * expectOk(scoresFromUnknown, { ada: 10n, grace: 20n });
3983
+ * assertOk(scoresFromUnknown, { ada: 10n, grace: 20n });
3632
3984
  *
3633
3985
  * // Validate keys and values with their root Types.
3634
3986
  * const scoresInput = ScoresByUser.parent.fromUnknown({
3635
3987
  * ada: "10",
3636
3988
  * grace: "20",
3637
3989
  * });
3638
- * expectOk(scoresInput, { ada: "10", grace: "20" });
3990
+ * assertOk(scoresInput, { ada: "10", grace: "20" });
3639
3991
  *
3640
3992
  * // Run the remaining key and value stages.
3641
3993
  * const scoresFromInput = ScoresByUser.from.parent(scoresInput.value);
3642
3994
  *
3643
- * expectOk(scoresFromInput, { ada: 10n, grace: 20n });
3644
- * expectTypeOf(scoresFromInput.value).toEqualTypeOf<
3645
- * Readonly<Partial<Record<string, Int64>>>
3995
+ * assertOk(scoresFromInput, { ada: 10n, grace: 20n });
3996
+ * assertType<
3997
+ * Readonly<Partial<Record<string, Int64>>>,
3998
+ * typeof scoresFromInput.value
3646
3999
  * >();
3647
4000
  * ```
3648
4001
  *
@@ -3651,34 +4004,64 @@ const isPlainObject = (value) => {
3651
4004
  * as a Record entry:
3652
4005
  *
3653
4006
  * ```ts
4007
+ * import {
4008
+ * assertEqual,
4009
+ * assertErr,
4010
+ * assertTrue,
4011
+ * trySync,
4012
+ * } from "@evolu/common";
4013
+ *
3654
4014
  * type Values = Partial<Record<"toString", number>>;
3655
4015
  * const values: Values = {};
3656
4016
  *
3657
4017
  * // TypeScript treats the inherited function as `number | undefined`.
3658
4018
  * const value: number | undefined = values.toString;
4019
+ * const valueType = typeof value;
3659
4020
  *
3660
- * expect(typeof value).toBe("function");
3661
- * expect(() => {
3662
- * if (value !== undefined) value.toFixed();
3663
- * }).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);
3664
4030
  * ```
3665
4031
  *
3666
4032
  * Evolu Record Outputs use the same TypeScript Record representation.
3667
4033
  *
3668
4034
  * ```ts
3669
- * 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";
3670
4046
  *
3671
4047
  * const Values = record(literal("toString"), Number);
3672
4048
  * const result = Values.fromUnknown({});
4049
+ * const emptyValues: Data = {};
3673
4050
  *
3674
- * expectOk(result, {});
4051
+ * assertOk(result, emptyValues);
3675
4052
  *
3676
4053
  * const value: number | undefined = result.value.toString;
4054
+ * const valueType = typeof value;
3677
4055
  *
3678
- * expect(typeof value).toBe("function");
3679
- * expect(() => {
3680
- * if (value !== undefined) value.toFixed();
3681
- * }).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);
3682
4065
  * ```
3683
4066
  *
3684
4067
  * In other words, treat Record Outputs as string-keyed data rather than calling
@@ -3865,7 +4248,13 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
3865
4248
  * ### Example
3866
4249
  *
3867
4250
  * ```ts
3868
- * import { String, object, optional, undefinedOr } from "@evolu/common";
4251
+ * import {
4252
+ * assertOk,
4253
+ * String,
4254
+ * object,
4255
+ * optional,
4256
+ * undefinedOr,
4257
+ * } from "@evolu/common";
3869
4258
  *
3870
4259
  * const User = object({
3871
4260
  * name: String,
@@ -3873,8 +4262,8 @@ const validateRecordEntries = (input, validateKey, validateValue, options) => {
3873
4262
  * preferredName: optional(undefinedOr(String)),
3874
4263
  * });
3875
4264
  *
3876
- * expectOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
3877
- * expectOk(User.fromUnknown({ name: "Ada", preferredName: undefined }), {
4265
+ * assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
4266
+ * assertOk(User.fromUnknown({ name: "Ada", preferredName: undefined }), {
3878
4267
  * name: "Ada",
3879
4268
  * preferredName: undefined,
3880
4269
  * });
@@ -4231,13 +4620,20 @@ const createRecordPropertyError = (issue) => ({
4231
4620
  * ### Example
4232
4621
  *
4233
4622
  * ```ts
4234
- * import { PositiveInt, String, partial } from "@evolu/common";
4623
+ * import {
4624
+ * assertFalse,
4625
+ * assertOk,
4626
+ * PositiveInt,
4627
+ * String,
4628
+ * partial,
4629
+ * } from "@evolu/common";
4235
4630
  *
4236
4631
  * const PartialUser = partial({ name: String, age: PositiveInt });
4632
+ * const emptyPartialUser: typeof PartialUser.Output = {};
4237
4633
  *
4238
- * expectOk(PartialUser.fromUnknown({}), {});
4239
- * expectOk(PartialUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4240
- * 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);
4241
4637
  * ```
4242
4638
  *
4243
4639
  * @group Objects
@@ -4264,15 +4660,20 @@ export const partial = (props, ..._validation) => {
4264
4660
  * ### Example
4265
4661
  *
4266
4662
  * ```ts
4267
- * import { String, nullOr, nullableToOptional } from "@evolu/common";
4663
+ * import {
4664
+ * assertOk,
4665
+ * String,
4666
+ * nullOr,
4667
+ * nullableToOptional,
4668
+ * } from "@evolu/common";
4268
4669
  *
4269
4670
  * const User = nullableToOptional({
4270
4671
  * name: String,
4271
4672
  * nickname: nullOr(String),
4272
4673
  * });
4273
4674
  *
4274
- * expectOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
4275
- * expectOk(User.fromUnknown({ name: "Ada", nickname: null }), {
4675
+ * assertOk(User.fromUnknown({ name: "Ada" }), { name: "Ada" });
4676
+ * assertOk(User.fromUnknown({ name: "Ada", nickname: null }), {
4276
4677
  * name: "Ada",
4277
4678
  * nickname: null,
4278
4679
  * });
@@ -4305,12 +4706,12 @@ export const nullableToOptional = (props, ..._validation) => {
4305
4706
  * ### Example
4306
4707
  *
4307
4708
  * ```ts
4308
- * import { String, object, omit } from "@evolu/common";
4709
+ * import { assertOk, String, object, omit } from "@evolu/common";
4309
4710
  *
4310
4711
  * const User = object({ name: String, password: String });
4311
4712
  * const PublicUser = omit(User, "password");
4312
4713
  *
4313
- * expectOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4714
+ * assertOk(PublicUser.fromUnknown({ name: "Ada" }), { name: "Ada" });
4314
4715
  * ```
4315
4716
  *
4316
4717
  * @group Objects
@@ -4565,7 +4966,7 @@ const lazyTypeNodes = /*#__PURE__*/ new WeakSet();
4565
4966
  const assertLazyReferencesAreGuarded = (type) => {
4566
4967
  for (;;) {
4567
4968
  assert(!lazyTypeNodes.has(type), "A Lazy Type definition must place every Lazy Type behind a structural boundary.");
4568
- if ("members" in type && globalThis.Array.isArray(type.members)) {
4969
+ if ("members" in type && Array.isArray(type.members)) {
4569
4970
  for (const member of type.members)
4570
4971
  assertLazyReferencesAreGuarded(member);
4571
4972
  }
@@ -4577,6 +4978,266 @@ const assertLazyReferencesAreGuarded = (type) => {
4577
4978
  type = type.parent;
4578
4979
  }
4579
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);
4580
5241
  const emptyJsonValuePath =
4581
5242
  /*#__PURE__*/ globalThis.Object.freeze([]);
4582
5243
  const jsonValuePathToArray = (path) => {
@@ -4635,7 +5296,7 @@ const validateJsonValue = (value, options = firstValidationOptions) => {
4635
5296
  }
4636
5297
  continue;
4637
5298
  }
4638
- const isArray = globalThis.Array.isArray(value);
5299
+ const isArray = Array.isArray(value);
4639
5300
  if (!isArray && !isPlainObject(value)) {
4640
5301
  if (addIssue({
4641
5302
  kind: "UnexpectedPrototype",
@@ -4800,7 +5461,7 @@ const getJsonValueRuntimeTypeIssues = (error, mode) => {
4800
5461
  }),
4801
5462
  }));
4802
5463
  };
4803
- const parseJson = (value) => globalThis.JSON.parse(value);
5464
+ const parseJson = (value) => JSON.parse(value);
4804
5465
  const jsonToJsonValueResult = (value) => {
4805
5466
  const result = trySync(() => parseJson(value));
4806
5467
  return result.ok && validateJsonValue(result.value).ok
@@ -4824,7 +5485,7 @@ const stringifyJsonValue = (value) => {
4824
5485
  // oxlint-disable-next-line typescript/switch-exhaustiveness-check -- JsonValue excludes the additional runtime types reported by tsgolint.
4825
5486
  switch (typeof value) {
4826
5487
  case "string":
4827
- chunks.push(globalThis.JSON.stringify(value));
5488
+ chunks.push(JSON.stringify(value));
4828
5489
  break;
4829
5490
  case "number":
4830
5491
  chunks.push(globalThis.Object.is(value, -0) ? "-0" : globalThis.String(value));
@@ -4833,7 +5494,7 @@ const stringifyJsonValue = (value) => {
4833
5494
  chunks.push(value ? "true" : "false");
4834
5495
  break;
4835
5496
  case "object": {
4836
- if (globalThis.Array.isArray(value)) {
5497
+ if (Array.isArray(value)) {
4837
5498
  const array = value;
4838
5499
  chunks.push("[");
4839
5500
  work.push({ kind: "Text", value: "]" });
@@ -4854,7 +5515,7 @@ const stringifyJsonValue = (value) => {
4854
5515
  work.push({ kind: "Text", value: ":" });
4855
5516
  work.push({
4856
5517
  kind: "Text",
4857
- value: globalThis.JSON.stringify(key),
5518
+ value: JSON.stringify(key),
4858
5519
  });
4859
5520
  if (index > 0)
4860
5521
  work.push({ kind: "Text", value: "," });
@@ -4871,7 +5532,12 @@ const stringifyJsonValue = (value) => {
4871
5532
  * ### Example
4872
5533
  *
4873
5534
  * ```ts
4874
- * import { JsonValue, type JsonValueInput } from "@evolu/common";
5535
+ * import {
5536
+ * assertOk,
5537
+ * assertType,
5538
+ * JsonValue,
5539
+ * type JsonValueInput,
5540
+ * } from "@evolu/common";
4875
5541
  *
4876
5542
  * const input: JsonValueInput = {
4877
5543
  * name: "Ada",
@@ -4879,8 +5545,8 @@ const stringifyJsonValue = (value) => {
4879
5545
  * };
4880
5546
  * const result = JsonValue.fromUnknown(input);
4881
5547
  *
4882
- * expectOk(result, input);
4883
- * expectTypeOf(result.value).toEqualTypeOf<JsonValue>();
5548
+ * assertOk(result, input);
5549
+ * assertType<JsonValue, typeof result.value>();
4884
5550
  * ```
4885
5551
  *
4886
5552
  * @group JSON
@@ -4918,11 +5584,11 @@ export const Json = /*#__PURE__*/ brand("Json", String, (value) => {
4918
5584
  * ### Example
4919
5585
  *
4920
5586
  * ```ts
4921
- * import { Json, jsonToJsonValue } from "@evolu/common";
5587
+ * import { assertEqual, Json, jsonToJsonValue } from "@evolu/common";
4922
5588
  *
4923
5589
  * const value = jsonToJsonValue(Json.orThrow('{"name":"Ada"}'));
4924
5590
  *
4925
- * expect(value).toEqual({ name: "Ada" });
5591
+ * assertEqual(value, { name: "Ada" });
4926
5592
  * ```
4927
5593
  *
4928
5594
  * @group JSON
@@ -4934,11 +5600,15 @@ export const jsonToJsonValue = (value) => parseJson(value);
4934
5600
  * ### Example
4935
5601
  *
4936
5602
  * ```ts
4937
- * import { jsonValueToJson, type JsonValue } from "@evolu/common";
5603
+ * import {
5604
+ * assertEqual,
5605
+ * jsonValueToJson,
5606
+ * type JsonValue,
5607
+ * } from "@evolu/common";
4938
5608
  *
4939
5609
  * const value: JsonValue = { name: "Ada" };
4940
5610
  *
4941
- * expect(jsonValueToJson(value)).toBe('{"name":"Ada"}');
5611
+ * assertEqual(jsonValueToJson(value), '{"name":"Ada"}');
4942
5612
  * ```
4943
5613
  *
4944
5614
  * @group JSON
@@ -4953,12 +5623,12 @@ export const jsonValueToJson = (value) => stringifyJsonValue(JsonValue.to(value)
4953
5623
  * ### Example
4954
5624
  *
4955
5625
  * ```ts
4956
- * import { JsonValueFromJson } from "@evolu/common";
5626
+ * import { assertEqual, assertOk, JsonValueFromJson } from "@evolu/common";
4957
5627
  *
4958
5628
  * const result = JsonValueFromJson.fromUnknown('{ "name": "Ada" }');
4959
5629
  *
4960
- * expectOk(result, { name: "Ada" });
4961
- * expect(JsonValueFromJson.to(result.value)).toBe('{"name":"Ada"}');
5630
+ * assertOk(result, { name: "Ada" });
5631
+ * assertEqual(JsonValueFromJson.to(result.value), '{"name":"Ada"}');
4962
5632
  * ```
4963
5633
  *
4964
5634
  * @group JSON
@@ -4979,6 +5649,8 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
4979
5649
  *
4980
5650
  * ```ts
4981
5651
  * import {
5652
+ * assertEqual,
5653
+ * assertType,
4982
5654
  * Age,
4983
5655
  * NonEmptyTrimmedString100,
4984
5656
  * json,
@@ -4999,11 +5671,13 @@ export const JsonValueFromJson = /*#__PURE__*/ transform("JsonValueFromJson", Js
4999
5671
  * const user = User.orThrow({ name: "Ada", age: 37 });
5000
5672
  * const userJson = userToUserJson(user);
5001
5673
  *
5002
- * expectTypeOf(userJson).toEqualTypeOf<
5003
- * string & Brand<"Json"> & Brand<"UserJson">
5674
+ * assertType<
5675
+ * string & Brand<"Json"> & Brand<"UserJson">,
5676
+ * typeof userJson
5004
5677
  * >();
5005
- * expect(userJson).toBe('{"name":"Ada","age":37}');
5006
- * expect(userJsonToUser(userJson)).toEqual(user);
5678
+ * assertEqual(userJson, '{"name":"Ada","age":37}');
5679
+ * assertEqual(UserJson.orThrow(userJson), userJson);
5680
+ * assertEqual(userJsonToUser(userJson), user);
5007
5681
  * ```
5008
5682
  *
5009
5683
  * The supplied Type must have a JSON-compatible `CanonicalInput`. The branded