@evolu/common 6.0.1-preview.19 → 6.0.1-preview.20

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 (140) hide show
  1. package/dist/src/Assert.d.ts.map +1 -1
  2. package/dist/src/Assert.js +1 -1
  3. package/dist/src/Buffer.d.ts +1 -1
  4. package/dist/src/Buffer.d.ts.map +1 -1
  5. package/dist/src/Buffer.js +1 -1
  6. package/dist/src/CallbackRegistry.d.ts +53 -0
  7. package/dist/src/CallbackRegistry.d.ts.map +1 -0
  8. package/dist/src/CallbackRegistry.js +25 -0
  9. package/dist/src/Console.d.ts +31 -6
  10. package/dist/src/Console.d.ts.map +1 -1
  11. package/dist/src/Console.js +72 -9
  12. package/dist/src/Crypto.d.ts +48 -37
  13. package/dist/src/Crypto.d.ts.map +1 -1
  14. package/dist/src/Crypto.js +27 -50
  15. package/dist/src/Evolu/Db.d.ts +138 -66
  16. package/dist/src/Evolu/Db.d.ts.map +1 -1
  17. package/dist/src/Evolu/Db.js +248 -645
  18. package/dist/src/Evolu/Diff.d.ts +3 -3
  19. package/dist/src/Evolu/Diff.d.ts.map +1 -1
  20. package/dist/src/Evolu/Diff.js +7 -5
  21. package/dist/src/Evolu/Evolu.d.ts +79 -116
  22. package/dist/src/Evolu/Evolu.d.ts.map +1 -1
  23. package/dist/src/Evolu/Evolu.js +275 -132
  24. package/dist/src/Evolu/Internal.d.ts +0 -2
  25. package/dist/src/Evolu/Internal.d.ts.map +1 -1
  26. package/dist/src/Evolu/Internal.js +0 -2
  27. package/dist/src/Evolu/LocalAuth.d.ts +144 -0
  28. package/dist/src/Evolu/LocalAuth.d.ts.map +1 -0
  29. package/dist/src/Evolu/LocalAuth.js +171 -0
  30. package/dist/src/Evolu/Owner.d.ts +129 -83
  31. package/dist/src/Evolu/Owner.d.ts.map +1 -1
  32. package/dist/src/Evolu/Owner.js +80 -89
  33. package/dist/src/Evolu/Platform.d.ts +9 -7
  34. package/dist/src/Evolu/Platform.d.ts.map +1 -1
  35. package/dist/src/Evolu/Protocol.d.ts +114 -191
  36. package/dist/src/Evolu/Protocol.d.ts.map +1 -1
  37. package/dist/src/Evolu/Protocol.js +409 -416
  38. package/dist/src/Evolu/Public.d.ts +6 -8
  39. package/dist/src/Evolu/Public.d.ts.map +1 -1
  40. package/dist/src/Evolu/Public.js +2 -3
  41. package/dist/src/Evolu/PublicKysely.js +3 -3
  42. package/dist/src/Evolu/Relay.d.ts +1 -2
  43. package/dist/src/Evolu/Relay.d.ts.map +1 -1
  44. package/dist/src/Evolu/Relay.js +11 -9
  45. package/dist/src/Evolu/Schema.d.ts +88 -27
  46. package/dist/src/Evolu/Schema.d.ts.map +1 -1
  47. package/dist/src/Evolu/Schema.js +141 -24
  48. package/dist/src/Evolu/Storage.d.ts +158 -14
  49. package/dist/src/Evolu/Storage.d.ts.map +1 -1
  50. package/dist/src/Evolu/Storage.js +32 -32
  51. package/dist/src/Evolu/Sync.d.ts +77 -13
  52. package/dist/src/Evolu/Sync.d.ts.map +1 -1
  53. package/dist/src/Evolu/Sync.js +453 -20
  54. package/dist/src/Evolu/Timestamp.d.ts +29 -27
  55. package/dist/src/Evolu/Timestamp.d.ts.map +1 -1
  56. package/dist/src/Evolu/Timestamp.js +20 -18
  57. package/dist/src/ManyToManyMap.d.ts +74 -10
  58. package/dist/src/ManyToManyMap.d.ts.map +1 -1
  59. package/dist/src/ManyToManyMap.js +41 -6
  60. package/dist/src/Random.d.ts +3 -2
  61. package/dist/src/Random.d.ts.map +1 -1
  62. package/dist/src/RefCountedResourceManager.d.ts +119 -0
  63. package/dist/src/RefCountedResourceManager.d.ts.map +1 -0
  64. package/dist/src/RefCountedResourceManager.js +197 -0
  65. package/dist/src/Result.d.ts +144 -22
  66. package/dist/src/Result.d.ts.map +1 -1
  67. package/dist/src/Result.js +5 -2
  68. package/dist/src/Sqlite.d.ts +20 -4
  69. package/dist/src/Sqlite.d.ts.map +1 -1
  70. package/dist/src/Sqlite.js +50 -8
  71. package/dist/src/Task.d.ts +511 -0
  72. package/dist/src/Task.d.ts.map +1 -0
  73. package/dist/src/Task.js +410 -0
  74. package/dist/src/Time.d.ts +59 -0
  75. package/dist/src/Time.d.ts.map +1 -1
  76. package/dist/src/Time.js +87 -4
  77. package/dist/src/Type.d.ts +431 -341
  78. package/dist/src/Type.d.ts.map +1 -1
  79. package/dist/src/Type.js +458 -466
  80. package/dist/src/WebSocket.d.ts +5 -2
  81. package/dist/src/WebSocket.d.ts.map +1 -1
  82. package/dist/src/WebSocket.js +12 -13
  83. package/dist/src/Worker.d.ts +39 -11
  84. package/dist/src/Worker.d.ts.map +1 -1
  85. package/dist/src/Worker.js +22 -4
  86. package/dist/src/index.d.ts +2 -3
  87. package/dist/src/index.d.ts.map +1 -1
  88. package/dist/src/index.js +2 -3
  89. package/package.json +7 -7
  90. package/src/Assert.ts +2 -4
  91. package/src/Buffer.ts +1 -1
  92. package/src/CallbackRegistry.ts +84 -0
  93. package/src/Console.ts +91 -11
  94. package/src/Crypto.ts +78 -91
  95. package/src/Evolu/Db.ts +455 -947
  96. package/src/Evolu/Diff.ts +7 -5
  97. package/src/Evolu/Evolu.ts +545 -307
  98. package/src/Evolu/Internal.ts +0 -2
  99. package/src/Evolu/LocalAuth.ts +422 -0
  100. package/src/Evolu/Owner.ts +191 -131
  101. package/src/Evolu/Platform.ts +9 -9
  102. package/src/Evolu/Protocol.ts +536 -653
  103. package/src/Evolu/Public.ts +7 -9
  104. package/src/Evolu/PublicKysely.ts +3 -3
  105. package/src/Evolu/Relay.ts +17 -12
  106. package/src/Evolu/Schema.ts +271 -66
  107. package/src/Evolu/Storage.ts +263 -55
  108. package/src/Evolu/Sync.ts +758 -37
  109. package/src/Evolu/Timestamp.ts +30 -35
  110. package/src/ManyToManyMap.ts +127 -24
  111. package/src/Random.ts +3 -2
  112. package/src/RefCountedResourceManager.ts +368 -0
  113. package/src/Result.ts +149 -23
  114. package/src/Sqlite.ts +59 -24
  115. package/src/Task.ts +779 -0
  116. package/src/Time.ts +168 -4
  117. package/src/Type.ts +657 -695
  118. package/src/WebSocket.ts +23 -17
  119. package/src/Worker.ts +72 -23
  120. package/src/index.ts +2 -3
  121. package/dist/src/Callbacks.d.ts +0 -20
  122. package/dist/src/Callbacks.d.ts.map +0 -1
  123. package/dist/src/Callbacks.js +0 -18
  124. package/dist/src/Evolu/Config.d.ts +0 -82
  125. package/dist/src/Evolu/Config.d.ts.map +0 -1
  126. package/dist/src/Evolu/Config.js +0 -9
  127. package/dist/src/Evolu/Kysely.d.ts +0 -6
  128. package/dist/src/Evolu/Kysely.d.ts.map +0 -1
  129. package/dist/src/Evolu/Kysely.js +0 -21
  130. package/dist/src/NanoId.d.ts +0 -27
  131. package/dist/src/NanoId.d.ts.map +0 -1
  132. package/dist/src/NanoId.js +0 -6
  133. package/dist/src/Promise.d.ts +0 -180
  134. package/dist/src/Promise.d.ts.map +0 -1
  135. package/dist/src/Promise.js +0 -176
  136. package/src/Callbacks.ts +0 -43
  137. package/src/Evolu/Config.ts +0 -97
  138. package/src/Evolu/Kysely.ts +0 -38
  139. package/src/NanoId.ts +0 -39
  140. package/src/Promise.ts +0 -295
