@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.
- 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 +851 -430
- 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 -824
- 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 +206 -277
- 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 -526
- package/src/Instances.ts +0 -92
- 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 -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
|
-
|
|
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) => {
|
|
@@ -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
|
|
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
|
|
343
|
+
export interface BaseSqliteStorage extends Omit<
|
|
327
344
|
Storage,
|
|
328
|
-
| "
|
|
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
|
-
) =>
|
|
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
|
-
) =>
|
|
361
|
+
) => ReadonlyArray<TimestampBytes>;
|
|
350
362
|
}
|
|
351
363
|
|
|
352
364
|
export interface BaseSqliteStorageDep {
|
|
353
|
-
readonly
|
|
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
|
-
|
|
377
|
-
|
|
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
|
-
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
|
-
|
|
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
|
-
):
|
|
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
|
-
|
|
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):
|
|
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
|
-
|
|
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
|
-
):
|
|
1243
|
+
): NonNegativeInt => {
|
|
1292
1244
|
assertBeginEnd(begin, end);
|
|
1293
1245
|
|
|
1294
1246
|
if (end === 0 || begin === end || upperBound === InfiniteUpperBound) {
|
|
1295
|
-
return
|
|
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.
|
|
1310
|
-
return
|
|
1260
|
+
if (result.rows.length === 0) {
|
|
1261
|
+
return end;
|
|
1311
1262
|
}
|
|
1312
1263
|
|
|
1313
|
-
const count = getTimestampCount(deps)(ownerId, result.
|
|
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
|
|
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
|
-
|
|
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
|
-
):
|
|
1329
|
+
): Fingerprint => {
|
|
1385
1330
|
// There is no need to fingerprint an empty range.
|
|
1386
1331
|
if (end - begin === 0) {
|
|
1387
|
-
return
|
|
1332
|
+
return zeroFingerprint;
|
|
1388
1333
|
}
|
|
1389
1334
|
|
|
1390
1335
|
if (begin === 0) {
|
|
1391
|
-
|
|
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
|
-
|
|
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
|
-
):
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
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 (!
|
|
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.
|
|
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
|
-
):
|
|
1695
|
-
|
|
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
|
};
|