@evolu/common 7.4.0 → 8.0.0-next.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 (251) hide show
  1. package/README.md +4 -61
  2. package/dist/src/Array.d.ts +343 -102
  3. package/dist/src/Array.d.ts.map +1 -1
  4. package/dist/src/Array.js +181 -85
  5. package/dist/src/Assert.d.ts +57 -11
  6. package/dist/src/Assert.d.ts.map +1 -1
  7. package/dist/src/Assert.js +47 -11
  8. package/dist/src/BigInt.d.ts +6 -1
  9. package/dist/src/BigInt.d.ts.map +1 -1
  10. package/dist/src/BigInt.js +5 -0
  11. package/dist/src/Brand.d.ts +40 -12
  12. package/dist/src/Brand.d.ts.map +1 -1
  13. package/dist/src/Brand.js +5 -0
  14. package/dist/src/Buffer.d.ts +6 -1
  15. package/dist/src/Buffer.d.ts.map +1 -1
  16. package/dist/src/Buffer.js +7 -2
  17. package/dist/src/Cache.d.ts +10 -2
  18. package/dist/src/Cache.d.ts.map +1 -1
  19. package/dist/src/Cache.js +6 -0
  20. package/dist/src/Callbacks.d.ts +10 -7
  21. package/dist/src/Callbacks.d.ts.map +1 -1
  22. package/dist/src/Callbacks.js +23 -13
  23. package/dist/src/Console.d.ts +361 -69
  24. package/dist/src/Console.d.ts.map +1 -1
  25. package/dist/src/Console.js +217 -117
  26. package/dist/src/Crypto.d.ts +70 -21
  27. package/dist/src/Crypto.d.ts.map +1 -1
  28. package/dist/src/Crypto.js +72 -27
  29. package/dist/src/Eq.d.ts +22 -2
  30. package/dist/src/Eq.d.ts.map +1 -1
  31. package/dist/src/Eq.js +21 -1
  32. package/dist/src/Error.d.ts +23 -12
  33. package/dist/src/Error.d.ts.map +1 -1
  34. package/dist/src/Error.js +27 -11
  35. package/dist/src/Function.d.ts +128 -56
  36. package/dist/src/Function.d.ts.map +1 -1
  37. package/dist/src/Function.js +118 -14
  38. package/dist/src/Identicon.d.ts +5 -0
  39. package/dist/src/Identicon.d.ts.map +1 -1
  40. package/dist/src/Identicon.js +6 -1
  41. package/dist/src/Lookup.d.ts +160 -0
  42. package/dist/src/Lookup.d.ts.map +1 -0
  43. package/dist/src/Lookup.js +192 -0
  44. package/dist/src/Microtask.d.ts +21 -0
  45. package/dist/src/Microtask.d.ts.map +1 -0
  46. package/dist/src/Microtask.js +37 -0
  47. package/dist/src/Number.d.ts +18 -4
  48. package/dist/src/Number.d.ts.map +1 -1
  49. package/dist/src/Number.js +23 -1
  50. package/dist/src/Object.d.ts +110 -5
  51. package/dist/src/Object.d.ts.map +1 -1
  52. package/dist/src/Object.js +108 -5
  53. package/dist/src/Option.d.ts +58 -0
  54. package/dist/src/Option.d.ts.map +1 -0
  55. package/dist/src/Option.js +43 -0
  56. package/dist/src/Order.d.ts +5 -0
  57. package/dist/src/Order.d.ts.map +1 -1
  58. package/dist/src/Order.js +8 -3
  59. package/dist/src/Platform.d.ts +36 -2
  60. package/dist/src/Platform.d.ts.map +1 -1
  61. package/dist/src/Platform.js +10 -6
  62. package/dist/src/Polyfills.d.ts +27 -0
  63. package/dist/src/Polyfills.d.ts.map +1 -0
  64. package/dist/src/Polyfills.js +299 -0
  65. package/dist/src/Random.d.ts +18 -7
  66. package/dist/src/Random.d.ts.map +1 -1
  67. package/dist/src/Random.js +9 -7
  68. package/dist/src/Redacted.d.ts +7 -4
  69. package/dist/src/Redacted.d.ts.map +1 -1
  70. package/dist/src/Redacted.js +5 -0
  71. package/dist/src/Ref.d.ts +31 -16
  72. package/dist/src/Ref.d.ts.map +1 -1
  73. package/dist/src/Ref.js +35 -7
  74. package/dist/src/RefCount.d.ts +62 -0
  75. package/dist/src/RefCount.d.ts.map +1 -0
  76. package/dist/src/RefCount.js +83 -0
  77. package/dist/src/Relation.d.ts +84 -29
  78. package/dist/src/Relation.d.ts.map +1 -1
  79. package/dist/src/Relation.js +83 -105
  80. package/dist/src/Resource.d.ts +263 -0
  81. package/dist/src/Resource.d.ts.map +1 -0
  82. package/dist/src/Resource.js +389 -0
  83. package/dist/src/Result.d.ts +390 -374
  84. package/dist/src/Result.d.ts.map +1 -1
  85. package/dist/src/Result.js +142 -70
  86. package/dist/src/Schedule.d.ts +953 -0
  87. package/dist/src/Schedule.d.ts.map +1 -0
  88. package/dist/src/Schedule.js +1199 -0
  89. package/dist/src/Set.d.ts +181 -0
  90. package/dist/src/Set.d.ts.map +1 -0
  91. package/dist/src/Set.js +137 -0
  92. package/dist/src/Skiplist.d.ts +6 -1
  93. package/dist/src/Skiplist.d.ts.map +1 -1
  94. package/dist/src/Skiplist.js +5 -0
  95. package/dist/src/Sqlite.d.ts +160 -60
  96. package/dist/src/Sqlite.d.ts.map +1 -1
  97. package/dist/src/Sqlite.js +290 -190
  98. package/dist/src/Store.d.ts +36 -28
  99. package/dist/src/Store.d.ts.map +1 -1
  100. package/dist/src/Store.js +55 -15
  101. package/dist/src/String.d.ts +5 -0
  102. package/dist/src/String.d.ts.map +1 -1
  103. package/dist/src/String.js +5 -0
  104. package/dist/src/Task.d.ts +2263 -423
  105. package/dist/src/Task.d.ts.map +1 -1
  106. package/dist/src/Task.js +1488 -348
  107. package/dist/src/Test.d.ts +122 -0
  108. package/dist/src/Test.d.ts.map +1 -0
  109. package/dist/src/Test.js +66 -0
  110. package/dist/src/Time.d.ts +201 -48
  111. package/dist/src/Time.d.ts.map +1 -1
  112. package/dist/src/Time.js +190 -86
  113. package/dist/src/Tracer.d.ts +48 -0
  114. package/dist/src/Tracer.d.ts.map +1 -0
  115. package/dist/src/Tracer.js +6 -0
  116. package/dist/src/Type.d.ts +582 -209
  117. package/dist/src/Type.d.ts.map +1 -1
  118. package/dist/src/Type.js +362 -240
  119. package/dist/src/Types.d.ts +160 -6
  120. package/dist/src/Types.d.ts.map +1 -1
  121. package/dist/src/Types.js +22 -2
  122. package/dist/src/WebSocket.d.ts +113 -58
  123. package/dist/src/WebSocket.d.ts.map +1 -1
  124. package/dist/src/WebSocket.js +210 -118
  125. package/dist/src/Worker.d.ts +245 -60
  126. package/dist/src/Worker.d.ts.map +1 -1
  127. package/dist/src/Worker.js +220 -64
  128. package/dist/src/index.d.ts +25 -3
  129. package/dist/src/index.d.ts.map +1 -1
  130. package/dist/src/index.js +19 -3
  131. package/dist/src/local-first/Db.d.ts +24 -213
  132. package/dist/src/local-first/Db.d.ts.map +1 -1
  133. package/dist/src/local-first/Db.js +622 -293
  134. package/dist/src/local-first/Error.d.ts +12 -0
  135. package/dist/src/local-first/Error.d.ts.map +1 -0
  136. package/dist/src/local-first/Error.js +6 -0
  137. package/dist/src/local-first/Evolu.d.ts +330 -285
  138. package/dist/src/local-first/Evolu.d.ts.map +1 -1
  139. package/dist/src/local-first/Evolu.js +472 -402
  140. package/dist/src/local-first/LocalAuth.d.ts +10 -10
  141. package/dist/src/local-first/LocalAuth.d.ts.map +1 -1
  142. package/dist/src/local-first/LocalAuth.js +6 -1
  143. package/dist/src/local-first/Owner.d.ts +70 -27
  144. package/dist/src/local-first/Owner.d.ts.map +1 -1
  145. package/dist/src/local-first/Owner.js +27 -14
  146. package/dist/src/local-first/Protocol.d.ts +58 -50
  147. package/dist/src/local-first/Protocol.d.ts.map +1 -1
  148. package/dist/src/local-first/Protocol.js +253 -155
  149. package/dist/src/local-first/Query.d.ts +133 -43
  150. package/dist/src/local-first/Query.d.ts.map +1 -1
  151. package/dist/src/local-first/Query.js +139 -90
  152. package/dist/src/local-first/Relay.d.ts +18 -38
  153. package/dist/src/local-first/Relay.d.ts.map +1 -1
  154. package/dist/src/local-first/Relay.js +59 -171
  155. package/dist/src/local-first/Schema.d.ts +139 -144
  156. package/dist/src/local-first/Schema.d.ts.map +1 -1
  157. package/dist/src/local-first/Schema.js +78 -163
  158. package/dist/src/local-first/Shared.d.ts +125 -0
  159. package/dist/src/local-first/Shared.d.ts.map +1 -0
  160. package/dist/src/local-first/Shared.js +569 -0
  161. package/dist/src/local-first/Storage.d.ts +50 -53
  162. package/dist/src/local-first/Storage.d.ts.map +1 -1
  163. package/dist/src/local-first/Storage.js +121 -173
  164. package/dist/src/local-first/Timestamp.d.ts +28 -35
  165. package/dist/src/local-first/Timestamp.d.ts.map +1 -1
  166. package/dist/src/local-first/Timestamp.js +24 -25
  167. package/dist/src/local-first/index.d.ts +2 -15
  168. package/dist/src/local-first/index.d.ts.map +1 -1
  169. package/dist/src/local-first/index.js +2 -15
  170. package/package.json +36 -26
  171. package/src/Array.ts +467 -106
  172. package/src/Assert.ts +87 -12
  173. package/src/BigInt.ts +7 -1
  174. package/src/Brand.ts +41 -12
  175. package/src/Buffer.ts +10 -4
  176. package/src/Cache.ts +11 -2
  177. package/src/Callbacks.ts +33 -23
  178. package/src/Console.ts +567 -178
  179. package/src/Crypto.ts +111 -62
  180. package/src/Eq.ts +30 -3
  181. package/src/Error.ts +50 -34
  182. package/src/Function.ts +140 -68
  183. package/src/Identicon.ts +7 -1
  184. package/src/Lookup.ts +415 -0
  185. package/src/Microtask.ts +58 -0
  186. package/src/Number.ts +40 -7
  187. package/src/Object.ts +155 -16
  188. package/src/Option.ts +74 -0
  189. package/src/Order.ts +11 -3
  190. package/src/Platform.ts +43 -7
  191. package/src/Polyfills.ts +465 -0
  192. package/src/Random.ts +25 -11
  193. package/src/Redacted.ts +8 -4
  194. package/src/Ref.ts +72 -21
  195. package/src/RefCount.ts +170 -0
  196. package/src/Relation.ts +188 -136
  197. package/src/Resource.ts +864 -0
  198. package/src/Result.ts +548 -381
  199. package/src/Schedule.ts +1471 -0
  200. package/src/Set.ts +247 -0
  201. package/src/Skiplist.ts +7 -1
  202. package/src/Sqlite.ts +409 -304
  203. package/src/Store.ts +94 -48
  204. package/src/String.ts +6 -0
  205. package/src/Task.ts +3685 -740
  206. package/src/Test.ts +162 -0
  207. package/src/Time.ts +348 -145
  208. package/src/Tracer.ts +54 -0
  209. package/src/Type.ts +1064 -630
  210. package/src/Types.ts +202 -7
  211. package/src/WebSocket.ts +311 -178
  212. package/src/Worker.ts +527 -147
  213. package/src/index.ts +74 -3
  214. package/src/local-first/Db.ts +871 -597
  215. package/src/local-first/Error.ts +17 -0
  216. package/src/local-first/Evolu.ts +884 -823
  217. package/src/local-first/LocalAuth.ts +10 -10
  218. package/src/local-first/Owner.ts +94 -42
  219. package/src/local-first/Protocol.ts +440 -303
  220. package/src/local-first/Query.ts +243 -194
  221. package/src/local-first/Relay.ts +91 -252
  222. package/src/local-first/Schema.ts +284 -394
  223. package/src/local-first/Shared.ts +932 -0
  224. package/src/local-first/Storage.ts +209 -282
  225. package/src/local-first/Timestamp.ts +38 -45
  226. package/src/local-first/index.ts +2 -16
  227. package/LICENSE +0 -21
  228. package/dist/src/Instances.d.ts +0 -34
  229. package/dist/src/Instances.d.ts.map +0 -1
  230. package/dist/src/Instances.js +0 -44
  231. package/dist/src/Resources.d.ts +0 -118
  232. package/dist/src/Resources.d.ts.map +0 -1
  233. package/dist/src/Resources.js +0 -197
  234. package/dist/src/local-first/Platform.d.ts +0 -25
  235. package/dist/src/local-first/Platform.d.ts.map +0 -1
  236. package/dist/src/local-first/Platform.js +0 -1
  237. package/dist/src/local-first/Public.d.ts +0 -22
  238. package/dist/src/local-first/Public.d.ts.map +0 -1
  239. package/dist/src/local-first/Public.js +0 -15
  240. package/dist/src/local-first/PublicKysely.d.ts +0 -148
  241. package/dist/src/local-first/PublicKysely.d.ts.map +0 -1
  242. package/dist/src/local-first/PublicKysely.js +0 -184
  243. package/dist/src/local-first/Sync.d.ts +0 -112
  244. package/dist/src/local-first/Sync.d.ts.map +0 -1
  245. package/dist/src/local-first/Sync.js +0 -529
  246. package/src/Instances.ts +0 -90
  247. package/src/Resources.ts +0 -367
  248. package/src/local-first/Platform.ts +0 -27
  249. package/src/local-first/Public.ts +0 -42
  250. package/src/local-first/PublicKysely.ts +0 -239
  251. package/src/local-first/Sync.ts +0 -960
