@evolu/common 7.4.1 → 8.0.0-next.1

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