@evolu/common 8.9.0 → 8.11.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 (337) hide show
  1. package/dist/src/Bytes.d.ts +647 -0
  2. package/dist/src/Bytes.d.ts.map +1 -0
  3. package/dist/src/{Binary.js → Bytes.js} +266 -16
  4. package/dist/src/Config.d.ts +142 -0
  5. package/dist/src/Config.d.ts.map +1 -0
  6. package/dist/src/Config.js +181 -0
  7. package/dist/src/Console.d.ts +62 -7
  8. package/dist/src/Console.d.ts.map +1 -1
  9. package/dist/src/Console.js +20 -4
  10. package/dist/src/Crypto.d.ts +76 -4
  11. package/dist/src/Crypto.d.ts.map +1 -1
  12. package/dist/src/Crypto.js +55 -4
  13. package/dist/src/Error.d.ts +45 -0
  14. package/dist/src/Error.d.ts.map +1 -1
  15. package/dist/src/Error.js +69 -0
  16. package/dist/src/Fs.d.ts +376 -0
  17. package/dist/src/Fs.d.ts.map +1 -0
  18. package/dist/src/Fs.js +113 -0
  19. package/dist/src/Identicon.d.ts +2 -2
  20. package/dist/src/Identicon.js +2 -2
  21. package/dist/src/LeakDetector.d.ts +22 -3
  22. package/dist/src/LeakDetector.d.ts.map +1 -1
  23. package/dist/src/LeakDetector.js +12 -2
  24. package/dist/src/LockManager.d.ts +8 -0
  25. package/dist/src/LockManager.d.ts.map +1 -1
  26. package/dist/src/LockManager.js +6 -0
  27. package/dist/src/Number.d.ts +50 -7
  28. package/dist/src/Number.d.ts.map +1 -1
  29. package/dist/src/Number.js +47 -8
  30. package/dist/src/Object.d.ts +32 -0
  31. package/dist/src/Object.d.ts.map +1 -1
  32. package/dist/src/Object.js +46 -0
  33. package/dist/src/Platform.d.ts +47 -7
  34. package/dist/src/Platform.d.ts.map +1 -1
  35. package/dist/src/Platform.js +24 -5
  36. package/dist/src/Random.d.ts +25 -2
  37. package/dist/src/Random.d.ts.map +1 -1
  38. package/dist/src/Random.js +14 -2
  39. package/dist/src/Resource.d.ts +156 -1
  40. package/dist/src/Resource.d.ts.map +1 -1
  41. package/dist/src/Resource.js +201 -72
  42. package/dist/src/Schedule.d.ts +11 -10
  43. package/dist/src/Schedule.d.ts.map +1 -1
  44. package/dist/src/Schedule.js +1 -1
  45. package/dist/src/Sqlite.d.ts +132 -16
  46. package/dist/src/Sqlite.d.ts.map +1 -1
  47. package/dist/src/Sqlite.js +64 -10
  48. package/dist/src/Task.d.ts +15 -4
  49. package/dist/src/Task.d.ts.map +1 -1
  50. package/dist/src/Task.js +41 -15
  51. package/dist/src/Test.d.ts +9 -0
  52. package/dist/src/Test.d.ts.map +1 -1
  53. package/dist/src/Test.js +4 -0
  54. package/dist/src/Time.d.ts +179 -20
  55. package/dist/src/Time.d.ts.map +1 -1
  56. package/dist/src/Time.js +95 -6
  57. package/dist/src/Type.d.ts +3056 -1539
  58. package/dist/src/Type.d.ts.map +1 -1
  59. package/dist/src/Type.js +2548 -584
  60. package/dist/src/WebSocket.d.ts +164 -13
  61. package/dist/src/WebSocket.d.ts.map +1 -1
  62. package/dist/src/WebSocket.js +133 -24
  63. package/dist/src/Worker.d.ts +90 -8
  64. package/dist/src/Worker.d.ts.map +1 -1
  65. package/dist/src/Worker.js +28 -2
  66. package/dist/src/index.d.ts +9 -8
  67. package/dist/src/index.d.ts.map +1 -1
  68. package/dist/src/index.js +5 -4
  69. package/dist/src/intl/_en.d.ts +24 -1
  70. package/dist/src/intl/_en.d.ts.map +1 -1
  71. package/dist/src/intl/_en.js +20 -0
  72. package/dist/src/intl/ar.d.ts +24 -1
  73. package/dist/src/intl/ar.d.ts.map +1 -1
  74. package/dist/src/intl/ar.js +20 -0
  75. package/dist/src/intl/bn.d.ts +24 -1
  76. package/dist/src/intl/bn.d.ts.map +1 -1
  77. package/dist/src/intl/bn.js +20 -0
  78. package/dist/src/intl/ca.d.ts +24 -1
  79. package/dist/src/intl/ca.d.ts.map +1 -1
  80. package/dist/src/intl/ca.js +20 -0
  81. package/dist/src/intl/cs.d.ts +24 -1
  82. package/dist/src/intl/cs.d.ts.map +1 -1
  83. package/dist/src/intl/cs.js +20 -0
  84. package/dist/src/intl/da.d.ts +24 -1
  85. package/dist/src/intl/da.d.ts.map +1 -1
  86. package/dist/src/intl/da.js +20 -0
  87. package/dist/src/intl/de.d.ts +24 -1
  88. package/dist/src/intl/de.d.ts.map +1 -1
  89. package/dist/src/intl/de.js +20 -0
  90. package/dist/src/intl/el.d.ts +24 -1
  91. package/dist/src/intl/el.d.ts.map +1 -1
  92. package/dist/src/intl/el.js +20 -0
  93. package/dist/src/intl/es.d.ts +24 -1
  94. package/dist/src/intl/es.d.ts.map +1 -1
  95. package/dist/src/intl/es.js +20 -0
  96. package/dist/src/intl/fa.d.ts +24 -1
  97. package/dist/src/intl/fa.d.ts.map +1 -1
  98. package/dist/src/intl/fa.js +20 -0
  99. package/dist/src/intl/fi.d.ts +24 -1
  100. package/dist/src/intl/fi.d.ts.map +1 -1
  101. package/dist/src/intl/fi.js +20 -0
  102. package/dist/src/intl/fil.d.ts +24 -1
  103. package/dist/src/intl/fil.d.ts.map +1 -1
  104. package/dist/src/intl/fil.js +20 -0
  105. package/dist/src/intl/fr.d.ts +24 -1
  106. package/dist/src/intl/fr.d.ts.map +1 -1
  107. package/dist/src/intl/fr.js +20 -0
  108. package/dist/src/intl/he.d.ts +24 -1
  109. package/dist/src/intl/he.d.ts.map +1 -1
  110. package/dist/src/intl/he.js +20 -0
  111. package/dist/src/intl/hi.d.ts +24 -1
  112. package/dist/src/intl/hi.d.ts.map +1 -1
  113. package/dist/src/intl/hi.js +20 -0
  114. package/dist/src/intl/hr.d.ts +24 -1
  115. package/dist/src/intl/hr.d.ts.map +1 -1
  116. package/dist/src/intl/hr.js +20 -0
  117. package/dist/src/intl/hu.d.ts +22 -1
  118. package/dist/src/intl/hu.d.ts.map +1 -1
  119. package/dist/src/intl/hu.js +18 -0
  120. package/dist/src/intl/id.d.ts +24 -1
  121. package/dist/src/intl/id.d.ts.map +1 -1
  122. package/dist/src/intl/id.js +20 -0
  123. package/dist/src/intl/it.d.ts +24 -1
  124. package/dist/src/intl/it.d.ts.map +1 -1
  125. package/dist/src/intl/it.js +20 -0
  126. package/dist/src/intl/ja.d.ts +24 -1
  127. package/dist/src/intl/ja.d.ts.map +1 -1
  128. package/dist/src/intl/ja.js +20 -0
  129. package/dist/src/intl/ko.d.ts +24 -1
  130. package/dist/src/intl/ko.d.ts.map +1 -1
  131. package/dist/src/intl/ko.js +20 -0
  132. package/dist/src/intl/ml.d.ts +24 -1
  133. package/dist/src/intl/ml.d.ts.map +1 -1
  134. package/dist/src/intl/ml.js +20 -0
  135. package/dist/src/intl/mr.d.ts +24 -1
  136. package/dist/src/intl/mr.d.ts.map +1 -1
  137. package/dist/src/intl/mr.js +20 -0
  138. package/dist/src/intl/ms.d.ts +24 -1
  139. package/dist/src/intl/ms.d.ts.map +1 -1
  140. package/dist/src/intl/ms.js +20 -0
  141. package/dist/src/intl/nb.d.ts +22 -1
  142. package/dist/src/intl/nb.d.ts.map +1 -1
  143. package/dist/src/intl/nb.js +18 -0
  144. package/dist/src/intl/nl.d.ts +24 -1
  145. package/dist/src/intl/nl.d.ts.map +1 -1
  146. package/dist/src/intl/nl.js +20 -0
  147. package/dist/src/intl/pa.d.ts +24 -1
  148. package/dist/src/intl/pa.d.ts.map +1 -1
  149. package/dist/src/intl/pa.js +20 -0
  150. package/dist/src/intl/pl.d.ts +23 -0
  151. package/dist/src/intl/pl.d.ts.map +1 -1
  152. package/dist/src/intl/pl.js +20 -0
  153. package/dist/src/intl/pt-BR.d.ts +24 -1
  154. package/dist/src/intl/pt-BR.d.ts.map +1 -1
  155. package/dist/src/intl/pt-BR.js +20 -0
  156. package/dist/src/intl/pt.d.ts +24 -1
  157. package/dist/src/intl/pt.d.ts.map +1 -1
  158. package/dist/src/intl/pt.js +20 -0
  159. package/dist/src/intl/ro.d.ts +24 -1
  160. package/dist/src/intl/ro.d.ts.map +1 -1
  161. package/dist/src/intl/ro.js +20 -0
  162. package/dist/src/intl/sk.d.ts +24 -1
  163. package/dist/src/intl/sk.d.ts.map +1 -1
  164. package/dist/src/intl/sk.js +20 -0
  165. package/dist/src/intl/sl.d.ts +24 -1
  166. package/dist/src/intl/sl.d.ts.map +1 -1
  167. package/dist/src/intl/sl.js +20 -0
  168. package/dist/src/intl/sv.d.ts +24 -1
  169. package/dist/src/intl/sv.d.ts.map +1 -1
  170. package/dist/src/intl/sv.js +20 -0
  171. package/dist/src/intl/sw.d.ts +21 -0
  172. package/dist/src/intl/sw.d.ts.map +1 -1
  173. package/dist/src/intl/sw.js +18 -0
  174. package/dist/src/intl/ta.d.ts +24 -1
  175. package/dist/src/intl/ta.d.ts.map +1 -1
  176. package/dist/src/intl/ta.js +20 -0
  177. package/dist/src/intl/te.d.ts +24 -1
  178. package/dist/src/intl/te.d.ts.map +1 -1
  179. package/dist/src/intl/te.js +20 -0
  180. package/dist/src/intl/th.d.ts +24 -1
  181. package/dist/src/intl/th.d.ts.map +1 -1
  182. package/dist/src/intl/th.js +20 -0
  183. package/dist/src/intl/tr.d.ts +24 -1
  184. package/dist/src/intl/tr.d.ts.map +1 -1
  185. package/dist/src/intl/tr.js +20 -0
  186. package/dist/src/intl/uk.d.ts +80 -57
  187. package/dist/src/intl/uk.d.ts.map +1 -1
  188. package/dist/src/intl/uk.js +174 -149
  189. package/dist/src/intl/ur.d.ts +24 -1
  190. package/dist/src/intl/ur.d.ts.map +1 -1
  191. package/dist/src/intl/ur.js +20 -0
  192. package/dist/src/intl/vi.d.ts +24 -1
  193. package/dist/src/intl/vi.d.ts.map +1 -1
  194. package/dist/src/intl/vi.js +20 -0
  195. package/dist/src/intl/zh-CN.d.ts +24 -1
  196. package/dist/src/intl/zh-CN.d.ts.map +1 -1
  197. package/dist/src/intl/zh-CN.js +20 -0
  198. package/dist/src/intl/zh-TW.d.ts +24 -1
  199. package/dist/src/intl/zh-TW.d.ts.map +1 -1
  200. package/dist/src/intl/zh-TW.js +20 -0
  201. package/dist/src/local-first/Db.d.ts +52 -3
  202. package/dist/src/local-first/Db.d.ts.map +1 -1
  203. package/dist/src/local-first/Db.js +412 -137
  204. package/dist/src/local-first/Evolu.d.ts +336 -211
  205. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  206. package/dist/src/local-first/Evolu.js +102 -15
  207. package/dist/src/local-first/Owner.d.ts +13 -30
  208. package/dist/src/local-first/Owner.d.ts.map +1 -1
  209. package/dist/src/local-first/Owner.js +13 -30
  210. package/dist/src/local-first/Protocol.d.ts +95 -17
  211. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  212. package/dist/src/local-first/Protocol.js +119 -39
  213. package/dist/src/local-first/Query.d.ts +8 -15
  214. package/dist/src/local-first/Query.d.ts.map +1 -1
  215. package/dist/src/local-first/Schema.d.ts +345 -21
  216. package/dist/src/local-first/Schema.d.ts.map +1 -1
  217. package/dist/src/local-first/Schema.js +214 -17
  218. package/dist/src/local-first/Shared.d.ts +537 -22
  219. package/dist/src/local-first/Shared.d.ts.map +1 -1
  220. package/dist/src/local-first/Shared.js +1437 -234
  221. package/dist/src/local-first/Storage.d.ts +192 -14
  222. package/dist/src/local-first/Storage.d.ts.map +1 -1
  223. package/dist/src/local-first/Storage.js +82 -21
  224. package/dist/src/local-first/Timestamp.d.ts +392 -41
  225. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  226. package/dist/src/local-first/Timestamp.js +404 -82
  227. package/dist/src/local-first/index.d.ts +0 -1
  228. package/dist/src/local-first/index.d.ts.map +1 -1
  229. package/dist/src/local-first/index.js +0 -1
  230. package/package.json +1 -1
  231. package/src/Assert.test.ts +2 -5
  232. package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
  233. package/src/{Binary.ts → Bytes.ts} +652 -21
  234. package/src/Config.test.ts +668 -0
  235. package/src/Config.ts +410 -0
  236. package/src/Console.ts +62 -7
  237. package/src/Crypto.ts +76 -4
  238. package/src/Eq.test.ts +2 -3
  239. package/src/Error.test.ts +76 -3
  240. package/src/Error.ts +71 -0
  241. package/src/Fs.test.ts +105 -0
  242. package/src/Fs.ts +488 -0
  243. package/src/Identicon.ts +2 -2
  244. package/src/LeakDetector.ts +22 -3
  245. package/src/LockManager.ts +8 -0
  246. package/src/Number.test.ts +82 -18
  247. package/src/Number.ts +76 -8
  248. package/src/Object.test.ts +139 -10
  249. package/src/Object.ts +49 -0
  250. package/src/Platform.ts +50 -8
  251. package/src/Random.ts +25 -2
  252. package/src/Resource.test.ts +837 -0
  253. package/src/Resource.ts +235 -15
  254. package/src/Schedule.test.ts +50 -12
  255. package/src/Schedule.ts +24 -14
  256. package/src/Sqlite.ts +138 -18
  257. package/src/Task.test.ts +189 -8
  258. package/src/Task.ts +56 -17
  259. package/src/Test.ts +9 -0
  260. package/src/Time.test.ts +82 -11
  261. package/src/Time.ts +246 -24
  262. package/src/Type.test.ts +3994 -1119
  263. package/src/Type.ts +7258 -3842
  264. package/src/Types.test.ts +4 -14
  265. package/src/WebSocket.ts +313 -40
  266. package/src/Worker.ts +90 -8
  267. package/src/index.ts +18 -7
  268. package/src/intl/_en.ts +70 -0
  269. package/src/intl/ar.ts +71 -0
  270. package/src/intl/bn.ts +70 -0
  271. package/src/intl/ca.ts +70 -0
  272. package/src/intl/cs.ts +70 -0
  273. package/src/intl/da.ts +70 -0
  274. package/src/intl/de.ts +70 -0
  275. package/src/intl/el.ts +70 -0
  276. package/src/intl/es.ts +70 -0
  277. package/src/intl/fa.ts +70 -0
  278. package/src/intl/fi.ts +70 -0
  279. package/src/intl/fil.ts +70 -0
  280. package/src/intl/fr.ts +70 -0
  281. package/src/intl/he.ts +70 -0
  282. package/src/intl/hi.ts +70 -0
  283. package/src/intl/hr.ts +70 -0
  284. package/src/intl/hu.ts +69 -0
  285. package/src/intl/id.ts +70 -0
  286. package/src/intl/intl.test.ts +819 -1
  287. package/src/intl/it.ts +70 -0
  288. package/src/intl/ja.ts +70 -0
  289. package/src/intl/ko.ts +68 -0
  290. package/src/intl/ml.ts +70 -0
  291. package/src/intl/mr.ts +70 -0
  292. package/src/intl/ms.ts +71 -0
  293. package/src/intl/nb.ts +69 -0
  294. package/src/intl/nl.ts +70 -0
  295. package/src/intl/pa.ts +70 -0
  296. package/src/intl/pl.ts +63 -0
  297. package/src/intl/pt-BR.ts +70 -0
  298. package/src/intl/pt.ts +71 -0
  299. package/src/intl/ro.ts +70 -0
  300. package/src/intl/sk.ts +71 -0
  301. package/src/intl/sl.ts +70 -0
  302. package/src/intl/sv.ts +70 -0
  303. package/src/intl/sw.ts +62 -0
  304. package/src/intl/ta.ts +70 -0
  305. package/src/intl/te.ts +70 -0
  306. package/src/intl/th.ts +68 -0
  307. package/src/intl/tr.ts +70 -0
  308. package/src/intl/uk.ts +228 -155
  309. package/src/intl/ur.ts +70 -0
  310. package/src/intl/vi.ts +70 -0
  311. package/src/intl/zh-CN.ts +68 -0
  312. package/src/intl/zh-TW.ts +68 -0
  313. package/src/local-first/Db.ts +644 -339
  314. package/src/local-first/Evolu.test.ts +686 -21
  315. package/src/local-first/Evolu.ts +450 -228
  316. package/src/local-first/Owner.ts +13 -30
  317. package/src/local-first/Protocol.test.ts +618 -11
  318. package/src/local-first/Protocol.ts +197 -73
  319. package/src/local-first/Query.ts +8 -15
  320. package/src/local-first/Schema.test.ts +143 -0
  321. package/src/local-first/Schema.ts +374 -24
  322. package/src/local-first/Shared.test.ts +7731 -559
  323. package/src/local-first/Shared.ts +2036 -267
  324. package/src/local-first/Storage.ts +219 -33
  325. package/src/local-first/Timestamp.test.ts +344 -70
  326. package/src/local-first/Timestamp.ts +435 -119
  327. package/src/local-first/index.ts +0 -1
  328. package/dist/src/Binary.d.ts +0 -254
  329. package/dist/src/Binary.d.ts.map +0 -1
  330. package/dist/src/local-first/Error.d.ts +0 -12
  331. package/dist/src/local-first/Error.d.ts.map +0 -1
  332. package/dist/src/local-first/Error.js +0 -6
  333. package/dist/src/local-first/LocalAuth.d.ts +0 -150
  334. package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
  335. package/dist/src/local-first/LocalAuth.js +0 -179
  336. package/src/local-first/Error.ts +0 -17
  337. package/src/local-first/LocalAuth.ts +0 -457
