@evolu/common 7.4.1 → 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 +851 -430
  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 -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 +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 +206 -277
  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 -526
  246. package/src/Instances.ts +0 -92
  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 -949
@@ -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) => {
@@ -281,14 +298,14 @@ export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues
281
298
  * A DbChange is a change to a table row. Together with a unique
282
299
  * {@link Timestamp}, it forms a {@link CrdtMessage}.
283
300
  */
284
- export const DbChange = object({
301
+ export const DbChange = /*#__PURE__*/ object({
285
302
  table: String,
286
303
  id: Id,
287
304
  values: ValidDbChangeValues,
288
305
  isInsert: Boolean,
289
- isDelete: nullOr(Boolean),
306
+ isDelete: /*#__PURE__*/ nullOr(Boolean),
290
307
  });
291
- export type DbChange = typeof DbChange.Type;
308
+ export interface DbChange extends InferType<typeof DbChange> {}
292
309
 
293
310
  /**
294
311
  * Common interface for both client and relay SQLite storages.
@@ -323,21 +340,16 @@ export type DbChange = typeof DbChange.Type;
323
340
  * users, and when it goes down, nothing happens, because it will be
324
341
  * synchronized later.
325
342
  */
326
- export interface BaseSqliteStorage extends Pick<
343
+ export interface BaseSqliteStorage extends Omit<
327
344
  Storage,
328
- | "getSize"
329
- | "fingerprint"
330
- | "fingerprintRanges"
331
- | "findLowerBound"
332
- | "iterate"
333
- | "deleteOwner"
345
+ "validateWriteKey" | "setWriteKey" | "writeMessages" | "readDbChange"
334
346
  > {
335
347
  /** Inserts a timestamp for an owner into the skiplist-based storage. */
336
348
  readonly insertTimestamp: (
337
349
  ownerId: OwnerIdBytes,
338
350
  timestamp: TimestampBytes,
339
351
  strategy: StorageInsertTimestampStrategy,
340
- ) => Result<void, SqliteError>;
352
+ ) => void;
341
353
 
342
354
  /**
343
355
  * Efficiently checks which timestamps already exist in the database using a
@@ -346,19 +358,15 @@ export interface BaseSqliteStorage extends Pick<
346
358
  readonly getExistingTimestamps: (
347
359
  ownerIdBytes: OwnerIdBytes,
348
360
  timestampsBytes: NonEmptyReadonlyArray<TimestampBytes>,
349
- ) => Result<ReadonlyArray<TimestampBytes>, SqliteError>;
361
+ ) => ReadonlyArray<TimestampBytes>;
350
362
  }
351
363
 
352
364
  export interface BaseSqliteStorageDep {
353
- readonly storage: BaseSqliteStorage;
365
+ readonly baseSqliteStorage: BaseSqliteStorage;
354
366
  }
355
367
 
356
368
  export type SqliteStorageDeps = RandomDep & SqliteDep;
357
369
 
358
- export interface CreateBaseSqliteStorageConfig extends StorageConfig {
359
- onStorageError: (error: SqliteError) => void;
360
- }
361
-
362
370
  /**
363
371
  * Creates a {@link BaseSqliteStorage} implementation.
364
372
  *
@@ -372,154 +380,104 @@ export interface CreateBaseSqliteStorageConfig extends StorageConfig {
372
380
  * doesn't persist between requests. While not extensively tested in all these
373
381
  * environments yet, the stateless design should work well across them.
374
382
  */
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
- };
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
+ });
515
475
 
516
476
  const assertBeginEnd = (begin: NonNegativeInt, end: NonNegativeInt) => {
517
477
  assert(begin <= end, "invalid begin or end");
518
478
  };
519
479
 