@@ -1,39 +1,45 @@
1
+ /**
2
+ * Encrypted storage layer for local-first data.
3
+ *
4
+ * @module
5
+ */
6
+
1
7
  import { sha256 } from "@noble/hashes/sha2.js";
2
- import {
3
- firstInArray,
4
- isNonEmptyReadonlyArray,
5
- NonEmptyReadonlyArray,
6
- } from "../Array.js";
8
+ import type { NonEmptyReadonlyArray } from "../Array.js";
9
+ import { firstInArray, isNonEmptyArray } from "../Array.js";
7
10
  import { assert } from "../Assert.js";
8
- import { Brand } from "../Brand.js";
11
+ import type { Brand } from "../Brand.js";
9
12
  import { concatBytes } from "../Buffer.js";
10
13
  import { decrement } from "../Number.js";
11
- import { RandomDep } from "../Random.js";
12
- import { err, ok, Result } from "../Result.js";
13
- import { sql, SqliteDep, SqliteError, SqliteValue } from "../Sqlite.js";
14
- import { MaybeAsync } from "../Task.js";
14
+ import type { RandomDep } from "../Random.js";
15
+ import type { Result } from "../Result.js";
16
+ import { err, ok } from "../Result.js";
17
+ import type { SqliteDep } from "../Sqlite.js";
18
+ import { sql, SqliteValue } from "../Sqlite.js";
19
+ import type { Task } from "../Task.js";
20
+ import { Millis } from "../Time.js";
21
+ import type { InferType, Int64String, Typed, TypeError } from "../Type.js";
15
22
  import {
16
23
  Boolean,
17
24
  brand,
18
25
  Id,
19
- Int64String,
20
26
  NonNegativeInt,
21
27
  nullOr,
22
28
  object,
23
29
  PositiveInt,
24
30
  record,
25
31
  String,
26
- TypeError,
27
32
  } from "../Type.js";