@@ -1,10 +1,230 @@
1
+ import { sha256 } from "@noble/hashes/sha2.js";
2
+ import { NonEmptyReadonlyArray } from "../Array.js";
3
+ import { assert } from "../Assert.js";
4
+ import { Brand } from "../Brand.js";
5
+ import { decrement } from "../Number.js";
6
+ import { RandomDep } from "../Random.js";
7
+ import { ok, Result } from "../Result.js";
8
+ import { sql, SqliteDep, SqliteError, SqliteValue } from "../Sqlite.js";
9
+ import {
10
+ Id,
11
+ Int64String,
12
+ NonNegativeInt,
13
+ object,
14
+ PositiveInt,
15
+ record,
16
+ String,
17
+ } from "../Type.js";
18
+ import {
19
+ Owner,
20
+ OwnerId,
21
+ OwnerIdBytes,
22
+ ownerIdBytesToOwnerId,
23
+ OwnerWriteKey,
24
+ } from "./Owner.js";
25
+ import { orderTimestampBytes, Timestamp, TimestampBytes } from "./Timestamp.js";
26
+
1
27
  /**
2
- * Evolu Storage for SQLite
28
+ * Evolu Storage
29
+ *
30
+ * The protocol using Storage is agnostic to storage implementation details—any
31
+ * storage can be plugged in, as long as it implements this interface.
32
+ * Implementations must handle their own errors; return values only indicate
33
+ * overall success or failure.
34
+ *
35
+ * The Storage API is synchronous because SQLite's synchronous API is the
36
+ * fastest way to use SQLite. Synchronous bindings (like better-sqlite3) call
37
+ * SQLite's C API directly with no context switching between the event loop and
38
+ * native code, and no promise microtasks or await overhead.
39
+ *
40
+ * The only exception is {@link Storage#writeMessages}, which is async to allow
41
+ * for async validation logic before writing to storage. The write operation
42
+ * itself remains synchronous.
43
+ */
44
+ export interface Storage {
45
+ readonly getSize: (ownerId: OwnerIdBytes) => NonNegativeInt | null;
46
+
47
+ readonly fingerprint: (
48
+ ownerId: OwnerIdBytes,
49
+ begin: NonNegativeInt,
50
+ end: NonNegativeInt,
51
+ ) => Fingerprint | null;
52
+
53
+ /**
54
+ * Computes fingerprints with their upper bounds in one call.
55
+ *
56
+ * This function can be replaced with many fingerprint/findLowerBound calls,
57
+ * but implementations can leverage it for batching and more efficient
58
+ * fingerprint computation.
59
+ */
60
+ readonly fingerprintRanges: (
61
+ ownerId: OwnerIdBytes,
62
+ buckets: ReadonlyArray<NonNegativeInt>,
63
+ upperBound?: RangeUpperBound,
64
+ ) => ReadonlyArray<FingerprintRange> | null;
65
+
66
+ readonly findLowerBound: (
67
+ ownerId: OwnerIdBytes,
68
+ begin: NonNegativeInt,
69
+ end: NonNegativeInt,
70
+ upperBound: RangeUpperBound,
71
+ ) => NonNegativeInt | null;
72
+
73
+ readonly iterate: (
74
+ ownerId: OwnerIdBytes,
75
+ begin: NonNegativeInt,
76
+ end: NonNegativeInt,
77
+ callback: (timestamp: TimestampBytes, index: NonNegativeInt) => boolean,
78
+ ) => void;
79
+
80
+ /**
81
+ * Validates the {@link OwnerWriteKey} for the given {@link Owner}.
82
+ *
83
+ * Returns `true` if the write key is valid, `false` otherwise.
84
+ */
85
+ readonly validateWriteKey: (
86
+ ownerId: OwnerIdBytes,
87
+ writeKey: OwnerWriteKey,
88
+ ) => boolean;
89
+
90
+ /** Sets the {@link OwnerWriteKey} for the given {@link Owner}. */
91
+ readonly setWriteKey: (
92
+ ownerId: OwnerIdBytes,
93
+ writeKey: OwnerWriteKey,
94
+ ) => boolean;
95
+
96
+ /**
97
+ * Write encrypted {@link CrdtMessage}s to storage.
98
+ *
99
+ * Must use a mutex (per ownerId on Relay) to ensure sequential processing and
100
+ * proper protocol logic handling during sync operations.
101
+ *
102
+ * Returns `true` on success, `false` on failure.
103
+ */
104
+ readonly writeMessages: (
105
+ ownerId: OwnerIdBytes,
106
+ messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>,
107
+ ) => Promise<boolean>;
108
+
109
+ /** Read encrypted {@link DbChange}s from storage. */
110
+ readonly readDbChange: (
111
+ ownerId: OwnerIdBytes,
112
+ timestamp: TimestampBytes,
113
+ ) => EncryptedDbChange | null;
114
+
115
+ /**
116
+ * Delete all data for the given {@link Owner}.
117
+ *
118
+ * Returns `true` on success, `false` on failure.
119
+ */
120
+ readonly deleteOwner: (ownerId: OwnerIdBytes) => boolean;
121
+ }
122
+
123
+ export interface StorageDep {
124
+ readonly storage: Storage;
125
+ }
126
+
127
+ /**
128
+ * A cryptographic hash used for efficiently comparing collections of
129
+ * {@link TimestampBytes}s.
130
+ *
131
+ * It consists of the first {@link fingerprintSize} bytes of the SHA-256 hash of
132
+ * one or more timestamps.
133
+ */
134
+ export type Fingerprint = Uint8Array & Brand<"Fingerprint">;
135
+
136
+ export const fingerprintSize = 12 as NonNegativeInt;
137
+
138
+ /** A fingerprint of an empty range. */
139
+ export const zeroFingerprint = new Uint8Array(fingerprintSize) as Fingerprint;
140
+
141
+ export interface BaseRange {
142
+ readonly upperBound: RangeUpperBound;
143
+ }
144
+
145
+ /**
146
+ * Union type for Range's upperBound: either a {@link TimestampBytes} or
147
+ * {@link InfiniteUpperBound}.
148
+ */
149
+ export type RangeUpperBound = TimestampBytes | InfiniteUpperBound;
150
+
151
+ export const InfiniteUpperBound = Symbol("InfiniteUpperBound");
152
+ export type InfiniteUpperBound = typeof InfiniteUpperBound;
153
+
154
+ export const RangeType = {
155
+ Fingerprint: 1,
156
+ Skip: 0,
157
+ Timestamps: 2,
158
+ } as const;
159
+
160
+ export type RangeType = (typeof RangeType)[keyof typeof RangeType];
161
+
162
+ export interface SkipRange extends BaseRange {
163
+ readonly type: typeof RangeType.Skip;
164
+ }
165
+
166
+ export interface FingerprintRange extends BaseRange {
167
+ readonly type: typeof RangeType.Fingerprint;
168
+ readonly fingerprint: Fingerprint;
169
+ }
170
+
171
+ export interface TimestampsRange extends BaseRange {
172
+ readonly type: typeof RangeType.Timestamps;
173
+ readonly timestamps: ReadonlyArray<TimestampBytes>;
174
+ }
175
+
176
+ export type Range = SkipRange | FingerprintRange | TimestampsRange;
177
+
178
+ /** An encrypted {@link CrdtMessage}. */
179
+ export interface EncryptedCrdtMessage {
180
+ readonly timestamp: Timestamp;
181
+ readonly change: EncryptedDbChange;
182
+ }
183
+
184
+ /** Encrypted DbChange */
185
+ export type EncryptedDbChange = Uint8Array & Brand<"EncryptedDbChange">;
186
+
187
+ /**
188
+ * A CRDT message combining a unique {@link Timestamp} with a {@link DbChange}.
189
+ *
190
+ * Used in Evolu's sync protocol to replicate data changes across devices. Evolu
191
+ * operates as a durable queue, providing exactly-once delivery guarantees for
192
+ * reliable synchronization across application restarts and network failures.
193
+ */
194
+ export interface CrdtMessage {
195
+ readonly timestamp: Timestamp;
196
+ readonly change: DbChange;
197
+ }
198
+
199
+ /**
200
+ * A DbChange is a change to a table row. Together with a unique
201
+ * {@link Timestamp}, it forms a {@link CrdtMessage}.
202
+ */
203
+ export const DbChange = object({
204
+ table: String,
205
+ id: Id,
206
+ values: record(String, SqliteValue),
207
+ });
208
+ export type DbChange = typeof DbChange.Type;
209
+
210
+ /**
211
+ * Common interface for both client and relay SQLite storages.
3
212
  *
4
213
  * Evolu uses a Skiplist, which leverages SQLite indexes. The core logic is
5
214
  * implemented in SQL, so it doesn't have to make roundtrips to the DB.
6
215
  *
7
- * The ideal storage for a Relay should use a similar architecture to
216
+ * While the SQL implementation may look sophisticated, it's conceptually simple
217
+ * and LLMs can explain how it works. The Skiplist data structure is well
218
+ * explained in [this Stack Overflow
219
+ * answer](https://stackoverflow.com/questions/61944198/what-is-a-zip-tree-and-how-does-it-work).
220
+ * The logic resembles [Negentropy's C++
221
+ * storage](https://github.com/hoytech/negentropy), except we use a Skiplist to
222
+ * leverage SQLite indexes, which makes the code simpler.
223
+ *
224
+ * Note: A paid review by the SQLite team is planned, as they use the same
225
+ * algorithm for their rsync tool.
226
+ *
227
+ * The ideal storage for a Relay should use an architecture like
8
228
  * [strfry](https://github.com/hoytech/strfry) (a KV storage), but with Skiplist
9
229
  * to ensure that insertion order doesn't matter (local-first apps can often
10
230
  * write in the past.)
@@ -19,37 +239,18 @@
19
239
  * each other, if necessary. One relay should handle hundreds of thousands of
20
240
  * users, and when it goes down, nothing happens, because it will be
21
241
  * synchronized later.
22
- *
23
- * @module
24
242
  */
