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