@@ -9,7 +9,8 @@ import type { NonEmptyReadonlyArray } from "../Array.ts";
9
9
  import { firstInArray, isNonEmptyArray } from "../Array.ts";
10
10
  import { assert, assertNonNullable } from "../Assert.ts";
11
11
  import type { Brand } from "../Brand.ts";
12
- import { concatBytes } from "../Binary.ts";
12
+ import { concatBytes } from "../Bytes.ts";
13
+ import type { DecryptWithXChaCha20Poly1305Error } from "../Crypto.ts";
13
14
  import { decrement } from "../Number.ts";
14
15
  import type { RandomDep } from "../Random.ts";
15
16
  import { err, ok } from "../Result.ts";
@@ -38,6 +39,10 @@ import {
38
39
  import type { Awaitable } from "../Types.ts";
39
40
  import type { Owner, OwnerError, OwnerIdBytes } from "./Owner.ts";
40
41
  import { OwnerId, OwnerWriteKey } from "./Owner.ts";
42
+ import type {
43
+ ProtocolInvalidDataError,
44
+ ProtocolTimestampMismatchError,
45
+ } from "./Protocol.ts";
41
46
  import { systemColumnsWithId } from "./Schema.ts";
42
47
  import {
43
48
  createTimestamp,
@@ -46,6 +51,11 @@ import {
46
51
  TimestampBytes,
47
52
  } from "./Timestamp.ts";
48
53
 
54
+ /**
55
+ * Configuration for {@link Storage}, such as quota checks.
56
+ *
57
+ * @group Core
58
+ */
49
59
  export interface StorageConfig {
50
60
  /**
51
61
  * Callback called before an attempt to write, to check if an {@link OwnerId}
@@ -104,12 +114,18 @@ export interface StorageConfig {
104
114
  }
105
115
 
106
116
  /**
107
- * Evolu Storage.
117
+ * Replica storage used by Evolu's synchronization protocol.
118
+ *
119
+ * Protocol owns message framing, set reconciliation, and sync continuation.
120
+ * Storage owns batch acceptance, persistence, and the timestamp and fingerprint
121
+ * queries used by reconciliation. Protocol functions accept any implementation
122
+ * that satisfies this contract.
108
123
  *
109
- * Evolu Protocol is agnostic to storage implementation—any storage can be
110
- * plugged in, as long as it implements this interface. Implementations must
111
- * handle their own errors; return values only indicate overall success or
112
- * failure.
124
+ * {@link Storage.writeMessages} returns the {@link StorageWriteMessagesError}
125
+ * that made it store none of a batch. Implementations return expected write
126
+ * rejections without reporting them; the caller owns reporting. The client
127
+ * protocol forwards these errors unchanged, while the relay protocol maps them
128
+ * to wire error codes.
113
129
  *
114
130
  * The Storage API is synchronous because SQLite's synchronous API is the
115
131
  * fastest way to use SQLite. Synchronous bindings (like better-sqlite3) call
@@ -119,6 +135,8 @@ export interface StorageConfig {
119
135
  * The only exception is {@link Storage.writeMessages}, which is async to allow
120
136
  * for async validation logic before writing to storage. The write operation
121
137
  * itself remains synchronous.
138
+ *
139
+ * @group Core
122
140
  */
123
141
  export interface Storage {
124
142
  readonly getSize: (ownerId: OwnerIdBytes) => NonNegativeInt;
@@ -175,13 +193,16 @@ export interface Storage {
175
193
  /**
176
194
  * Write encrypted {@link CrdtMessage}s to storage.
177
195
  *
196
+ * Stores none of the messages and returns the cause when the batch cannot be
197
+ * accepted.
198
+ *
178
199
  * Must use a mutex per ownerId to ensure sequential processing and proper
179
200
  * protocol logic handling during sync operations.
180
201
  */
181
202
  readonly writeMessages: (
182
203
  ownerIdBytes: OwnerIdBytes,
183
204
  messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>,
184
- ) => Task<void, StorageQuotaError>;
205
+ ) => Task<void, StorageWriteMessagesError>;
185
206
 
186
207
  /** Read encrypted {@link DbChange}s from storage. */
187
208
  readonly readDbChange: (
@@ -193,30 +214,72 @@ export interface Storage {
193
214
  readonly deleteOwner: (ownerId: OwnerIdBytes) => void;
194
215
  }
195
216
 
217
+ /**
218
+ * Dependency wrapper for {@link Storage}.
219
+ *
220
+ * @group Core
221
+ */
196
222
  export interface StorageDep {
197
223
  readonly storage: Storage;
198
224
  }
199
225
 
200
- /** Error when storage or billing quota is exceeded. */
226
+ /**
227
+ * Error when storage or billing quota is exceeded.
228
+ *
229
+ * @group Core
230
+ */
201
231
  export interface StorageQuotaError
202
232
  extends OwnerError, Typed<"StorageQuotaError"> {}
203
233
 
234
+ /**
235
+ * Expected reasons why {@link Storage.writeMessages} stored none of a batch.
236
+ *
237
+ * Each implementation returns the members that apply to it. The built-in relay
238
+ * storage currently stores opaque encrypted messages and rejects batches over
239
+ * quota. The built-in client storage decrypts and validates incoming messages
240
+ * before updating its clock and database tables. The contract permits quota
241
+ * checks on either side.
242
+ *
243
+ * @group Core
244
+ */
245
+ export type StorageWriteMessagesError =
246
+ | DecryptWithXChaCha20Poly1305Error
247
+ | ProtocolInvalidDataError
248
+ | ProtocolTimestampMismatchError
249
+ | StorageQuotaError;
250
+
204
251
  /**
205
252
  * A cryptographic hash used for efficiently comparing collections of
206
253
  * {@link TimestampBytes}es.
207
254
  *
208
255
  * It consists of the first {@link fingerprintSize} bytes of the SHA-256 hash of
209
256
  * one or more timestamps.
257
+ *
258
+ * @group Ranges
210
259
  */
211
260
  export type Fingerprint = Uint8Array & Brand<"Fingerprint">;
212
261
 
262
+ /**
263
+ * Number of leading SHA-256 bytes that form a {@link Fingerprint}.
264
+ *
265
+ * @group Ranges
266
+ */
213
267
  export const fingerprintSize = /*#__PURE__*/ NonNegativeInt.orThrow(12);
214
268
 
215
- /** A fingerprint of an empty range. */
269
+ /**
270
+ * A fingerprint of an empty range.
271
+ *
272
+ * @group Ranges
273
+ */
216
274
  export const zeroFingerprint = /*#__PURE__*/ new Uint8Array(
217
275
  fingerprintSize,
218
276
  ) as Fingerprint;
219
277
 
278
+ /**
279
+ * Common shape of every {@link Range}.
280
+ *
281
+ * @group Ranges
282
+ */
220
283
  export interface BaseRange {
221
284
  readonly upperBound: RangeUpperBound;
222
285
  }
@@ -224,45 +287,96 @@ export interface BaseRange {
224
287
  /**
225
288
  * Union type for Range's upperBound: either a {@link TimestampBytes} or
226
289
  * {@link InfiniteUpperBound}.
290
+ *
291
+ * @group Ranges
227
292
  */
228
293
  export type RangeUpperBound = TimestampBytes | InfiniteUpperBound;
229
294
 
295
+ /**
296
+ * Sentinel {@link RangeUpperBound} for a range without an upper limit.
297
+ *
298
+ * @group Ranges
299
+ */
230
300
  export const InfiniteUpperBound = /*#__PURE__*/ Symbol(
231
301
  "evolu.local-first.Storage.InfiniteUpperBound",
232
302
  );
303
+ /**
304
+ * Type of the {@link InfiniteUpperBound} sentinel.
305
+ *
306
+ * @group Ranges
307
+ */
233
308
  export type InfiniteUpperBound = typeof InfiniteUpperBound;
234
309
 
310
+ /**
311
+ * Numeric tags discriminating {@link Range} variants.
312
+ *
313
+ * @group Ranges
314
+ */
235
315
  export const RangeType = {
236
316
  Fingerprint: 1,
237
317
  Skip: 0,
238
318
  Timestamps: 2,
239
319
  } as const;
240
320
 
321
+ /**
322
+ * Numeric tag of one {@link Range} variant.
323
+ *
324
+ * @group Ranges
325
+ */
241
326
  export type RangeType = (typeof RangeType)[keyof typeof RangeType];
242
327
 
328
+ /**
329
+ * Range with nothing to reconcile.
330
+ *
331
+ * @group Ranges
332
+ */
243
333
  export interface SkipRange extends BaseRange {
244
334
  readonly type: typeof RangeType.Skip;
245
335
  }
246
336
 
337
+ /**
338
+ * Range summarized by a {@link Fingerprint} for comparison.
339
+ *
340
+ * @group Ranges
341
+ */
247
342
  export interface FingerprintRange extends BaseRange {
248
343
  readonly type: typeof RangeType.Fingerprint;
249
344
  readonly fingerprint: Fingerprint;
250
345
  }
251
346
 
347
+ /**
348
+ * Range listing its {@link TimestampBytes} explicitly.
349
+ *
350
+ * @group Ranges
351
+ */
252
352
  export interface TimestampsRange extends BaseRange {
253
353
  readonly type: typeof RangeType.Timestamps;
254
354
  readonly timestamps: ReadonlyArray<TimestampBytes>;
255
355
  }
256
356
 
357
+ /**
358
+ * Range exchanged during sync: {@link SkipRange}, {@link FingerprintRange}, or
359
+ * {@link TimestampsRange}.
360
+ *
361
+ * @group Ranges
362
+ */
257
363
  export type Range = SkipRange | FingerprintRange | TimestampsRange;
258
364
 
259
- /** An encrypted {@link CrdtMessage}. */
365
+ /**
366
+ * An encrypted {@link CrdtMessage}.
367
+ *
368
+ * @group Messages
369
+ */
260
370
  export interface EncryptedCrdtMessage {
261
371
  readonly timestamp: Timestamp;
262
372
  readonly change: EncryptedDbChange;
263
373
  }
264
374
 
265
- /** Encrypted DbChange */
375
+ /**
376
+ * Encrypted DbChange
377
+ *
378
+ * @group Messages
379
+ */
266
380
  export type EncryptedDbChange = Uint8Array & Brand<"EncryptedDbChange">;
267
381
 
268
382
  /**
@@ -271,13 +385,19 @@ export type EncryptedDbChange = Uint8Array & Brand<"EncryptedDbChange">;
271
385
  * Used in Evolu's sync protocol to replicate data changes across devices. Evolu
272
386
  * operates as a durable queue, providing exactly-once delivery guarantees for
273
387
  * reliable synchronization across application restarts and network failures.
388
+ *
389
+ * @group Messages
274
390
  */
275
391
  export interface CrdtMessage {
276
392
  readonly timestamp: Timestamp;
277
393
  readonly change: DbChange;
278
394
  }
279
395
 
280
- /** Test helper for creating a simple {@link CrdtMessage}. */
396
+ /**
397
+ * Test helper for creating a simple {@link CrdtMessage}.
398
+ *
399
+ * @group Testing
400
+ */
281
401
  export const testCreateCrdtMessage = (
282
402
  id: Id,
283
403
  millis: number,
@@ -296,9 +416,19 @@ export const testCreateCrdtMessage = (
296
416
  }),
297
417
  });
298
418
 
419
+ /**
420
+ * Column values of a {@link DbChange}, keyed by column name.
421
+ *
422
+ * @group Messages
423
+ */
299
424
  export const DbChangeValues = /*#__PURE__*/ record(String, SqliteValue);
300
425
  export type DbChangeValues = typeof DbChangeValues.Output;
301
426
 
427
+ /**
428
+ * Column values that contain no reserved system columns.
429
+ *
430
+ * @group Messages
431
+ */
302
432
  export const ValidDbChangeValues = /*#__PURE__*/ brand(
303
433
  "ValidDbChangeValues",
304
434
  DbChangeValues,
@@ -318,6 +448,11 @@ export const ValidDbChangeValues = /*#__PURE__*/ brand(
318
448
  );
319
449
  export type ValidDbChangeValues = typeof ValidDbChangeValues.Output;
320
450
 
451
+ /**
452
+ * Error produced when {@link DbChangeValues} contain reserved system columns.
453
+ *
454
+ * @group Messages
455
+ */
321
456
  export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues"> {
322
457
  readonly value: DbChangeValues;
323
458
  readonly invalidColumns: ReadonlyArray<string>;
@@ -326,6 +461,8 @@ export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues
326
461
  /**
327
462
  * A DbChange is a change to a table row. Together with a unique
328
463
  * {@link Timestamp}, it forms a {@link CrdtMessage}.
464
+ *
465
+ * @group Messages
329
466
  */
330
467
  export const DbChange: ObjectType<{
331
468
  readonly table: typeof String;
@@ -371,17 +508,24 @@ export interface DbChange extends InferType<typeof DbChange> {}
371
508
  * each other, if necessary. One relay should handle hundreds of thousands of
372
509
  * users, and when it goes down, nothing happens, because it will be
373
510
  * synchronized later.
511
+ *
512
+ * @group SQLite
374
513
  */
375
514
  export interface BaseSqliteStorage extends Omit<
376
515
  Storage,
377
516
  "validateWriteKey" | "setWriteKey" | "writeMessages" | "readDbChange"
378
517
  > {
379
- /** Inserts a timestamp for an owner into the skiplist-based storage. */
518
+ /**
519
+ * Inserts a timestamp for an owner into the skiplist-based storage.
520
+ *
521
+ * Returns whether the timestamp was new. An existing timestamp is left
522
+ * unchanged.
523
+ */
380
524
  readonly insertTimestamp: (
381
525
  ownerId: OwnerIdBytes,
382
526
  timestamp: TimestampBytes,
383
527
  strategy: StorageInsertTimestampStrategy,
384
- ) => void;
528
+ ) => boolean;
385
529
 
386
530
  /**
387
531
  * Efficiently checks which timestamps already exist in the database using a
@@ -393,10 +537,20 @@ export interface BaseSqliteStorage extends Omit<
393
537
  ) => ReadonlyArray<TimestampBytes>;
394
538
  }
395
539
 
540
+ /**
541
+ * Dependency wrapper for {@link BaseSqliteStorage}.
542
+ *
543
+ * @group SQLite
544
+ */
396
545
  export interface BaseSqliteStorageDep {
397
546
  readonly baseSqliteStorage: BaseSqliteStorage;
398
547
  }
399
548
 
549
+ /**
550
+ * Dependencies required by {@link createBaseSqliteStorage}.
551
+ *
552
+ * @group SQLite
553
+ */
400
554
  export type SqliteStorageDeps = RandomDep & SqliteDep;
401
555
 
402
556
  /**
@@ -411,6 +565,8 @@ export type SqliteStorageDeps = RandomDep & SqliteDep;
411
565
  * Cloudflare Workers with Durable Objects, and other platforms where memory
412
566
  * doesn't persist between requests. While not extensively tested in all these
413
567
  * environments yet, the stateless design should work well across them.
568
+ *
569
+ * @group SQLite
414
570
  */
415
571
  export const createBaseSqliteStorage = (
416
572
  deps: SqliteStorageDeps,
@@ -421,7 +577,7 @@ export const createBaseSqliteStorage = (
421
577
  strategy: StorageInsertTimestampStrategy,
422
578
  ) => {
423
579
  const level = randomSkiplistLevel(deps);
424
- insertTimestamp(deps)(ownerId, timestamp, level, strategy);
580
+ return insertTimestamp(deps)(ownerId, timestamp, level, strategy);
425
581
  },
426
582
 
427
583
  getExistingTimestamps: (ownerIdBytes, timestampsBytes) => {
@@ -509,6 +665,11 @@ const assertBeginEnd = (begin: NonNegativeInt, end: NonNegativeInt) => {
509
665
  assert(begin <= end, "invalid begin or end");
510
666
  };
511
667
 
668
+ /**
669
+ * Creates the SQLite tables used by {@link BaseSqliteStorage}.
670
+ *
671
+ * @group SQLite
672
+ */
512
673
  export const createBaseSqliteStorageTables = (deps: SqliteDep): void => {
513
674
  for (const query of [
514
675
  /**
@@ -575,6 +736,11 @@ export const createBaseSqliteStorageTables = (deps: SqliteDep): void => {
575
736
  }
576
737
  };
577
738
 
739
+ /**
740
+ * Position at which a timestamp is inserted relative to existing timestamps.
741
+ *
742
+ * @group SQLite
743
+ */
578
744
  export type StorageInsertTimestampStrategy = "append" | "prepend" | "insert";
579
745
 
580
746
  /**
@@ -582,6 +748,8 @@ export type StorageInsertTimestampStrategy = "append" | "prepend" | "insert";
582
748
  * relative to the current first and last timestamps.
583
749
  *
584
750
  * Returns a tuple with the strategy and updated timestamp bounds.
751
+ *
752
+ * @group SQLite
585
753
  */
586
754
  export const getTimestampInsertStrategy = (
587
755
  timestamp: TimestampBytes,
@@ -624,9 +792,9 @@ export const getTimestampInsertStrategy = (
624
792
  * key instead of repeating the same correlated range lookup for every column.
625
793
  *
626
794
  * Inserts are idempotent to support direct calls and message replay. `on
627
- * conflict do nothing` makes a duplicate insertion a no-op, and `changes() > 0`
628
- * ensures ancestor metadata is updated only when the preceding insertion added
629
- * a timestamp.
795
+ * conflict do nothing` makes a duplicate insertion a no-op that reports the
796
+ * timestamp as not new, so the follow-up statements update metadata only for a
797
+ * new timestamp.
630
798
  */
631
799
  const insertTimestamp =
632
800
  (deps: SqliteDep) =>
@@ -635,12 +803,15 @@ const insertTimestamp =
635
803
  timestamp: TimestampBytes,
636
804
  level: PositiveInt,
637
805
  strategy: StorageInsertTimestampStrategy,
638
- ): void => {
806
+ ): boolean => {
639
807
  const [h1, h2] = fingerprintToSqliteFingerprint(
640
808
  timestampBytesToFingerprint(timestamp),
641
809
  );
642
810
 
643
- let queries: Array<ReturnType<typeof sql.prepared>> = [];
811
+ let queries: [
812
+ insert: ReturnType<typeof sql.prepared>,
813
+ ...updates: Array<ReturnType<typeof sql.prepared>>,
814
+ ];
644
815
 
645
816
  switch (strategy) {
646
817
  case "append":
@@ -816,10 +987,7 @@ const insertTimestamp =
816
987
  h2 = u.h2,
817
988
  c = c + 1
818
989
  from u
819
- where
820
- changes() > 0
821
- and ownerId = ${ownerId}
822
- and evolu_timestamp.t = u.t;
990
+ where ownerId = ${ownerId} and evolu_timestamp.t = u.t;
823
991
  `,
824
992
  ];
825
993
  break;
@@ -886,10 +1054,7 @@ const insertTimestamp =
886
1054
  h2 = u.h2,
887
1055
  c = c + 1
888
1056
  from u
889
- where
890
- changes() > 0
891
- and ownerId = ${ownerId}
892
- and evolu_timestamp.t = u.t;
1057
+ where ownerId = ${ownerId} and evolu_timestamp.t = u.t;
893
1058
  `,
894
1059
  ]
895
1060
  : [
@@ -1072,17 +1237,25 @@ const insertTimestamp =
1072
1237
  h2 = uh2,
1073
1238
  c = uc
1074
1239
  from u
1075
- where changes() > 0 and ownerId = ${ownerId} and t = ut;
1240
+ where ownerId = ${ownerId} and t = ut;
1076
1241
  `,
1077
1242
  ];
1078
1243
  break;
1079
1244
  }
1080
1245
 
1081
- for (const query of queries) {
1082
- deps.sqlite.exec(query);
1083
- }
1246
+ // The insert uses "on conflict do nothing". An existing timestamp changes
1247
+ // nothing, and the metadata updates are skipped.
1248
+ const [insert, ...updates] = queries;
1249
+ if (deps.sqlite.exec(insert).changes === 0) return false;
1250
+ for (const update of updates) deps.sqlite.exec(update);
1251
+ return true;
1084
1252
  };
1085
1253
 
1254
+ /**
1255
+ * Computes the {@link Fingerprint} of a single timestamp.
1256
+ *
1257
+ * @group Ranges
1258
+ */
1086
1259
  export const timestampBytesToFingerprint = (
1087
1260
  timestamp: TimestampBytes,
1088
1261
  ): Fingerprint => {
@@ -1093,6 +1266,8 @@ export const timestampBytesToFingerprint = (
1093
1266
  /**
1094
1267
  * Computes a brute-force {@link Fingerprint} from {@link TimestampBytes} values
1095
1268
  * for tests and benchmarks.
1269
+ *
1270
+ * @group Testing
1096
1271
  */
1097
1272
  export const testFingerprintTimestamps = (
1098
1273
  timestamps: ReadonlyArray<TimestampBytes>,
@@ -1492,6 +1667,11 @@ const fingerprintRanges =
1492
1667
  // XOR in SQLite
1493
1668
  const x = (a: string, b: string) => sql.raw(`(${a} | ${b}) - (${a} & ${b})`);
1494
1669
 
1670
+ /**
1671
+ * Reads the timestamp at a position within an owner's ordered timestamps.
1672
+ *
1673
+ * @group SQLite
1674
+ */
1495
1675
  export const getTimestampByIndex =
1496
1676
  (deps: SqliteDep) =>
1497
1677
  (ownerId: OwnerIdBytes, index: NonNegativeInt): TimestampBytes => {
@@ -1572,7 +1752,11 @@ export const getTimestampByIndex =
1572
1752
  return result.rows[0].pt;
1573
1753
  };
1574
1754
 
1575
- /** Reads owner usage from SQLite and returns default bounds when absent. */
1755
+ /**
1756
+ * Reads owner usage from SQLite and returns default bounds when absent.
1757
+ *
1758
+ * @group SQLite
1759
+ */
1576
1760
  export const readOwnerUsageOrDefault =
1577
1761
  (deps: SqliteDep) =>
1578
1762
  (
@@ -1617,6 +1801,8 @@ export const readOwnerUsageOrDefault =
1617
1801
  *
1618
1802
  * Used by both relay and client to maintain firstTimestamp/lastTimestamp after
1619
1803
  * processing messages.
1804
+ *
1805
+ * @group SQLite
1620
1806
  */
1621
1807
  export const updateOwnerUsage =
1622
1808
  (deps: SqliteDep) =>