28
- import {
29
- Owner,
30
- OwnerError,
31
- OwnerId,
32
- OwnerIdBytes,
33
- OwnerWriteKey,
34
- } from "./Owner.js";
33
+ import type { Awaitable } from "../Types.js";
34
+ import type { Owner, OwnerError, OwnerIdBytes } from "./Owner.js";
35
+ import { OwnerId, OwnerWriteKey } from "./Owner.js";
35
36
  import { systemColumnsWithId } from "./Schema.js";
36
- import { orderTimestampBytes, Timestamp, TimestampBytes } from "./Timestamp.js";
37
+ import {
38
+ createTimestamp,
39
+ orderTimestampBytes,
40
+ Timestamp,
41
+ TimestampBytes,
42
+ } from "./Timestamp.js";
37
43
 
38
44
  export interface StorageConfig {
39
45
  /**
@@ -42,7 +48,7 @@ export interface StorageConfig {
42
48
  *
43
49
  * The callback receives the {@link OwnerId} and the total bytes that would be
44
50
  * stored after the write (current stored bytes plus incoming bytes), and
45
- * returns a {@link MaybeAsync} boolean: `true` to allow the write, or `false`
51
+ * returns a {@link Awaitable} boolean: `true` to allow the write, or `false`
46
52
  * to deny it due to quota limits.
47
53
  *
48
54
  * The callback can be synchronous (for SQLite or in-memory checks) or
@@ -68,34 +74,34 @@ export interface StorageConfig {
68
74
  readonly isOwnerWithinQuota: (
69
75
  ownerId: OwnerId,
70
76
  requiredBytes: PositiveInt,
71
- ) => MaybeAsync<boolean>;
77
+ ) => Awaitable<boolean>;
72
78
  }
73
79
 
74
80
  /**
75
- * Evolu Storage
81
+ * Evolu Storage.
76
82
  *
77
- * Evolu protocol using Storage is agnostic to storage implementation
78
- * details—any storage can be plugged in, as long as it implements this
79
- * interface. Implementations must handle their own errors; return values only
80
- * indicate overall success or failure.
83
+ * Evolu Protocol is agnostic to storage implementation—any storage can be
84
+ * plugged in, as long as it implements this interface. Implementations must
85
+ * handle their own errors; return values only indicate overall success or
86
+ * failure.
81
87
  *
82
88
  * The Storage API is synchronous because SQLite's synchronous API is the
83
89
  * fastest way to use SQLite. Synchronous bindings (like better-sqlite3) call
84
90
  * SQLite's C API directly with no context switching between the event loop and
85
91
  * native code, and no promise microtasks or await overhead.
86
92
  *
87
- * The only exception is {@link Storage#writeMessages}, which is async to allow
93
+ * The only exception is {@link Storage.writeMessages}, which is async to allow
88
94
  * for async validation logic before writing to storage. The write operation
89
95
  * itself remains synchronous.
90
96
  */
91
97
  export interface Storage {
92
- readonly getSize: (ownerId: OwnerIdBytes) => NonNegativeInt | null;
98
+ readonly getSize: (ownerId: OwnerIdBytes) => NonNegativeInt;
93
99
 
94
100
  readonly fingerprint: (
95
101
  ownerId: OwnerIdBytes,
96
102
  begin: NonNegativeInt,
97
103
  end: NonNegativeInt,
98
- ) => Fingerprint | null;
104
+ ) => Fingerprint;
99
105
 
100
106
  /**
101
107
  * Computes fingerprints with their upper bounds in one call.
@@ -108,14 +114,14 @@ export interface Storage {
108
114
  ownerId: OwnerIdBytes,
109
115
  buckets: ReadonlyArray<NonNegativeInt>,
110
116
  upperBound?: RangeUpperBound,
111
- ) => ReadonlyArray<FingerprintRange> | null;
117
+ ) => ReadonlyArray<FingerprintRange>;
112
118
 
113
119
  readonly findLowerBound: (
114
120
  ownerId: OwnerIdBytes,
115
121
  begin: NonNegativeInt,
116
122
  end: NonNegativeInt,
117
123
  upperBound: RangeUpperBound,
118
- ) => NonNegativeInt | null;
124
+ ) => NonNegativeInt;
119
125
 
120
126
  readonly iterate: (
121
127
  ownerId: OwnerIdBytes,
@@ -138,62 +144,52 @@ export interface Storage {
138
144
  readonly setWriteKey: (
139
145
  ownerId: OwnerIdBytes,
140
146
  writeKey: OwnerWriteKey,
141
- ) => boolean;
147
+ ) => void;
142
148
 
143
149
  /**
144
150
  * Write encrypted {@link CrdtMessage}s to storage.
145
151
  *
146
152
  * Must use a mutex per ownerId to ensure sequential processing and proper
147
153
  * protocol logic handling during sync operations.
148
- *
149
- * TODO: Use MaybeAsync
150
154
  */
151
155
  readonly writeMessages: (
152
156
  ownerIdBytes: OwnerIdBytes,
153
157
  messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>,
154
- ) => MaybeAsync<Result<void, StorageWriteError | StorageQuotaError>>;
158
+ ) => Task<void, StorageQuotaError>;
155
159
 
