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