520
- export const createBaseSqliteStorageTables = (
521
- deps: SqliteDep,
522
- ): Result<void, SqliteError> => {
480
+ export const createBaseSqliteStorageTables = (deps: SqliteDep): void => {
523
481
  for (const query of [
524
482
  /**
525
483
  * Creates the `evolu_timestamp` table for storing timestamps of multiple
@@ -581,10 +539,8 @@ export const createBaseSqliteStorageTables = (
581
539
  strict;
582
540
  `,
583
541
  ]) {
584
- const result = deps.sqlite.exec(query);
585
- if (!result.ok) return result;
542
+ deps.sqlite.exec(query);
586
543
  }
587
- return ok();
588
544
  };
589
545
 
590
546
  export type StorageInsertTimestampStrategy = "append" | "prepend" | "insert";
@@ -634,7 +590,7 @@ const insertTimestamp =
634
590
  timestamp: TimestampBytes,
635
591
  level: PositiveInt,
636
592
  strategy: StorageInsertTimestampStrategy,
637
- ): Result<void, SqliteError> => {
593
+ ): void => {
638
594
  const [h1, h2] = fingerprintToSqliteFingerprint(
639
595
  timestampBytesToFingerprint(timestamp),
640
596
  );
@@ -1147,11 +1103,8 @@ const insertTimestamp =
1147
1103
  }
1148
1104
 
1149
1105
  for (const query of queries) {
1150
- const result = deps.sqlite.exec(query);
1151
- if (!result.ok) return result;
1106
+ deps.sqlite.exec(query);
1152
1107
  }
1153
-
1154
- return ok();
1155
1108
  };
1156
1109
 
1157
1110
  export const timestampBytesToFingerprint = (
@@ -1241,7 +1194,7 @@ const sqliteFingerprintToFingerprint = ([
1241
1194
 
1242
1195
  const getSize =
1243
1196
  (deps: SqliteDep) =>
1244
- (ownerId: OwnerIdBytes): Result<NonNegativeInt, SqliteError> => {
1197
+ (ownerId: OwnerIdBytes): NonNegativeInt => {
1245
1198
  const result = deps.sqlite.exec<{ size: NonNegativeInt }>(sql.prepared`
1246
1199
  with
1247
1200
  ml(ml) as (
@@ -1277,8 +1230,7 @@ const getSize =
1277
1230
  from sc;
1278
1231
  `);
1279
1232
 
1280
- if (!result.ok) return result;
1281
- return ok(result.value.rows[0].size);
1233
+ return result.rows[0].size;
1282
1234
  };
1283
1235
 
1284
1236
  const findLowerBound =
@@ -1288,11 +1240,11 @@ const findLowerBound =
1288
1240
  begin: NonNegativeInt,
1289
1241
  end: NonNegativeInt,
1290
1242
  upperBound: RangeUpperBound,
1291
- ): Result<NonNegativeInt, SqliteError> => {
1243
+ ): NonNegativeInt => {
1292
1244
  assertBeginEnd(begin, end);
1293
1245
 
1294
1246
  if (end === 0 || begin === end || upperBound === InfiniteUpperBound) {
1295
- return ok(end);
1247
+ return end;
1296
1248
  }
1297
1249
 
1298
1250
  const result = deps.sqlite.exec<{
@@ -1304,25 +1256,19 @@ const findLowerBound =
1304
1256
  order by t
1305
1257
  limit 1;
1306
1258
  `);
1307
- if (!result.ok) return result;
1308
1259
 
1309
- if (result.value.rows.length === 0) {
1310
- return ok(end);
1260
+ if (result.rows.length === 0) {
1261
+ return end;
1311
1262
  }
1312
1263
 
1313
- const count = getTimestampCount(deps)(ownerId, result.value.rows[0].t);
1314
- if (!count.ok) return count;
1315
-
1264
+ const count = getTimestampCount(deps)(ownerId, result.rows[0].t);
1316
1265
  // `decrement` converts a count to an index.
1317
- return ok(NonNegativeInt.orThrow(decrement(count.value)));
1266
+ return NonNegativeInt.orThrow(decrement(count));
1318
1267
  };
1319
1268
 
1320
1269
  const getTimestampCount =
1321
1270
  (deps: SqliteDep) =>
1322
- (
1323
- ownerId: OwnerIdBytes,
1324
- timestamp: TimestampBytes,
1325
- ): Result<PositiveInt, SqliteError> => {
1271
+ (ownerId: OwnerIdBytes, timestamp: TimestampBytes): PositiveInt => {
1326
1272
  const result = deps.sqlite.exec<{
1327
1273
  count: PositiveInt;
1328
1274
  }>(sql.prepared`
@@ -1367,8 +1313,7 @@ const getTimestampCount =
1367
1313
  from sc;
1368
1314
  `);
1369
1315
 
1370
- if (!result.ok) return result;
1371
- return ok(result.value.rows[0].count);
1316
+ return result.rows[0].count;
1372
1317
  };
1373
1318
 
1374
1319
  /**
@@ -1381,22 +1326,18 @@ const fingerprint =
1381
1326
  ownerId: OwnerIdBytes,
1382
1327
  begin: NonNegativeInt,
1383
1328
  end: NonNegativeInt,
1384
- ): Result<Fingerprint, SqliteError> => {
1329
+ ): Fingerprint => {
1385
1330
  // There is no need to fingerprint an empty range.
1386
1331
  if (end - begin === 0) {
1387
- return ok(zeroFingerprint);
1332
+ return zeroFingerprint;
1388
1333
  }
1389
1334
 
1390
1335
  if (begin === 0) {
1391
- const result = fingerprintRanges(deps)(ownerId, [end]);
1392
- if (!result.ok) return result;
1393
- return ok(result.value[0].fingerprint);
1336
+ return fingerprintRanges(deps)(ownerId, [end])[0].fingerprint;
1394
1337
  }
1395
1338
 
1396
1339
  // 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);
1340
+ return fingerprintRanges(deps)(ownerId, [begin, end])[1].fingerprint;
1400
1341
  };
1401
1342
 
1402
1343
  /**
@@ -1413,7 +1354,7 @@ const fingerprintRanges =
1413
1354
  ownerId: OwnerIdBytes,
1414
1355
  buckets: ReadonlyArray<NonNegativeInt>,
1415
1356
  upperBound: RangeUpperBound = InfiniteUpperBound,
1416
- ): Result<ReadonlyArray<FingerprintRange>, SqliteError> => {
1357
+ ): ReadonlyArray<FingerprintRange> => {
1417
1358
  const bucketsJson = JSON.stringify(buckets);
1418
1359
 
1419
1360
  const result = deps.sqlite.exec<{
@@ -1531,9 +1472,7 @@ const fingerprintRanges =
1531
1472
  from c3;
1532
1473
  `);
1533
1474
 
1534
- if (!result.ok) return result;
1535
-
1536
- const fingerprintRanges = result.value.rows.map(
1475
+ const fingerprintRanges = result.rows.map(
1537
1476
  (row, i, arr): FingerprintRange => ({
1538
1477
  type: RangeType.Fingerprint,
1539
1478
  upperBound: i === arr.length - 1 ? upperBound : row.b!,
@@ -1544,7 +1483,7 @@ const fingerprintRanges =
1544
1483
  }),
1545
1484
  );
1546
1485
 
1547
- return ok(fingerprintRanges);
1486
+ return fingerprintRanges;
1548
1487
  };
1549
1488
 
1550
1489
  // XOR in SQLite
@@ -1552,10 +1491,7 @@ const x = (a: string, b: string) => sql.raw(`(${a} | ${b}) - (${a} & ${b})`);
1552
1491
 
1553
1492
  export const getTimestampByIndex =
1554
1493
  (deps: SqliteDep) =>
1555
- (
1556
- ownerId: OwnerIdBytes,
1557
- index: NonNegativeInt,
1558
- ): Result<TimestampBytes, SqliteError> => {
1494
+ (ownerId: OwnerIdBytes, index: NonNegativeInt): TimestampBytes => {
1559
1495
  const result = deps.sqlite.exec<{
1560
1496
  readonly pt: TimestampBytes;
1561
1497
  }>(sql.prepared`
@@ -1630,8 +1566,7 @@ export const getTimestampByIndex =
1630
1566
  where ic == ${index + 1};
1631
1567
  `);
1632
1568
 
1633
- if (!result.ok) return result;
1634
- return ok(result.value.rows[0].pt);
1569
+ return result.rows[0].pt;
1635
1570
  };
1636
1571
 
1637
1572
  /** Retrieves usage information for an owner from the evolu_usage table. */
@@ -1640,14 +1575,11 @@ export const getOwnerUsage =
1640
1575
  (
1641
1576
  ownerIdBytes: OwnerIdBytes,
1642
1577
  initialTimestamp: TimestampBytes,
1643
- ): Result<
1644
- {
1645
- storedBytes: NonNegativeInt | null;
1646
- firstTimestamp: TimestampBytes;
1647
- lastTimestamp: TimestampBytes;
1648
- },
1649
- SqliteError
1650
- > => {
1578
+ ): Result<{
1579
+ storedBytes: NonNegativeInt | null;
1580
+ firstTimestamp: TimestampBytes;
1581
+ lastTimestamp: TimestampBytes;
1582
+ }> => {
1651
1583
  const result = deps.sqlite.exec<{
1652
1584
  storedBytes: NonNegativeInt;
1653
1585
  firstTimestamp: TimestampBytes | null;
@@ -1657,9 +1589,8 @@ export const getOwnerUsage =
1657
1589
  from evolu_usage
1658
1590
  where ownerId = ${ownerIdBytes};
1659
1591
  `);
1660
- if (!result.ok) return result;
1661
1592
 
1662
- if (!isNonEmptyReadonlyArray(result.value.rows)) {
1593
+ if (!isNonEmptyArray(result.rows)) {
1663
1594
  return ok({
1664
1595
  storedBytes: null,
1665
1596
  firstTimestamp: initialTimestamp,
@@ -1667,7 +1598,7 @@ export const getOwnerUsage =
1667
1598
  });
1668
1599
  }
1669
1600
 
1670
- const row = firstInArray(result.value.rows);
1601
+ const row = firstInArray(result.rows);
1671
1602
  assert(row.firstTimestamp, "not null");
1672
1603
  assert(row.lastTimestamp, "not null");
1673
1604
 
@@ -1691,8 +1622,8 @@ export const updateOwnerUsage =
1691
1622
  storedBytes: PositiveInt,
1692
1623
  firstTimestamp: TimestampBytes,
1693
1624
  lastTimestamp: TimestampBytes,
1694
- ): Result<void, SqliteError> => {
1695
- const result = deps.sqlite.exec(sql`
1625
+ ): void => {
1626
+ deps.sqlite.exec(sql`
1696
1627
  insert into evolu_usage
1697
1628
  ("ownerId", "storedBytes", "firstTimestamp", "lastTimestamp")
1698
1629
  values
@@ -1703,6 +1634,4 @@ export const updateOwnerUsage =
1703
1634
  firstTimestamp = ${firstTimestamp},
1704
1635
  lastTimestamp = ${lastTimestamp};
1705
1636
  `);
1706
- if (!result.ok) return result;
1707
- return ok();
1708
1637
  };