156
160
  /** Read encrypted {@link DbChange}s from storage. */
157
161
  readonly readDbChange: (
158
162
  ownerId: OwnerIdBytes,
159
163
  timestamp: TimestampBytes,
160
- ) => EncryptedDbChange | null;
164
+ ) => EncryptedDbChange;
161
165
 
162
- /**
163
- * Delete all data for the given {@link Owner}.
164
- *
165
- * Returns `true` on success, `false` on failure.
166
- */
167
- readonly deleteOwner: (ownerId: OwnerIdBytes) => boolean;
166
+ /** Delete all data for the given {@link Owner}. */
167
+ readonly deleteOwner: (ownerId: OwnerIdBytes) => void;
168
168
  }
169
169
 
170
170
  export interface StorageDep {
171
171
  readonly storage: Storage;
172
172
  }
173
173
 
174
- /** Error indicating a serious write failure. */
175
- export interface StorageWriteError extends OwnerError {
176
- readonly type: "StorageWriteError";
177
- }
178
-
179
174
  /** Error when storage or billing quota is exceeded. */
180
- export interface StorageQuotaError extends OwnerError {
181
- readonly type: "StorageQuotaError";
182
- }
175
+ export interface StorageQuotaError
176
+ extends OwnerError, Typed<"StorageQuotaError"> {}
183
177
 
184
178
  /**
185
179
  * A cryptographic hash used for efficiently comparing collections of
186
- * {@link TimestampBytes}s.
180
+ * {@link TimestampBytes}es.
187
181
  *
188
182
  * It consists of the first {@link fingerprintSize} bytes of the SHA-256 hash of
189
183
  * one or more timestamps.
190
184
  */
191
185
  export type Fingerprint = Uint8Array & Brand<"Fingerprint">;
192
186
 
193
- export const fingerprintSize = NonNegativeInt.orThrow(12);
187
+ export const fingerprintSize = /*#__PURE__*/ NonNegativeInt.orThrow(12);
194
188
 
195
189
  /** A fingerprint of an empty range. */
196
- export const zeroFingerprint = new Uint8Array(fingerprintSize) as Fingerprint;
190
+ export const zeroFingerprint = /*#__PURE__*/ new Uint8Array(
191
+ fingerprintSize,
192
+ ) as Fingerprint;
197
193
 
