@evolu/common 8.9.0 → 8.11.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/dist/src/Bytes.d.ts +647 -0
- package/dist/src/Bytes.d.ts.map +1 -0
- package/dist/src/{Binary.js → Bytes.js} +266 -16
- package/dist/src/Config.d.ts +142 -0
- package/dist/src/Config.d.ts.map +1 -0
- package/dist/src/Config.js +181 -0
- package/dist/src/Console.d.ts +62 -7
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +20 -4
- package/dist/src/Crypto.d.ts +76 -4
- package/dist/src/Crypto.d.ts.map +1 -1
- package/dist/src/Crypto.js +55 -4
- package/dist/src/Error.d.ts +45 -0
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +69 -0
- package/dist/src/Fs.d.ts +376 -0
- package/dist/src/Fs.d.ts.map +1 -0
- package/dist/src/Fs.js +113 -0
- package/dist/src/Identicon.d.ts +2 -2
- package/dist/src/Identicon.js +2 -2
- package/dist/src/LeakDetector.d.ts +22 -3
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +12 -2
- package/dist/src/LockManager.d.ts +8 -0
- package/dist/src/LockManager.d.ts.map +1 -1
- package/dist/src/LockManager.js +6 -0
- package/dist/src/Number.d.ts +50 -7
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +47 -8
- package/dist/src/Object.d.ts +32 -0
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +46 -0
- package/dist/src/Platform.d.ts +47 -7
- package/dist/src/Platform.d.ts.map +1 -1
- package/dist/src/Platform.js +24 -5
- package/dist/src/Random.d.ts +25 -2
- package/dist/src/Random.d.ts.map +1 -1
- package/dist/src/Random.js +14 -2
- package/dist/src/Resource.d.ts +156 -1
- package/dist/src/Resource.d.ts.map +1 -1
- package/dist/src/Resource.js +201 -72
- package/dist/src/Schedule.d.ts +11 -10
- package/dist/src/Schedule.d.ts.map +1 -1
- package/dist/src/Schedule.js +1 -1
- package/dist/src/Sqlite.d.ts +132 -16
- package/dist/src/Sqlite.d.ts.map +1 -1
- package/dist/src/Sqlite.js +64 -10
- package/dist/src/Task.d.ts +15 -4
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +41 -15
- package/dist/src/Test.d.ts +9 -0
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +4 -0
- package/dist/src/Time.d.ts +179 -20
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +95 -6
- package/dist/src/Type.d.ts +3056 -1539
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +2548 -584
- package/dist/src/WebSocket.d.ts +164 -13
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/WebSocket.js +133 -24
- package/dist/src/Worker.d.ts +90 -8
- package/dist/src/Worker.d.ts.map +1 -1
- package/dist/src/Worker.js +28 -2
- package/dist/src/index.d.ts +9 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -4
- package/dist/src/intl/_en.d.ts +24 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +20 -0
- package/dist/src/intl/ar.d.ts +24 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +20 -0
- package/dist/src/intl/bn.d.ts +24 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +20 -0
- package/dist/src/intl/ca.d.ts +24 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +20 -0
- package/dist/src/intl/cs.d.ts +24 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +20 -0
- package/dist/src/intl/da.d.ts +24 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +20 -0
- package/dist/src/intl/de.d.ts +24 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +20 -0
- package/dist/src/intl/el.d.ts +24 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +20 -0
- package/dist/src/intl/es.d.ts +24 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +20 -0
- package/dist/src/intl/fa.d.ts +24 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +20 -0
- package/dist/src/intl/fi.d.ts +24 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +20 -0
- package/dist/src/intl/fil.d.ts +24 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +20 -0
- package/dist/src/intl/fr.d.ts +24 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +20 -0
- package/dist/src/intl/he.d.ts +24 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +20 -0
- package/dist/src/intl/hi.d.ts +24 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +20 -0
- package/dist/src/intl/hr.d.ts +24 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +20 -0
- package/dist/src/intl/hu.d.ts +22 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +18 -0
- package/dist/src/intl/id.d.ts +24 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +20 -0
- package/dist/src/intl/it.d.ts +24 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +20 -0
- package/dist/src/intl/ja.d.ts +24 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +20 -0
- package/dist/src/intl/ko.d.ts +24 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +20 -0
- package/dist/src/intl/ml.d.ts +24 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +20 -0
- package/dist/src/intl/mr.d.ts +24 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +20 -0
- package/dist/src/intl/ms.d.ts +24 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +20 -0
- package/dist/src/intl/nb.d.ts +22 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +18 -0
- package/dist/src/intl/nl.d.ts +24 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +20 -0
- package/dist/src/intl/pa.d.ts +24 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +20 -0
- package/dist/src/intl/pl.d.ts +23 -0
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +20 -0
- package/dist/src/intl/pt-BR.d.ts +24 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +20 -0
- package/dist/src/intl/pt.d.ts +24 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +20 -0
- package/dist/src/intl/ro.d.ts +24 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +20 -0
- package/dist/src/intl/sk.d.ts +24 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +20 -0
- package/dist/src/intl/sl.d.ts +24 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +20 -0
- package/dist/src/intl/sv.d.ts +24 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +20 -0
- package/dist/src/intl/sw.d.ts +21 -0
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +18 -0
- package/dist/src/intl/ta.d.ts +24 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +20 -0
- package/dist/src/intl/te.d.ts +24 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +20 -0
- package/dist/src/intl/th.d.ts +24 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +20 -0
- package/dist/src/intl/tr.d.ts +24 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +20 -0
- package/dist/src/intl/uk.d.ts +80 -57
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +174 -149
- package/dist/src/intl/ur.d.ts +24 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +20 -0
- package/dist/src/intl/vi.d.ts +24 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +20 -0
- package/dist/src/intl/zh-CN.d.ts +24 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +20 -0
- package/dist/src/intl/zh-TW.d.ts +24 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +20 -0
- package/dist/src/local-first/Db.d.ts +52 -3
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +412 -137
- package/dist/src/local-first/Evolu.d.ts +336 -211
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +102 -15
- package/dist/src/local-first/Owner.d.ts +13 -30
- package/dist/src/local-first/Owner.d.ts.map +1 -1
- package/dist/src/local-first/Owner.js +13 -30
- package/dist/src/local-first/Protocol.d.ts +95 -17
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +119 -39
- package/dist/src/local-first/Query.d.ts +8 -15
- package/dist/src/local-first/Query.d.ts.map +1 -1
- package/dist/src/local-first/Schema.d.ts +345 -21
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +214 -17
- package/dist/src/local-first/Shared.d.ts +537 -22
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +1437 -234
- package/dist/src/local-first/Storage.d.ts +192 -14
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +82 -21
- package/dist/src/local-first/Timestamp.d.ts +392 -41
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +404 -82
- package/dist/src/local-first/index.d.ts +0 -1
- package/dist/src/local-first/index.d.ts.map +1 -1
- package/dist/src/local-first/index.js +0 -1
- package/package.json +1 -1
- package/src/Assert.test.ts +2 -5
- package/src/{Binary.test.ts → Bytes.test.ts} +286 -1
- package/src/{Binary.ts → Bytes.ts} +652 -21
- package/src/Config.test.ts +668 -0
- package/src/Config.ts +410 -0
- package/src/Console.ts +62 -7
- package/src/Crypto.ts +76 -4
- package/src/Eq.test.ts +2 -3
- package/src/Error.test.ts +76 -3
- package/src/Error.ts +71 -0
- package/src/Fs.test.ts +105 -0
- package/src/Fs.ts +488 -0
- package/src/Identicon.ts +2 -2
- package/src/LeakDetector.ts +22 -3
- package/src/LockManager.ts +8 -0
- package/src/Number.test.ts +82 -18
- package/src/Number.ts +76 -8
- package/src/Object.test.ts +139 -10
- package/src/Object.ts +49 -0
- package/src/Platform.ts +50 -8
- package/src/Random.ts +25 -2
- package/src/Resource.test.ts +837 -0
- package/src/Resource.ts +235 -15
- package/src/Schedule.test.ts +50 -12
- package/src/Schedule.ts +24 -14
- package/src/Sqlite.ts +138 -18
- package/src/Task.test.ts +189 -8
- package/src/Task.ts +56 -17
- package/src/Test.ts +9 -0
- package/src/Time.test.ts +82 -11
- package/src/Time.ts +246 -24
- package/src/Type.test.ts +3994 -1119
- package/src/Type.ts +7258 -3842
- package/src/Types.test.ts +4 -14
- package/src/WebSocket.ts +313 -40
- package/src/Worker.ts +90 -8
- package/src/index.ts +18 -7
- package/src/intl/_en.ts +70 -0
- package/src/intl/ar.ts +71 -0
- package/src/intl/bn.ts +70 -0
- package/src/intl/ca.ts +70 -0
- package/src/intl/cs.ts +70 -0
- package/src/intl/da.ts +70 -0
- package/src/intl/de.ts +70 -0
- package/src/intl/el.ts +70 -0
- package/src/intl/es.ts +70 -0
- package/src/intl/fa.ts +70 -0
- package/src/intl/fi.ts +70 -0
- package/src/intl/fil.ts +70 -0
- package/src/intl/fr.ts +70 -0
- package/src/intl/he.ts +70 -0
- package/src/intl/hi.ts +70 -0
- package/src/intl/hr.ts +70 -0
- package/src/intl/hu.ts +69 -0
- package/src/intl/id.ts +70 -0
- package/src/intl/intl.test.ts +819 -1
- package/src/intl/it.ts +70 -0
- package/src/intl/ja.ts +70 -0
- package/src/intl/ko.ts +68 -0
- package/src/intl/ml.ts +70 -0
- package/src/intl/mr.ts +70 -0
- package/src/intl/ms.ts +71 -0
- package/src/intl/nb.ts +69 -0
- package/src/intl/nl.ts +70 -0
- package/src/intl/pa.ts +70 -0
- package/src/intl/pl.ts +63 -0
- package/src/intl/pt-BR.ts +70 -0
- package/src/intl/pt.ts +71 -0
- package/src/intl/ro.ts +70 -0
- package/src/intl/sk.ts +71 -0
- package/src/intl/sl.ts +70 -0
- package/src/intl/sv.ts +70 -0
- package/src/intl/sw.ts +62 -0
- package/src/intl/ta.ts +70 -0
- package/src/intl/te.ts +70 -0
- package/src/intl/th.ts +68 -0
- package/src/intl/tr.ts +70 -0
- package/src/intl/uk.ts +228 -155
- package/src/intl/ur.ts +70 -0
- package/src/intl/vi.ts +70 -0
- package/src/intl/zh-CN.ts +68 -0
- package/src/intl/zh-TW.ts +68 -0
- package/src/local-first/Db.ts +644 -339
- package/src/local-first/Evolu.test.ts +686 -21
- package/src/local-first/Evolu.ts +450 -228
- package/src/local-first/Owner.ts +13 -30
- package/src/local-first/Protocol.test.ts +618 -11
- package/src/local-first/Protocol.ts +197 -73
- package/src/local-first/Query.ts +8 -15
- package/src/local-first/Schema.test.ts +143 -0
- package/src/local-first/Schema.ts +374 -24
- package/src/local-first/Shared.test.ts +7731 -559
- package/src/local-first/Shared.ts +2036 -267
- package/src/local-first/Storage.ts +219 -33
- package/src/local-first/Timestamp.test.ts +344 -70
- package/src/local-first/Timestamp.ts +435 -119
- package/src/local-first/index.ts +0 -1
- package/dist/src/Binary.d.ts +0 -254
- package/dist/src/Binary.d.ts.map +0 -1
- package/dist/src/local-first/Error.d.ts +0 -12
- package/dist/src/local-first/Error.d.ts.map +0 -1
- package/dist/src/local-first/Error.js +0 -6
- package/dist/src/local-first/LocalAuth.d.ts +0 -150
- package/dist/src/local-first/LocalAuth.d.ts.map +0 -1
- package/dist/src/local-first/LocalAuth.js +0 -179
- package/src/local-first/Error.ts +0 -17
- package/src/local-first/LocalAuth.ts +0 -457
|
@@ -9,7 +9,8 @@ import type { NonEmptyReadonlyArray } from "../Array.ts";
|
|
|
9
9
|
import { firstInArray, isNonEmptyArray } from "../Array.ts";
|
|
10
10
|
import { assert, assertNonNullable } from "../Assert.ts";
|
|
11
11
|
import type { Brand } from "../Brand.ts";
|
|
12
|
-
import { concatBytes } from "../
|
|
12
|
+
import { concatBytes } from "../Bytes.ts";
|
|
13
|
+
import type { DecryptWithXChaCha20Poly1305Error } from "../Crypto.ts";
|
|
13
14
|
import { decrement } from "../Number.ts";
|
|
14
15
|
import type { RandomDep } from "../Random.ts";
|
|
15
16
|
import { err, ok } from "../Result.ts";
|
|
@@ -38,6 +39,10 @@ import {
|
|
|
38
39
|
import type { Awaitable } from "../Types.ts";
|
|
39
40
|
import type { Owner, OwnerError, OwnerIdBytes } from "./Owner.ts";
|
|
40
41
|
import { OwnerId, OwnerWriteKey } from "./Owner.ts";
|
|
42
|
+
import type {
|
|
43
|
+
ProtocolInvalidDataError,
|
|
44
|
+
ProtocolTimestampMismatchError,
|
|
45
|
+
} from "./Protocol.ts";
|
|
41
46
|
import { systemColumnsWithId } from "./Schema.ts";
|
|
42
47
|
import {
|
|
43
48
|
createTimestamp,
|
|
@@ -46,6 +51,11 @@ import {
|
|
|
46
51
|
TimestampBytes,
|
|
47
52
|
} from "./Timestamp.ts";
|
|
48
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Configuration for {@link Storage}, such as quota checks.
|
|
56
|
+
*
|
|
57
|
+
* @group Core
|
|
58
|
+
*/
|
|
49
59
|
export interface StorageConfig {
|
|
50
60
|
/**
|
|
51
61
|
* Callback called before an attempt to write, to check if an {@link OwnerId}
|
|
@@ -104,12 +114,18 @@ export interface StorageConfig {
|
|
|
104
114
|
}
|
|
105
115
|
|
|
106
116
|
/**
|
|
107
|
-
* Evolu
|
|
117
|
+
* Replica storage used by Evolu's synchronization protocol.
|
|
118
|
+
*
|
|
119
|
+
* Protocol owns message framing, set reconciliation, and sync continuation.
|
|
120
|
+
* Storage owns batch acceptance, persistence, and the timestamp and fingerprint
|
|
121
|
+
* queries used by reconciliation. Protocol functions accept any implementation
|
|
122
|
+
* that satisfies this contract.
|
|
108
123
|
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
124
|
+
* {@link Storage.writeMessages} returns the {@link StorageWriteMessagesError}
|
|
125
|
+
* that made it store none of a batch. Implementations return expected write
|
|
126
|
+
* rejections without reporting them; the caller owns reporting. The client
|
|
127
|
+
* protocol forwards these errors unchanged, while the relay protocol maps them
|
|
128
|
+
* to wire error codes.
|
|
113
129
|
*
|
|
114
130
|
* The Storage API is synchronous because SQLite's synchronous API is the
|
|
115
131
|
* fastest way to use SQLite. Synchronous bindings (like better-sqlite3) call
|
|
@@ -119,6 +135,8 @@ export interface StorageConfig {
|
|
|
119
135
|
* The only exception is {@link Storage.writeMessages}, which is async to allow
|
|
120
136
|
* for async validation logic before writing to storage. The write operation
|
|
121
137
|
* itself remains synchronous.
|
|
138
|
+
*
|
|
139
|
+
* @group Core
|
|
122
140
|
*/
|
|
123
141
|
export interface Storage {
|
|
124
142
|
readonly getSize: (ownerId: OwnerIdBytes) => NonNegativeInt;
|
|
@@ -175,13 +193,16 @@ export interface Storage {
|
|
|
175
193
|
/**
|
|
176
194
|
* Write encrypted {@link CrdtMessage}s to storage.
|
|
177
195
|
*
|
|
196
|
+
* Stores none of the messages and returns the cause when the batch cannot be
|
|
197
|
+
* accepted.
|
|
198
|
+
*
|
|
178
199
|
* Must use a mutex per ownerId to ensure sequential processing and proper
|
|
179
200
|
* protocol logic handling during sync operations.
|
|
180
201
|
*/
|
|
181
202
|
readonly writeMessages: (
|
|
182
203
|
ownerIdBytes: OwnerIdBytes,
|
|
183
204
|
messages: NonEmptyReadonlyArray<EncryptedCrdtMessage>,
|
|
184
|
-
) => Task<void,
|
|
205
|
+
) => Task<void, StorageWriteMessagesError>;
|
|
185
206
|
|
|
186
207
|
/** Read encrypted {@link DbChange}s from storage. */
|
|
187
208
|
readonly readDbChange: (
|
|
@@ -193,30 +214,72 @@ export interface Storage {
|
|
|
193
214
|
readonly deleteOwner: (ownerId: OwnerIdBytes) => void;
|
|
194
215
|
}
|
|
195
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Dependency wrapper for {@link Storage}.
|
|
219
|
+
*
|
|
220
|
+
* @group Core
|
|
221
|
+
*/
|
|
196
222
|
export interface StorageDep {
|
|
197
223
|
readonly storage: Storage;
|
|
198
224
|
}
|
|
199
225
|
|
|
200
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Error when storage or billing quota is exceeded.
|
|
228
|
+
*
|
|
229
|
+
* @group Core
|
|
230
|
+
*/
|
|
201
231
|
export interface StorageQuotaError
|
|
202
232
|
extends OwnerError, Typed<"StorageQuotaError"> {}
|
|
203
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Expected reasons why {@link Storage.writeMessages} stored none of a batch.
|
|
236
|
+
*
|
|
237
|
+
* Each implementation returns the members that apply to it. The built-in relay
|
|
238
|
+
* storage currently stores opaque encrypted messages and rejects batches over
|
|
239
|
+
* quota. The built-in client storage decrypts and validates incoming messages
|
|
240
|
+
* before updating its clock and database tables. The contract permits quota
|
|
241
|
+
* checks on either side.
|
|
242
|
+
*
|
|
243
|
+
* @group Core
|
|
244
|
+
*/
|
|
245
|
+
export type StorageWriteMessagesError =
|
|
246
|
+
| DecryptWithXChaCha20Poly1305Error
|
|
247
|
+
| ProtocolInvalidDataError
|
|
248
|
+
| ProtocolTimestampMismatchError
|
|
249
|
+
| StorageQuotaError;
|
|
250
|
+
|
|
204
251
|
/**
|
|
205
252
|
* A cryptographic hash used for efficiently comparing collections of
|
|
206
253
|
* {@link TimestampBytes}es.
|
|
207
254
|
*
|
|
208
255
|
* It consists of the first {@link fingerprintSize} bytes of the SHA-256 hash of
|
|
209
256
|
* one or more timestamps.
|
|
257
|
+
*
|
|
258
|
+
* @group Ranges
|
|
210
259
|
*/
|
|
211
260
|
export type Fingerprint = Uint8Array & Brand<"Fingerprint">;
|
|
212
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Number of leading SHA-256 bytes that form a {@link Fingerprint}.
|
|
264
|
+
*
|
|
265
|
+
* @group Ranges
|
|
266
|
+
*/
|
|
213
267
|
export const fingerprintSize = /*#__PURE__*/ NonNegativeInt.orThrow(12);
|
|
214
268
|
|
|
215
|
-
/**
|
|
269
|
+
/**
|
|
270
|
+
* A fingerprint of an empty range.
|
|
271
|
+
*
|
|
272
|
+
* @group Ranges
|
|
273
|
+
*/
|
|
216
274
|
export const zeroFingerprint = /*#__PURE__*/ new Uint8Array(
|
|
217
275
|
fingerprintSize,
|
|
218
276
|
) as Fingerprint;
|
|
219
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Common shape of every {@link Range}.
|
|
280
|
+
*
|
|
281
|
+
* @group Ranges
|
|
282
|
+
*/
|
|
220
283
|
export interface BaseRange {
|
|
221
284
|
readonly upperBound: RangeUpperBound;
|
|
222
285
|
}
|
|
@@ -224,45 +287,96 @@ export interface BaseRange {
|
|
|
224
287
|
/**
|
|
225
288
|
* Union type for Range's upperBound: either a {@link TimestampBytes} or
|
|
226
289
|
* {@link InfiniteUpperBound}.
|
|
290
|
+
*
|
|
291
|
+
* @group Ranges
|
|
227
292
|
*/
|
|
228
293
|
export type RangeUpperBound = TimestampBytes | InfiniteUpperBound;
|
|
229
294
|
|
|
295
|
+
/**
|
|
296
|
+
* Sentinel {@link RangeUpperBound} for a range without an upper limit.
|
|
297
|
+
*
|
|
298
|
+
* @group Ranges
|
|
299
|
+
*/
|
|
230
300
|
export const InfiniteUpperBound = /*#__PURE__*/ Symbol(
|
|
231
301
|
"evolu.local-first.Storage.InfiniteUpperBound",
|
|
232
302
|
);
|
|
303
|
+
/**
|
|
304
|
+
* Type of the {@link InfiniteUpperBound} sentinel.
|
|
305
|
+
*
|
|
306
|
+
* @group Ranges
|
|
307
|
+
*/
|
|
233
308
|
export type InfiniteUpperBound = typeof InfiniteUpperBound;
|
|
234
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Numeric tags discriminating {@link Range} variants.
|
|
312
|
+
*
|
|
313
|
+
* @group Ranges
|
|
314
|
+
*/
|
|
235
315
|
export const RangeType = {
|
|
236
316
|
Fingerprint: 1,
|
|
237
317
|
Skip: 0,
|
|
238
318
|
Timestamps: 2,
|
|
239
319
|
} as const;
|
|
240
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Numeric tag of one {@link Range} variant.
|
|
323
|
+
*
|
|
324
|
+
* @group Ranges
|
|
325
|
+
*/
|
|
241
326
|
export type RangeType = (typeof RangeType)[keyof typeof RangeType];
|
|
242
327
|
|
|
328
|
+
/**
|
|
329
|
+
* Range with nothing to reconcile.
|
|
330
|
+
*
|
|
331
|
+
* @group Ranges
|
|
332
|
+
*/
|
|
243
333
|
export interface SkipRange extends BaseRange {
|
|
244
334
|
readonly type: typeof RangeType.Skip;
|
|
245
335
|
}
|
|
246
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Range summarized by a {@link Fingerprint} for comparison.
|
|
339
|
+
*
|
|
340
|
+
* @group Ranges
|
|
341
|
+
*/
|
|
247
342
|
export interface FingerprintRange extends BaseRange {
|
|
248
343
|
readonly type: typeof RangeType.Fingerprint;
|
|
249
344
|
readonly fingerprint: Fingerprint;
|
|
250
345
|
}
|
|
251
346
|
|
|
347
|
+
/**
|
|
348
|
+
* Range listing its {@link TimestampBytes} explicitly.
|
|
349
|
+
*
|
|
350
|
+
* @group Ranges
|
|
351
|
+
*/
|
|
252
352
|
export interface TimestampsRange extends BaseRange {
|
|
253
353
|
readonly type: typeof RangeType.Timestamps;
|
|
254
354
|
readonly timestamps: ReadonlyArray<TimestampBytes>;
|
|
255
355
|
}
|
|
256
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Range exchanged during sync: {@link SkipRange}, {@link FingerprintRange}, or
|
|
359
|
+
* {@link TimestampsRange}.
|
|
360
|
+
*
|
|
361
|
+
* @group Ranges
|
|
362
|
+
*/
|
|
257
363
|
export type Range = SkipRange | FingerprintRange | TimestampsRange;
|
|
258
364
|
|
|
259
|
-
/**
|
|
365
|
+
/**
|
|
366
|
+
* An encrypted {@link CrdtMessage}.
|
|
367
|
+
*
|
|
368
|
+
* @group Messages
|
|
369
|
+
*/
|
|
260
370
|
export interface EncryptedCrdtMessage {
|
|
261
371
|
readonly timestamp: Timestamp;
|
|
262
372
|
readonly change: EncryptedDbChange;
|
|
263
373
|
}
|
|
264
374
|
|
|
265
|
-
/**
|
|
375
|
+
/**
|
|
376
|
+
* Encrypted DbChange
|
|
377
|
+
*
|
|
378
|
+
* @group Messages
|
|
379
|
+
*/
|
|
266
380
|
export type EncryptedDbChange = Uint8Array & Brand<"EncryptedDbChange">;
|
|
267
381
|
|
|
268
382
|
/**
|
|
@@ -271,13 +385,19 @@ export type EncryptedDbChange = Uint8Array & Brand<"EncryptedDbChange">;
|
|
|
271
385
|
* Used in Evolu's sync protocol to replicate data changes across devices. Evolu
|
|
272
386
|
* operates as a durable queue, providing exactly-once delivery guarantees for
|
|
273
387
|
* reliable synchronization across application restarts and network failures.
|
|
388
|
+
*
|
|
389
|
+
* @group Messages
|
|
274
390
|
*/
|
|
275
391
|
export interface CrdtMessage {
|
|
276
392
|
readonly timestamp: Timestamp;
|
|
277
393
|
readonly change: DbChange;
|
|
278
394
|
}
|
|
279
395
|
|
|
280
|
-
/**
|
|
396
|
+
/**
|
|
397
|
+
* Test helper for creating a simple {@link CrdtMessage}.
|
|
398
|
+
*
|
|
399
|
+
* @group Testing
|
|
400
|
+
*/
|
|
281
401
|
export const testCreateCrdtMessage = (
|
|
282
402
|
id: Id,
|
|
283
403
|
millis: number,
|
|
@@ -296,9 +416,19 @@ export const testCreateCrdtMessage = (
|
|
|
296
416
|
}),
|
|
297
417
|
});
|
|
298
418
|
|
|
419
|
+
/**
|
|
420
|
+
* Column values of a {@link DbChange}, keyed by column name.
|
|
421
|
+
*
|
|
422
|
+
* @group Messages
|
|
423
|
+
*/
|
|
299
424
|
export const DbChangeValues = /*#__PURE__*/ record(String, SqliteValue);
|
|
300
425
|
export type DbChangeValues = typeof DbChangeValues.Output;
|
|
301
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Column values that contain no reserved system columns.
|
|
429
|
+
*
|
|
430
|
+
* @group Messages
|
|
431
|
+
*/
|
|
302
432
|
export const ValidDbChangeValues = /*#__PURE__*/ brand(
|
|
303
433
|
"ValidDbChangeValues",
|
|
304
434
|
DbChangeValues,
|
|
@@ -318,6 +448,11 @@ export const ValidDbChangeValues = /*#__PURE__*/ brand(
|
|
|
318
448
|
);
|
|
319
449
|
export type ValidDbChangeValues = typeof ValidDbChangeValues.Output;
|
|
320
450
|
|
|
451
|
+
/**
|
|
452
|
+
* Error produced when {@link DbChangeValues} contain reserved system columns.
|
|
453
|
+
*
|
|
454
|
+
* @group Messages
|
|
455
|
+
*/
|
|
321
456
|
export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues"> {
|
|
322
457
|
readonly value: DbChangeValues;
|
|
323
458
|
readonly invalidColumns: ReadonlyArray<string>;
|
|
@@ -326,6 +461,8 @@ export interface ValidDbChangeValuesError extends TypeError<"ValidDbChangeValues
|
|
|
326
461
|
/**
|
|
327
462
|
* A DbChange is a change to a table row. Together with a unique
|
|
328
463
|
* {@link Timestamp}, it forms a {@link CrdtMessage}.
|
|
464
|
+
*
|
|
465
|
+
* @group Messages
|
|
329
466
|
*/
|
|
330
467
|
export const DbChange: ObjectType<{
|
|
331
468
|
readonly table: typeof String;
|
|
@@ -371,17 +508,24 @@ export interface DbChange extends InferType<typeof DbChange> {}
|
|
|
371
508
|
* each other, if necessary. One relay should handle hundreds of thousands of
|
|
372
509
|
* users, and when it goes down, nothing happens, because it will be
|
|
373
510
|
* synchronized later.
|
|
511
|
+
*
|
|
512
|
+
* @group SQLite
|
|
374
513
|
*/
|
|
375
514
|
export interface BaseSqliteStorage extends Omit<
|
|
376
515
|
Storage,
|
|
377
516
|
"validateWriteKey" | "setWriteKey" | "writeMessages" | "readDbChange"
|
|
378
517
|
> {
|
|
379
|
-
/**
|
|
518
|
+
/**
|
|
519
|
+
* Inserts a timestamp for an owner into the skiplist-based storage.
|
|
520
|
+
*
|
|
521
|
+
* Returns whether the timestamp was new. An existing timestamp is left
|
|
522
|
+
* unchanged.
|
|
523
|
+
*/
|
|
380
524
|
readonly insertTimestamp: (
|
|
381
525
|
ownerId: OwnerIdBytes,
|
|
382
526
|
timestamp: TimestampBytes,
|
|
383
527
|
strategy: StorageInsertTimestampStrategy,
|
|
384
|
-
) =>
|
|
528
|
+
) => boolean;
|
|
385
529
|
|
|
386
530
|
/**
|
|
387
531
|
* Efficiently checks which timestamps already exist in the database using a
|
|
@@ -393,10 +537,20 @@ export interface BaseSqliteStorage extends Omit<
|
|
|
393
537
|
) => ReadonlyArray<TimestampBytes>;
|
|
394
538
|
}
|
|
395
539
|
|
|
540
|
+
/**
|
|
541
|
+
* Dependency wrapper for {@link BaseSqliteStorage}.
|
|
542
|
+
*
|
|
543
|
+
* @group SQLite
|
|
544
|
+
*/
|
|
396
545
|
export interface BaseSqliteStorageDep {
|
|
397
546
|
readonly baseSqliteStorage: BaseSqliteStorage;
|
|
398
547
|
}
|
|
399
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Dependencies required by {@link createBaseSqliteStorage}.
|
|
551
|
+
*
|
|
552
|
+
* @group SQLite
|
|
553
|
+
*/
|
|
400
554
|
export type SqliteStorageDeps = RandomDep & SqliteDep;
|
|
401
555
|
|
|
402
556
|
/**
|
|
@@ -411,6 +565,8 @@ export type SqliteStorageDeps = RandomDep & SqliteDep;
|
|
|
411
565
|
* Cloudflare Workers with Durable Objects, and other platforms where memory
|
|
412
566
|
* doesn't persist between requests. While not extensively tested in all these
|
|
413
567
|
* environments yet, the stateless design should work well across them.
|
|
568
|
+
*
|
|
569
|
+
* @group SQLite
|
|
414
570
|
*/
|
|
415
571
|
export const createBaseSqliteStorage = (
|
|
416
572
|
deps: SqliteStorageDeps,
|
|
@@ -421,7 +577,7 @@ export const createBaseSqliteStorage = (
|
|
|
421
577
|
strategy: StorageInsertTimestampStrategy,
|
|
422
578
|
) => {
|
|
423
579
|
const level = randomSkiplistLevel(deps);
|
|
424
|
-
insertTimestamp(deps)(ownerId, timestamp, level, strategy);
|
|
580
|
+
return insertTimestamp(deps)(ownerId, timestamp, level, strategy);
|
|
425
581
|
},
|
|
426
582
|
|
|
427
583
|
getExistingTimestamps: (ownerIdBytes, timestampsBytes) => {
|
|
@@ -509,6 +665,11 @@ const assertBeginEnd = (begin: NonNegativeInt, end: NonNegativeInt) => {
|
|
|
509
665
|
assert(begin <= end, "invalid begin or end");
|
|
510
666
|
};
|
|
511
667
|
|
|
668
|
+
/**
|
|
669
|
+
* Creates the SQLite tables used by {@link BaseSqliteStorage}.
|
|
670
|
+
*
|
|
671
|
+
* @group SQLite
|
|
672
|
+
*/
|
|
512
673
|
export const createBaseSqliteStorageTables = (deps: SqliteDep): void => {
|
|
513
674
|
for (const query of [
|
|
514
675
|
/**
|
|
@@ -575,6 +736,11 @@ export const createBaseSqliteStorageTables = (deps: SqliteDep): void => {
|
|
|
575
736
|
}
|
|
576
737
|
};
|
|
577
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Position at which a timestamp is inserted relative to existing timestamps.
|
|
741
|
+
*
|
|
742
|
+
* @group SQLite
|
|
743
|
+
*/
|
|
578
744
|
export type StorageInsertTimestampStrategy = "append" | "prepend" | "insert";
|
|
579
745
|
|
|
580
746
|
/**
|
|
@@ -582,6 +748,8 @@ export type StorageInsertTimestampStrategy = "append" | "prepend" | "insert";
|
|
|
582
748
|
* relative to the current first and last timestamps.
|
|
583
749
|
*
|
|
584
750
|
* Returns a tuple with the strategy and updated timestamp bounds.
|
|
751
|
+
*
|
|
752
|
+
* @group SQLite
|
|
585
753
|
*/
|
|
586
754
|
export const getTimestampInsertStrategy = (
|
|
587
755
|
timestamp: TimestampBytes,
|
|
@@ -624,9 +792,9 @@ export const getTimestampInsertStrategy = (
|
|
|
624
792
|
* key instead of repeating the same correlated range lookup for every column.
|
|
625
793
|
*
|
|
626
794
|
* Inserts are idempotent to support direct calls and message replay. `on
|
|
627
|
-
* conflict do nothing` makes a duplicate insertion a no-op
|
|
628
|
-
*
|
|
629
|
-
*
|
|
795
|
+
* conflict do nothing` makes a duplicate insertion a no-op that reports the
|
|
796
|
+
* timestamp as not new, so the follow-up statements update metadata only for a
|
|
797
|
+
* new timestamp.
|
|
630
798
|
*/
|
|
631
799
|
const insertTimestamp =
|
|
632
800
|
(deps: SqliteDep) =>
|
|
@@ -635,12 +803,15 @@ const insertTimestamp =
|
|
|
635
803
|
timestamp: TimestampBytes,
|
|
636
804
|
level: PositiveInt,
|
|
637
805
|
strategy: StorageInsertTimestampStrategy,
|
|
638
|
-
):
|
|
806
|
+
): boolean => {
|
|
639
807
|
const [h1, h2] = fingerprintToSqliteFingerprint(
|
|
640
808
|
timestampBytesToFingerprint(timestamp),
|
|
641
809
|
);
|
|
642
810
|
|
|
643
|
-
let queries:
|
|
811
|
+
let queries: [
|
|
812
|
+
insert: ReturnType<typeof sql.prepared>,
|
|
813
|
+
...updates: Array<ReturnType<typeof sql.prepared>>,
|
|
814
|
+
];
|
|
644
815
|
|
|
645
816
|
switch (strategy) {
|
|
646
817
|
case "append":
|
|
@@ -816,10 +987,7 @@ const insertTimestamp =
|
|
|
816
987
|
h2 = u.h2,
|
|
817
988
|
c = c + 1
|
|
818
989
|
from u
|
|
819
|
-
where
|
|
820
|
-
changes() > 0
|
|
821
|
-
and ownerId = ${ownerId}
|
|
822
|
-
and evolu_timestamp.t = u.t;
|
|
990
|
+
where ownerId = ${ownerId} and evolu_timestamp.t = u.t;
|
|
823
991
|
`,
|
|
824
992
|
];
|
|
825
993
|
break;
|
|
@@ -886,10 +1054,7 @@ const insertTimestamp =
|
|
|
886
1054
|
h2 = u.h2,
|
|
887
1055
|
c = c + 1
|
|
888
1056
|
from u
|
|
889
|
-
where
|
|
890
|
-
changes() > 0
|
|
891
|
-
and ownerId = ${ownerId}
|
|
892
|
-
and evolu_timestamp.t = u.t;
|
|
1057
|
+
where ownerId = ${ownerId} and evolu_timestamp.t = u.t;
|
|
893
1058
|
`,
|
|
894
1059
|
]
|
|
895
1060
|
: [
|
|
@@ -1072,17 +1237,25 @@ const insertTimestamp =
|
|
|
1072
1237
|
h2 = uh2,
|
|
1073
1238
|
c = uc
|
|
1074
1239
|
from u
|
|
1075
|
-
where
|
|
1240
|
+
where ownerId = ${ownerId} and t = ut;
|
|
1076
1241
|
`,
|
|
1077
1242
|
];
|
|
1078
1243
|
break;
|
|
1079
1244
|
}
|
|
1080
1245
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1246
|
+
// The insert uses "on conflict do nothing". An existing timestamp changes
|
|
1247
|
+
// nothing, and the metadata updates are skipped.
|
|
1248
|
+
const [insert, ...updates] = queries;
|
|
1249
|
+
if (deps.sqlite.exec(insert).changes === 0) return false;
|
|
1250
|
+
for (const update of updates) deps.sqlite.exec(update);
|
|
1251
|
+
return true;
|
|
1084
1252
|
};
|
|
1085
1253
|
|
|
1254
|
+
/**
|
|
1255
|
+
* Computes the {@link Fingerprint} of a single timestamp.
|
|
1256
|
+
*
|
|
1257
|
+
* @group Ranges
|
|
1258
|
+
*/
|
|
1086
1259
|
export const timestampBytesToFingerprint = (
|
|
1087
1260
|
timestamp: TimestampBytes,
|
|
1088
1261
|
): Fingerprint => {
|
|
@@ -1093,6 +1266,8 @@ export const timestampBytesToFingerprint = (
|
|
|
1093
1266
|
/**
|
|
1094
1267
|
* Computes a brute-force {@link Fingerprint} from {@link TimestampBytes} values
|
|
1095
1268
|
* for tests and benchmarks.
|
|
1269
|
+
*
|
|
1270
|
+
* @group Testing
|
|
1096
1271
|
*/
|
|
1097
1272
|
export const testFingerprintTimestamps = (
|
|
1098
1273
|
timestamps: ReadonlyArray<TimestampBytes>,
|
|
@@ -1492,6 +1667,11 @@ const fingerprintRanges =
|
|
|
1492
1667
|
// XOR in SQLite
|
|
1493
1668
|
const x = (a: string, b: string) => sql.raw(`(${a} | ${b}) - (${a} & ${b})`);
|
|
1494
1669
|
|
|
1670
|
+
/**
|
|
1671
|
+
* Reads the timestamp at a position within an owner's ordered timestamps.
|
|
1672
|
+
*
|
|
1673
|
+
* @group SQLite
|
|
1674
|
+
*/
|
|
1495
1675
|
export const getTimestampByIndex =
|
|
1496
1676
|
(deps: SqliteDep) =>
|
|
1497
1677
|
(ownerId: OwnerIdBytes, index: NonNegativeInt): TimestampBytes => {
|
|
@@ -1572,7 +1752,11 @@ export const getTimestampByIndex =
|
|
|
1572
1752
|
return result.rows[0].pt;
|
|
1573
1753
|
};
|
|
1574
1754
|
|
|
1575
|
-
/**
|
|
1755
|
+
/**
|
|
1756
|
+
* Reads owner usage from SQLite and returns default bounds when absent.
|
|
1757
|
+
*
|
|
1758
|
+
* @group SQLite
|
|
1759
|
+
*/
|
|
1576
1760
|
export const readOwnerUsageOrDefault =
|
|
1577
1761
|
(deps: SqliteDep) =>
|
|
1578
1762
|
(
|
|
@@ -1617,6 +1801,8 @@ export const readOwnerUsageOrDefault =
|
|
|
1617
1801
|
*
|
|
1618
1802
|
* Used by both relay and client to maintain firstTimestamp/lastTimestamp after
|
|
1619
1803
|
* processing messages.
|
|
1804
|
+
*
|
|
1805
|
+
* @group SQLite
|
|
1620
1806
|
*/
|
|
1621
1807
|
export const updateOwnerUsage =
|
|
1622
1808
|
(deps: SqliteDep) =>
|