25
-
26
- import { assert } from "../Assert.js";
27
- import { decrement } from "../Number.js";
28
- import { RandomDep } from "../Random.js";
29
- import { ok, Result } from "../Result.js";
30
- import { sql, SqliteDep, SqliteError } from "../Sqlite.js";
31
- import { Int64String, NonNegativeInt, PositiveInt } from "../Type.js";
32
- import { Brand } from "../Brand.js";
33
- import { OwnerId } from "./Owner.js";
34
- import {
35
- BinaryOwnerId,
36
- binaryOwnerIdToOwnerId,
37
- binaryTimestampToFingerprint,
38
- Fingerprint,
39
- FingerprintRange,
40
- InfiniteUpperBound,
41
- RangeType,
42
- RangeUpperBound,
43
- Storage,
44
- zeroFingerprint,
45
- } from "./Protocol.js";
46
- import { BinaryTimestamp, orderBinaryTimestamp } from "./Timestamp.js";
47
-
48
- /** Common interface for both client and relay SQLite storages. */
49
243
  export interface SqliteStorageBase {
244
+ /**
245
+ * Inserts a timestamp for an owner into the skiplist-based storage.
246
+ *
247
+ * Must be idempotent - inserting the same timestamp multiple times has no
248
+ * effect after the first insertion. This is crucial for sync reliability as
249
+ * messages may be received and processed multiple times.
250
+ */
50
251
  readonly insertTimestamp: (
51
- ownerId: BinaryOwnerId,
52
- timestamp: BinaryTimestamp,
252
+ ownerId: OwnerIdBytes,
253
+ timestamp: TimestampBytes,
53
254
  ) => Result<void, SqliteError>;
54
255
 
55
256
  readonly getSize: Storage["getSize"];
@@ -64,7 +265,7 @@ export interface SqliteStorageBaseDep {
64
265
  readonly storage: SqliteStorageBase;
65
266
  }
66
267
 
67
- export type SqliteStorageDeps = SqliteDep & RandomDep;
268
+ export type SqliteStorageDeps = RandomDep & SqliteDep;
68
269
 
69
270
  export interface CreateSqliteStorageBaseOptions {
70
271
  onStorageError: (error: SqliteError) => void;
@@ -81,22 +282,22 @@ export const createSqliteStorageBase =
81
282
  const ownerStats = new Map<
82
283
  OwnerId,
83
284
  {
84
- minT: BinaryTimestamp;
85
- maxT: BinaryTimestamp;
285
+ minT: TimestampBytes;
286
+ maxT: TimestampBytes;
86
287
  }
87
288
  >();
88
289
 
89
290
  return ok({
90
- insertTimestamp: (ownerId: BinaryOwnerId, timestamp: BinaryTimestamp) => {
91
- const ownerIdString = binaryOwnerIdToOwnerId(ownerId);
291
+ insertTimestamp: (ownerId: OwnerIdBytes, timestamp: TimestampBytes) => {
292
+ const ownerIdString = ownerIdBytesToOwnerId(ownerId);
92
293
  const level = randomSkiplistLevel(deps);
93
294
 
94
295
  let stats = ownerStats.get(ownerIdString);
95
296
 
96
297
  if (!stats) {
97
298
  const result = deps.sqlite.exec<{
98
- maxT: BinaryTimestamp | null;
99
- minT: BinaryTimestamp | null;
299
+ maxT: TimestampBytes | null;
300
+ minT: TimestampBytes | null;
100
301
  }>(sql.prepared`
101
302
  select min(t) as minT, max(t) as maxT
102
303
  from evolu_timestamp
@@ -113,10 +314,10 @@ export const createSqliteStorageBase =
113
314
 
114
315
  let strategy: InsertTimestampStrategy;
115
316
 
116
- if (orderBinaryTimestamp(timestamp, stats.maxT) === 1) {
317
+ if (orderTimestampBytes(timestamp, stats.maxT) === 1) {
117
318
  strategy = "append";
118
319
  stats.maxT = timestamp;
119
- } else if (orderBinaryTimestamp(timestamp, stats.minT) === -1) {
320
+ } else if (orderTimestampBytes(timestamp, stats.minT) === -1) {
120
321
  strategy = "prepend";
121
322
  stats.minT = timestamp;
122
323
  } else {
@@ -193,7 +394,7 @@ export const createSqliteStorageBase =
193
394
  * implementing chunking, be sure to run performance tests (including
194
395
  * fetching one by one).
195
396
  */
196
- const result = deps.sqlite.exec<{ t: BinaryTimestamp }>(sql`
397
+ const result = deps.sqlite.exec<{ t: TimestampBytes }>(sql`
197
398
  select t
198
399
  from evolu_timestamp
199
400
  where ownerId = ${ownerId} and t > ${first.value}
@@ -241,7 +442,7 @@ const createTables = (deps: SqliteDep): Result<void, SqliteError> => {
241
442
  *
242
443
  * Columns:
243
444
  *
244
- * - `t` – globally unique binary timestamp
445
+ * - `t` – TimestampBytes
245
446
  * - `h1`/`h2` – 12-byte fingerprint split into two integers for fast XOR
246
447
  * - `c` – incremental count
247
448
  * - `l` – Skiplist level (1 to 32)
@@ -295,13 +496,13 @@ type InsertTimestampStrategy = "append" | "prepend" | "insert";
295
496
  const insertTimestamp =
296
497
  (deps: SqliteDep) =>
297
498
  (
298
- ownerId: BinaryOwnerId,
299
- timestamp: BinaryTimestamp,
499
+ ownerId: OwnerIdBytes,
500
+ timestamp: TimestampBytes,
300
501
  level: PositiveInt,
301
502
  strategy: InsertTimestampStrategy,
302
503
  ): Result<void, SqliteError> => {
303
504
  const [h1, h2] = fingerprintToSqliteFingerprint(
304
- binaryTimestampToFingerprint(timestamp),
505
+ timestampBytesToFingerprint(timestamp),
305
506
  );
306
507
 
307
508
  let queries: Array<ReturnType<typeof sql.prepared>> = [];
@@ -819,6 +1020,13 @@ const insertTimestamp =
819
1020
  return ok();
820
1021
  };
821
1022
 
1023
+ export const timestampBytesToFingerprint = (
1024
+ timestamp: TimestampBytes,
1025
+ ): Fingerprint => {
1026
+ const hash = sha256(timestamp).slice(0, fingerprintSize);
1027
+ return hash as Fingerprint;
1028
+ };
1029
+
822
1030
  /**
823
1031
  * Generates a random skiplist level in the range [1, skiplistMaxLevel].
824
1032
  * Probabilistic approach avoids the need for explicit tree balancing.
@@ -899,7 +1107,7 @@ const sqliteFingerprintToFingerprint = ([
899
1107
 
900
1108
  const getSize =
901
1109
  (deps: SqliteDep) =>
902
- (ownerId: BinaryOwnerId): Result<NonNegativeInt, SqliteError> => {
1110
+ (ownerId: OwnerIdBytes): Result<NonNegativeInt, SqliteError> => {
903
1111
  const result = deps.sqlite.exec<{ size: NonNegativeInt }>(sql.prepared`
904
1112
  with
905
1113
  ml(ml) as (
@@ -942,7 +1150,7 @@ const getSize =
942
1150
  const findLowerBound =
943
1151
  (deps: SqliteDep) =>
944
1152
  (
945
- ownerId: BinaryOwnerId,
1153
+ ownerId: OwnerIdBytes,
946
1154
  begin: NonNegativeInt,
947
1155
  end: NonNegativeInt,
948
1156
  upperBound: RangeUpperBound,
@@ -954,7 +1162,7 @@ const findLowerBound =
954
1162
  }
955
1163
 
956
1164
  const result = deps.sqlite.exec<{
957
- t: BinaryTimestamp;
1165
+ t: TimestampBytes;
958
1166
  }>(sql.prepared`
959
1167
  select t
960
1168
  from evolu_timestamp
@@ -978,8 +1186,8 @@ const findLowerBound =
978
1186
  const getTimestampCount =
979
1187
  (deps: SqliteDep) =>
980
1188
  (
981
- ownerId: BinaryOwnerId,
982
- timestamp: BinaryTimestamp,
1189
+ ownerId: OwnerIdBytes,
1190
+ timestamp: TimestampBytes,
983
1191
  ): Result<PositiveInt, SqliteError> => {
984
1192
  const result = deps.sqlite.exec<{
985
1193
  count: PositiveInt;
@@ -1036,7 +1244,7 @@ const getTimestampCount =
1036
1244
  const fingerprint =
1037
1245
  (deps: SqliteDep) =>
1038
1246
  (
1039
- ownerId: BinaryOwnerId,
1247
+ ownerId: OwnerIdBytes,
1040
1248
  begin: NonNegativeInt,
1041
1249
  end: NonNegativeInt,
1042
1250
  ): Result<Fingerprint, SqliteError> => {
@@ -1068,14 +1276,14 @@ const fingerprint =
1068
1276
  const fingerprintRanges =
1069
1277
  (deps: SqliteDep) =>
1070
1278
  (
1071
- ownerId: BinaryOwnerId,
1279
+ ownerId: OwnerIdBytes,
1072
1280
  buckets: ReadonlyArray<NonNegativeInt>,
1073
1281
  upperBound: RangeUpperBound = InfiniteUpperBound,
1074
1282
  ): Result<ReadonlyArray<FingerprintRange>, SqliteError> => {
1075
1283
  const bucketsJson = JSON.stringify(buckets);
1076
1284
 
1077
1285
  const result = deps.sqlite.exec<{
1078
- b: BinaryTimestamp | null;
1286
+ b: TimestampBytes | null;
1079
1287
  h1: Int64String;
1080
1288
  h2: Int64String;
1081
1289
  }>(sql.prepared`
@@ -1211,11 +1419,11 @@ const x = (a: string, b: string) => sql.raw(`(${a} | ${b}) - (${a} & ${b})`);
1211
1419
  export const getTimestampByIndex =
1212
1420
  (deps: SqliteDep) =>
1213
1421
  (
1214
- ownerId: BinaryOwnerId,
1422
+ ownerId: OwnerIdBytes,
1215
1423
  index: NonNegativeInt,
1216
- ): Result<BinaryTimestamp, SqliteError> => {
1424
+ ): Result<TimestampBytes, SqliteError> => {
1217
1425
  const result = deps.sqlite.exec<{
1218
- readonly pt: BinaryTimestamp;
1426
+ readonly pt: TimestampBytes;
1219
1427
  }>(sql.prepared`
1220
1428
  with
1221
1429
  fi(b, cl, ic, pt, mt, nt, nc) as (