198
194
  export interface BaseRange {
199
195
  readonly upperBound: RangeUpperBound;
@@ -205,7 +201,9 @@ export interface BaseRange {
205
201
  */
206
202
  export type RangeUpperBound = TimestampBytes | InfiniteUpperBound;
207
203
 
208
- export const InfiniteUpperBound = Symbol("InfiniteUpperBound");
204
+ export const InfiniteUpperBound = /*#__PURE__*/ Symbol(
205
+ "evolu.local-first.Storage.InfiniteUpperBound",
206
+ );
209
207
  export type InfiniteUpperBound = typeof InfiniteUpperBound;
210
208
 
211
209
  export const RangeType = {
@@ -253,10 +251,29 @@ export interface CrdtMessage {
253
251
  readonly change: DbChange;
254
252
  }
255
253
 
256
- export const DbChangeValues = record(String, SqliteValue);
254
+ /** Test helper for creating a simple {@link CrdtMessage}. */
255
+ export const testCreateCrdtMessage = (
256
+ id: Id,
257
+ millis: number,
258
+ name: string,
259
+ ): CrdtMessage => ({
260
+ timestamp: createTimestamp({
261
+ millis: Millis.orThrow(millis),
262
+ counter: 0 as never,
263
+ }),
264
+ change: DbChange.orThrow({
265
+ table: "testTable",
266
+ id,
267
+ values: { name },
268
+ isInsert: true,
269
+ isDelete: false,
270
+ }),
271
+ });
272
+
273
+ export const DbChangeValues = /*#__PURE__*/ record(String, SqliteValue);
257
274
  export type DbChangeValues = typeof DbChangeValues.Type;
258
275
 
259
- export const ValidDbChangeValues = brand(
276
+ export const ValidDbChangeValues = /*#__PURE__*/ brand(
260
277
  "ValidDbChangeValues",
261
278
  DbChangeValues,
262
279
  (value) => {
@@ -273,8 +290,7 @@ export const ValidDbChangeValues = brand(
273
290
  );
274
291
  export type ValidDbChangeValues = typeof ValidDbChangeValues.Type;
275
292
 
276
- export interface ValidDbChangeValuesError
277
- extends TypeError<"ValidDbChangeValues"> {
293
+ export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues"> {
278
294
  readonly invalidColumns: ReadonlyArray<string>;
279
295
  }
280
296
 
@@ -282,14 +298,14 @@ export interface ValidDbChangeValuesError
282
298
  * A DbChange is a change to a table row. Together with a unique
283
299
  * {@link Timestamp}, it forms a {@link CrdtMessage}.
284
300
  */
285
- export const DbChange = object({
301
+ export const DbChange = /*#__PURE__*/ object({
286
302
  table: String,
287
303
  id: Id,
288
304
  values: ValidDbChangeValues,
289
305
  isInsert: Boolean,
290
- isDelete: nullOr(Boolean),
306
+ isDelete: /*#__PURE__*/ nullOr(Boolean),
291
307
  });
292
- export type DbChange = typeof DbChange.Type;
308
+ export interface DbChange extends InferType<typeof DbChange> {}
293
309
 
294
310
  /**
295
311
  * Common interface for both client and relay SQLite storages.
@@ -324,22 +340,16 @@ export type DbChange = typeof DbChange.Type;
324
340
  * users, and when it goes down, nothing happens, because it will be
325
341
  * synchronized later.
326
342
  */
327
- export interface BaseSqliteStorage
328
- extends Pick<
329
- Storage,
330
- | "getSize"
331
- | "fingerprint"
332
- | "fingerprintRanges"
333
- | "findLowerBound"
334
- | "iterate"
335
- | "deleteOwner"
336
- > {
343
+ export interface BaseSqliteStorage extends Omit<
344
+ Storage,
345
+ "validateWriteKey" | "setWriteKey" | "writeMessages" | "readDbChange"
346
+ > {
337
347
  /** Inserts a timestamp for an owner into the skiplist-based storage. */
338
348
  readonly insertTimestamp: (
339
349
  ownerId: OwnerIdBytes,
340
350
  timestamp: TimestampBytes,
341
351
  strategy: StorageInsertTimestampStrategy,
342
- ) => Result<void, SqliteError>;
352
+ ) => void;
343
353
 
344
354
  /**
345
355
  * Efficiently checks which timestamps already exist in the database using a
@@ -348,19 +358,15 @@ export interface BaseSqliteStorage
348
358
  readonly getExistingTimestamps: (
349
359
  ownerIdBytes: OwnerIdBytes,
350
360
  timestampsBytes: NonEmptyReadonlyArray<TimestampBytes>,
351
- ) => Result<ReadonlyArray<TimestampBytes>, SqliteError>;
361
+ ) => ReadonlyArray<TimestampBytes>;
352
362
  }
353
363
 
354
364
  export interface BaseSqliteStorageDep {
355
- readonly storage: BaseSqliteStorage;
365
+ readonly baseSqliteStorage: BaseSqliteStorage;
356
366
  }
357
367
 
358
368
  export type SqliteStorageDeps = RandomDep & SqliteDep;
359
369
 
360
- export interface CreateBaseSqliteStorageConfig extends StorageConfig {
361
- onStorageError: (error: SqliteError) => void;
362
- }
363
-
364
370
  /**
365
371
  * Creates a {@link BaseSqliteStorage} implementation.
366
372
  *
@@ -374,154 +380,104 @@ export interface CreateBaseSqliteStorageConfig extends StorageConfig {
374
380
  * doesn't persist between requests. While not extensively tested in all these
375
381
  * environments yet, the stateless design should work well across them.
376
382
  */
377
- export const createBaseSqliteStorage =
378
- (deps: SqliteStorageDeps) =>
379
- (config: CreateBaseSqliteStorageConfig): BaseSqliteStorage => {
380
- return {
381
- insertTimestamp: (
382
- ownerId: OwnerIdBytes,
383
- timestamp: TimestampBytes,
384
- strategy: StorageInsertTimestampStrategy,
385
- ) => {
386
- const level = randomSkiplistLevel(deps);
387
- return insertTimestamp(deps)(ownerId, timestamp, level, strategy);
388
- },
389
-
390
- getExistingTimestamps: (ownerIdBytes, timestampsBytes) => {
391
- const concatenatedTimestamps = concatBytes(...timestampsBytes);
392
-
393
- const result = deps.sqlite.exec<{
394
- timestampBytes: TimestampBytes;
395
- }>(sql`
396
- with recursive
397
- split_timestamps(timestampBytes, pos) as (
398
- select
399
- substr(${concatenatedTimestamps}, 1, 16),
400
- 17 as pos
401
- union all
402
- select
403
- substr(${concatenatedTimestamps}, pos, 16),
404
- pos + 16
405
- from split_timestamps
406
- where pos <= length(${concatenatedTimestamps})
407
- )
408
- select s.timestampBytes
409
- from
410
- split_timestamps s
411
- join evolu_timestamp t
412
- on t.ownerId = ${ownerIdBytes} and s.timestampBytes = t.t;
413
- `);
414
-
415
- if (!result.ok) return result;
416
-
417
- return ok(result.value.rows.map((row) => row.timestampBytes));
418
- },
419
-
420
- getSize: (ownerId) => {
421
- const size = getSize(deps)(ownerId);
422
- if (!size.ok) {
423
- config.onStorageError(size.error);
424
- return null;
425
- }
426
- return size.value;
427
- },
428
-
429
- fingerprint: (ownerId, begin, end) => {
430
- assertBeginEnd(begin, end);
431
- const result = fingerprint(deps)(ownerId, begin, end);
432
- if (!result.ok) {
433
- config.onStorageError(result.error);
434
- return null;
435
- }
436
- return result.value;
437
- },
438
-
439
- fingerprintRanges: (ownerId, buckets, upperBound) => {
440
- const ranges = fingerprintRanges(deps)(ownerId, buckets, upperBound);
441
- if (!ranges.ok) {
442
- config.onStorageError(ranges.error);
443
- return null;
444
- }
445
- return ranges.value;
446
- },
447
-
448
- findLowerBound: (ownerId, begin, end, upperBound) => {
449
- const lowerBound = findLowerBound(deps)(
450
- ownerId,
451
- begin,
452
- end,
453
- upperBound,
454
- );
455
- if (!lowerBound.ok) {
456
- config.onStorageError(lowerBound.error);
457
- return null;
458
- }
459
- return lowerBound.value;
460
- },
461
-
462
- iterate: (ownerId, begin, end, callback) => {
463
- assertBeginEnd(begin, end);
464
- const length = end - begin;
465
- if (length === 0) return;
466
-
467
- // This is much faster than SQL limit with offset.
468
- const first = getTimestampByIndex(deps)(ownerId, begin);
469
- if (!first.ok) {
470
- config.onStorageError(first.error);
471
- return;
472
- }
473
-
474
- if (!callback(first.value, begin)) return;
475
- if (length === 1) return;
476
-
477
- /**
478
- * TODO: In rare cases, we might overfetch a lot of rows here, but we
479
- * don't have real usage numbers yet. Fetching one row at a time would
480
- * probably be slower in almost all cases. In the future, we should
481
- * fetch in chunks (e.g., 1,000 rows at a time). For now, consider
482
- * logging unused rows to gather data and calculate an average, then use
483
- * that information to determine an optimal chunk size. Before
484
- * implementing chunking, be sure to run performance tests (including
485
- * fetching one by one).
486
- */
487
- const result = deps.sqlite.exec<{ t: TimestampBytes }>(sql`
488
- select t
489
- from evolu_timestamp
490
- where ownerId = ${ownerId} and t > ${first.value}
491
- order by t
492
- limit ${length - 1};
493
- `);
494
- if (!result.ok) {
495
- config.onStorageError(result.error);
496
- return;
497
- }
498
-
499
- for (let i = 0; i < result.value.rows.length; i++) {
500
- const index = NonNegativeInt.orThrow(begin + 1 + i);
501
- if (!callback(result.value.rows[i].t, index)) return;
502
- }
503
- },
504
-
505
- deleteOwner: (ownerId) => {
506
- const result = deps.sqlite.exec(sql`
507
- delete from evolu_timestamp where ownerId = ${ownerId};
508
- `);
509
- if (!result.ok) {
510
- config.onStorageError(result.error);
511
- return false;
512
- }
513
- return true;
514
- },
515
- };
516
- };
383
+ export const createBaseSqliteStorage = (
384
+ deps: SqliteStorageDeps,
385
+ ): BaseSqliteStorage => ({
386
+ insertTimestamp: (
387
+ ownerId: OwnerIdBytes,
388
+ timestamp: TimestampBytes,
389
+ strategy: StorageInsertTimestampStrategy,
390
+ ) => {
391
+ const level = randomSkiplistLevel(deps);
392
+ insertTimestamp(deps)(ownerId, timestamp, level, strategy);
393
+ },
394
+
395
+ getExistingTimestamps: (ownerIdBytes, timestampsBytes) => {
396
+ const concatenatedTimestamps = concatBytes(...timestampsBytes);
397
+
398
+ const result = deps.sqlite.exec<{
399
+ timestampBytes: TimestampBytes;
400
+ }>(sql`
401
+ with recursive
402
+ split_timestamps(timestampBytes, pos) as (
403
+ select
404
+ substr(${concatenatedTimestamps}, 1, 16),
405
+ 17 as pos
406
+ union all
407
+ select
408
+ substr(${concatenatedTimestamps}, pos, 16),
409
+ pos + 16
410
+ from split_timestamps
411
+ where pos <= length(${concatenatedTimestamps})
412
+ )
413
+ select s.timestampBytes
414
+ from
415
+ split_timestamps s
416
+ join evolu_timestamp t
417
+ on t.ownerId = ${ownerIdBytes} and s.timestampBytes = t.t;
418
+ `);
419
+
420
+ return result.rows.map((row) => row.timestampBytes);
421
+ },
422
+
423
+ getSize: getSize(deps),
424
+
425
+ fingerprint: (ownerId, begin, end) => {
426
+ assertBeginEnd(begin, end);
427
+ return fingerprint(deps)(ownerId, begin, end);
428
+ },
429
+
430
+ fingerprintRanges: fingerprintRanges(deps),
431
+
432
+ findLowerBound: (ownerId, begin, end, upperBound) =>
433
+ findLowerBound(deps)(ownerId, begin, end, upperBound),
434
+
435
+ iterate: (ownerId, begin, end, callback) => {
436
+ assertBeginEnd(begin, end);
437
+ const length = end - begin;
438
+ if (length === 0) return;
439
+
440
+ // This is much faster than SQL limit with offset.
441
+ const first = getTimestampByIndex(deps)(ownerId, begin);
442
+
443
+ if (!callback(first, begin)) return;
444
+ if (length === 1) return;
445
+
446
+ /**
447
+ * TODO: In rare cases, we might overfetch a lot of rows here, but we don't
448
+ * have real usage numbers yet. Fetching one row at a time would probably be
449
+ * slower in almost all cases. In the future, we should fetch in chunks
450
+ * (e.g., 1,000 rows at a time). For now, consider logging unused rows to
451
+ * gather data and calculate an average, then use that information to
452
+ * determine an optimal chunk size. Before implementing chunking, be sure to
453
+ * run performance tests (including fetching one by one).
454
+ */
455
+ const result = deps.sqlite.exec<{ t: TimestampBytes }>(sql`
456
+ select t
457
+ from evolu_timestamp
458
+ where ownerId = ${ownerId} and t > ${first}
459
+ order by t
460
+ limit ${length - 1};
461
+ `);
462
+
463
+ for (let i = 0; i < result.rows.length; i++) {
464
+ const index = NonNegativeInt.orThrow(begin + 1 + i);
465
+ if (!callback(result.rows[i].t, index)) return;
466
+ }
467
+ },
468
+
469
+ deleteOwner: (ownerId) => {
470
+ deps.sqlite.exec(sql`
471
+ delete from evolu_timestamp where ownerId = ${ownerId};
472
+ `);
473
+ },
474
+ });
517
475
 
518
476
  const assertBeginEnd = (begin: NonNegativeInt, end: NonNegativeInt) => {
519
477
  assert(begin <= end, "invalid begin or end");
520
478
  };
521
479
 
522
- export const createBaseSqliteStorageTables = (
523
- deps: SqliteDep,
524
- ): Result<void, SqliteError> => {
480
+ export const createBaseSqliteStorageTables = (deps: SqliteDep): void => {
525
481
  for (const query of [
526
482
  /**
527
483
  * Creates the `evolu_timestamp` table for storing timestamps of multiple
@@ -583,10 +539,8 @@ export const createBaseSqliteStorageTables = (
583
539
  strict;
584
540
  `,
585
541
  ]) {
586
- const result = deps.sqlite.exec(query);
587
- if (!result.ok) return result;
542
+ deps.sqlite.exec(query);
588
543
  }
589
- return ok();
590
544
  };
591
545
 
592
546
  export type StorageInsertTimestampStrategy = "append" | "prepend" | "insert";
@@ -636,7 +590,7 @@ const insertTimestamp =
636
590
  timestamp: TimestampBytes,
637
591
  level: PositiveInt,
638
592
  strategy: StorageInsertTimestampStrategy,
639
- ): Result<void, SqliteError> => {
593
+ ): void => {
640
594
  const [h1, h2] = fingerprintToSqliteFingerprint(
641
595
  timestampBytesToFingerprint(timestamp),
642
596
  );
@@ -1149,11 +1103,8 @@ const insertTimestamp =
1149
1103
  }
1150
1104
 
1151
1105
  for (const query of queries) {
1152
- const result = deps.sqlite.exec(query);
1153
- if (!result.ok) return result;
1106
+ deps.sqlite.exec(query);
1154
1107
  }
1155
-
1156
- return ok();
1157
1108
  };
1158
1109
 
1159
1110
  export const timestampBytesToFingerprint = (
@@ -1243,7 +1194,7 @@ const sqliteFingerprintToFingerprint = ([
1243
1194
 
1244
1195
  const getSize =
1245
1196
  (deps: SqliteDep) =>
1246
- (ownerId: OwnerIdBytes): Result<NonNegativeInt, SqliteError> => {
1197
+ (ownerId: OwnerIdBytes): NonNegativeInt => {
1247
1198
  const result = deps.sqlite.exec<{ size: NonNegativeInt }>(sql.prepared`
1248
1199
  with
1249
1200
  ml(ml) as (
@@ -1279,8 +1230,7 @@ const getSize =
1279
1230
  from sc;
1280
1231
  `);
1281
1232
 
1282
- if (!result.ok) return result;
1283
- return ok(result.value.rows[0].size);
1233
+ return result.rows[0].size;
1284
1234
  };
1285
1235
 
1286
1236
  const findLowerBound =
@@ -1290,11 +1240,11 @@ const findLowerBound =
1290
1240
  begin: NonNegativeInt,
1291
1241
  end: NonNegativeInt,
1292
1242
  upperBound: RangeUpperBound,
1293
- ): Result<NonNegativeInt, SqliteError> => {
1243
+ ): NonNegativeInt => {
1294
1244
  assertBeginEnd(begin, end);
1295
1245
 
1296
1246
  if (end === 0 || begin === end || upperBound === InfiniteUpperBound) {
1297
- return ok(end);
1247
+ return end;
1298
1248
  }
1299
1249
 
1300
1250
  const result = deps.sqlite.exec<{
@@ -1306,25 +1256,19 @@ const findLowerBound =
1306
1256
  order by t
1307
1257
  limit 1;
1308
1258
  `);
1309
- if (!result.ok) return result;
1310
1259
 
1311
- if (result.value.rows.length === 0) {
1312
- return ok(end);
1260
+ if (result.rows.length === 0) {
1261
+ return end;
1313
1262
  }
1314
1263
 
1315
- const count = getTimestampCount(deps)(ownerId, result.value.rows[0].t);
1316
- if (!count.ok) return count;
1317
-
1264
+ const count = getTimestampCount(deps)(ownerId, result.rows[0].t);
1318
1265
  // `decrement` converts a count to an index.
1319
- return ok(NonNegativeInt.orThrow(decrement(count.value)));
1266
+ return NonNegativeInt.orThrow(decrement(count));
1320
1267
  };
1321
1268
 
1322
1269
  const getTimestampCount =
1323
1270
  (deps: SqliteDep) =>
1324
- (
1325
- ownerId: OwnerIdBytes,
1326
- timestamp: TimestampBytes,
1327
- ): Result<PositiveInt, SqliteError> => {
1271
+ (ownerId: OwnerIdBytes, timestamp: TimestampBytes): PositiveInt => {
1328
1272
  const result = deps.sqlite.exec<{
1329
1273
  count: PositiveInt;
1330
1274
  }>(sql.prepared`
@@ -1369,8 +1313,7 @@ const getTimestampCount =
1369
1313
  from sc;
1370
1314
  `);
1371
1315
 
1372
- if (!result.ok) return result;
1373
- return ok(result.value.rows[0].count);
1316
+ return result.rows[0].count;
1374
1317
  };
1375
1318
 
1376
1319
  /**
@@ -1383,22 +1326,18 @@ const fingerprint =
1383
1326
  ownerId: OwnerIdBytes,
1384
1327
  begin: NonNegativeInt,
1385
1328
  end: NonNegativeInt,
1386
- ): Result<Fingerprint, SqliteError> => {
1329
+ ): Fingerprint => {
1387
1330
  // There is no need to fingerprint an empty range.
1388
1331
  if (end - begin === 0) {
1389
- return ok(zeroFingerprint);
1332
+ return zeroFingerprint;
1390
1333
  }
1391
1334
 
1392
1335
  if (begin === 0) {
1393
- const result = fingerprintRanges(deps)(ownerId, [end]);
1394
- if (!result.ok) return result;
1395
- return ok(result.value[0].fingerprint);
1336
+ return fingerprintRanges(deps)(ownerId, [end])[0].fingerprint;
1396
1337
  }
1397
1338
 
1398
1339
  // We should have a param to skip the first result.
1399
- const result = fingerprintRanges(deps)(ownerId, [begin, end]);
1400
- if (!result.ok) return result;
1401
- return ok(result.value[1].fingerprint);
1340
+ return fingerprintRanges(deps)(ownerId, [begin, end])[1].fingerprint;
1402
1341
  };
1403
1342
 
1404
1343
  /**
@@ -1415,7 +1354,7 @@ const fingerprintRanges =
1415
1354
  ownerId: OwnerIdBytes,
1416
1355
  buckets: ReadonlyArray<NonNegativeInt>,
1417
1356
  upperBound: RangeUpperBound = InfiniteUpperBound,
1418
- ): Result<ReadonlyArray<FingerprintRange>, SqliteError> => {
1357
+ ): ReadonlyArray<FingerprintRange> => {
1419
1358
  const bucketsJson = JSON.stringify(buckets);
1420
1359
 
1421
1360
  const result = deps.sqlite.exec<{
@@ -1533,9 +1472,7 @@ const fingerprintRanges =
1533
1472
  from c3;
1534
1473
  `);
1535
1474
 
1536
- if (!result.ok) return result;
1537
-
1538
- const fingerprintRanges = result.value.rows.map(
1475
+ const fingerprintRanges = result.rows.map(
1539
1476
  (row, i, arr): FingerprintRange => ({
1540
1477
  type: RangeType.Fingerprint,
1541
1478
  upperBound: i === arr.length - 1 ? upperBound : row.b!,
@@ -1546,7 +1483,7 @@ const fingerprintRanges =
1546
1483
  }),
1547
1484
  );
1548
1485
 
1549
- return ok(fingerprintRanges);
1486
+ return fingerprintRanges;
1550
1487
  };
1551
1488
 
1552
1489
  // XOR in SQLite
@@ -1554,10 +1491,7 @@ const x = (a: string, b: string) => sql.raw(`(${a} | ${b}) - (${a} & ${b})`);
1554
1491
 
1555
1492
  export const getTimestampByIndex =
1556
1493
  (deps: SqliteDep) =>
1557
- (
1558
- ownerId: OwnerIdBytes,
1559
- index: NonNegativeInt,
1560
- ): Result<TimestampBytes, SqliteError> => {
1494
+ (ownerId: OwnerIdBytes, index: NonNegativeInt): TimestampBytes => {
1561
1495
  const result = deps.sqlite.exec<{
1562
1496
  readonly pt: TimestampBytes;
1563
1497
  }>(sql.prepared`
@@ -1632,8 +1566,7 @@ export const getTimestampByIndex =
1632
1566
  where ic == ${index + 1};
1633
1567
  `);
1634
1568
 
1635
- if (!result.ok) return result;
1636
- return ok(result.value.rows[0].pt);
1569
+ return result.rows[0].pt;
1637
1570
  };
1638
1571
 
1639
1572
  /** Retrieves usage information for an owner from the evolu_usage table. */
@@ -1642,14 +1575,11 @@ export const getOwnerUsage =
1642
1575
  (
1643
1576
  ownerIdBytes: OwnerIdBytes,
1644
1577
  initialTimestamp: TimestampBytes,
1645
- ): Result<
1646
- {
1647
- storedBytes: NonNegativeInt | null;
1648
- firstTimestamp: TimestampBytes;
1649
- lastTimestamp: TimestampBytes;
1650
- },
1651
- SqliteError
1652
- > => {
1578
+ ): Result<{
1579
+ storedBytes: NonNegativeInt | null;
1580
+ firstTimestamp: TimestampBytes;
1581
+ lastTimestamp: TimestampBytes;
1582
+ }> => {
1653
1583
  const result = deps.sqlite.exec<{
1654
1584
  storedBytes: NonNegativeInt;
1655
1585
  firstTimestamp: TimestampBytes | null;
@@ -1659,9 +1589,8 @@ export const getOwnerUsage =
1659
1589
  from evolu_usage
1660
1590
  where ownerId = ${ownerIdBytes};
1661
1591
  `);
1662
- if (!result.ok) return result;
1663
1592
 
1664
- if (!isNonEmptyReadonlyArray(result.value.rows)) {
1593
+ if (!isNonEmptyArray(result.rows)) {
1665
1594
  return ok({
1666
1595
  storedBytes: null,
1667
1596
  firstTimestamp: initialTimestamp,
@@ -1669,7 +1598,7 @@ export const getOwnerUsage =
1669
1598
  });
1670
1599
  }
1671
1600
 
1672
- const row = firstInArray(result.value.rows);
1601
+ const row = firstInArray(result.rows);
1673
1602
  assert(row.firstTimestamp, "not null");
1674
1603
  assert(row.lastTimestamp, "not null");
1675
1604
 
@@ -1693,8 +1622,8 @@ export const updateOwnerUsage =
1693
1622
  storedBytes: PositiveInt,
1694
1623
  firstTimestamp: TimestampBytes,
1695
1624
  lastTimestamp: TimestampBytes,
1696
- ): Result<void, SqliteError> => {
1697
- const result = deps.sqlite.exec(sql`
1625
+ ): void => {
1626
+ deps.sqlite.exec(sql`
1698
1627
  insert into evolu_usage
1699
1628
  ("ownerId", "storedBytes", "firstTimestamp", "lastTimestamp")
1700
1629
  values
@@ -1705,6 +1634,4 @@ export const updateOwnerUsage =
1705
1634
  firstTimestamp = ${firstTimestamp},
1706
1635
  lastTimestamp = ${lastTimestamp};
1707
1636
  `);
1708
- if (!result.ok) return result;
1709
- return ok();
1710
1637
  };