@evolu/common 8.17.0 → 8.19.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 +31 -14
- package/dist/src/Bytes.d.ts.map +1 -1
- package/dist/src/Bytes.js +48 -8
- package/dist/src/Error.d.ts.map +1 -1
- package/dist/src/Error.js +5 -1
- package/dist/src/Polyfills.d.ts.map +1 -1
- package/dist/src/Polyfills.js +3 -2
- package/dist/src/Sqlite.d.ts +1 -1
- package/dist/src/Task.d.ts +4 -3
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +4 -3
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/local-first/Db.d.ts +32 -3
- package/dist/src/local-first/Db.d.ts.map +1 -1
- package/dist/src/local-first/Db.js +30 -9
- package/dist/src/local-first/Evolu.d.ts +21 -6
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +5 -1
- package/dist/src/local-first/Protocol.d.ts +215 -93
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +799 -489
- package/dist/src/local-first/Relay.d.ts +9 -1
- package/dist/src/local-first/Relay.d.ts.map +1 -1
- package/dist/src/local-first/Relay.js +41 -36
- package/dist/src/local-first/Shared.d.ts +79 -53
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +67 -42
- package/dist/src/local-first/Storage.d.ts +80 -18
- package/dist/src/local-first/Storage.d.ts.map +1 -1
- package/dist/src/local-first/Storage.js +20 -4
- package/package.json +1 -1
- package/src/Bytes.test.ts +212 -0
- package/src/Bytes.ts +67 -17
- package/src/Error.ts +5 -1
- package/src/Polyfills.ts +3 -2
- package/src/Sqlite.ts +1 -1
- package/src/Task.ts +4 -3
- package/src/index.ts +4 -1
- package/src/local-first/Db.ts +76 -17
- package/src/local-first/Evolu.test.ts +43 -12
- package/src/local-first/Evolu.ts +34 -7
- package/src/local-first/Protocol.test.ts +1541 -44
- package/src/local-first/Protocol.ts +1070 -605
- package/src/local-first/Relay.ts +80 -69
- package/src/local-first/Shared.test.ts +37 -1
- package/src/local-first/Shared.ts +124 -73
- package/src/local-first/Storage.ts +100 -27
|
@@ -75,7 +75,7 @@ import { err, ok } from "../Result.ts";
|
|
|
75
75
|
import { SqliteBoolean } from "../Sqlite.ts";
|
|
76
76
|
import { explicitAbortReason, testCreateDeps, testCreateRun } from "../Task.ts";
|
|
77
77
|
import { testCreateId } from "../Test.ts";
|
|
78
|
-
import { Millis } from "../Time.ts";
|
|
78
|
+
import { maxMillis, Millis } from "../Time.ts";
|
|
79
79
|
import {
|
|
80
80
|
assertType,
|
|
81
81
|
createIdFromString,
|
|
@@ -90,8 +90,14 @@ import {
|
|
|
90
90
|
Uint8Array as Uint8ArrayType,
|
|
91
91
|
} from "../Type.ts";
|
|
92
92
|
import type { ExtractTyped } from "../Type.ts";
|
|
93
|
-
import { DbChange } from "./Storage.ts";
|
|
94
|
-
import {
|
|
93
|
+
import { DbChange, RangeType } from "./Storage.ts";
|
|
94
|
+
import {
|
|
95
|
+
createTimestamp,
|
|
96
|
+
maxCounter,
|
|
97
|
+
maxNodeId,
|
|
98
|
+
type NodeId,
|
|
99
|
+
timestampToTimestampBytes,
|
|
100
|
+
} from "./Timestamp.ts";
|
|
95
101
|
import {
|
|
96
102
|
testCreateBroadcastChannel,
|
|
97
103
|
testCreateMessageChannel,
|
|
@@ -3144,13 +3150,25 @@ describe("Evolu", () => {
|
|
|
3144
3150
|
}),
|
|
3145
3151
|
};
|
|
3146
3152
|
|
|
3147
|
-
|
|
3148
|
-
|
|
3153
|
+
const change = encodeAndEncryptDbChange(deps)(
|
|
3154
|
+
message,
|
|
3155
|
+
testAppOwner.encryptionKey,
|
|
3156
|
+
);
|
|
3157
|
+
|
|
3158
|
+
// A request leaves out a change that does not fit and asks for another
|
|
3159
|
+
// round instead, so it must be longer than the change. A broadcast
|
|
3160
|
+
// throws when the change does not fit.
|
|
3161
|
+
assertTrue(
|
|
3162
|
+
createProtocolMessageFromCrdtMessages(deps)(testAppOwner, [message])
|
|
3163
|
+
.length > change.length,
|
|
3164
|
+
);
|
|
3149
3165
|
createProtocolBroadcastMessagesFromCrdtMessages(deps)(testAppOwner, [
|
|
3150
3166
|
message,
|
|
3151
3167
|
]);
|
|
3152
3168
|
|
|
3153
|
-
// A relay's response can pair it with the largest ranges section
|
|
3169
|
+
// A relay's response can pair it with the largest ranges section: a
|
|
3170
|
+
// Skip range and a Timestamps range listing nearly 100,000 bytes, with
|
|
3171
|
+
// room left to close the frame.
|
|
3154
3172
|
const rangesMaxSize = ProtocolMessageRangesMaxSize.orThrow(100_000);
|
|
3155
3173
|
const timestamps = createTimestampsBuffer();
|
|
3156
3174
|
for (let index = 1; timestamps.getLength() < 99_900; index++) {
|
|
@@ -3167,12 +3185,25 @@ describe("Evolu", () => {
|
|
|
3167
3185
|
rangesMaxSize,
|
|
3168
3186
|
});
|
|
3169
3187
|
assertTrue(
|
|
3170
|
-
response.
|
|
3171
|
-
timestamp: message.timestamp,
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3188
|
+
response.tryWrite(() => {
|
|
3189
|
+
response.addMessage({ timestamp: message.timestamp, change });
|
|
3190
|
+
response.addRange({
|
|
3191
|
+
type: RangeType.Skip,
|
|
3192
|
+
upperBound: timestampToTimestampBytes(
|
|
3193
|
+
createTimestamp({ millis: Millis.orThrow(2 ** 47) }),
|
|
3194
|
+
),
|
|
3195
|
+
});
|
|
3196
|
+
response.addRange({
|
|
3197
|
+
type: RangeType.Timestamps,
|
|
3198
|
+
upperBound: timestampToTimestampBytes(
|
|
3199
|
+
createTimestamp({
|
|
3200
|
+
millis: maxMillis,
|
|
3201
|
+
counter: maxCounter,
|
|
3202
|
+
nodeId: maxNodeId,
|
|
3203
|
+
}),
|
|
3204
|
+
),
|
|
3205
|
+
timestamps,
|
|
3206
|
+
});
|
|
3176
3207
|
}),
|
|
3177
3208
|
);
|
|
3178
3209
|
});
|
package/src/local-first/Evolu.ts
CHANGED
|
@@ -64,7 +64,11 @@ import type {
|
|
|
64
64
|
CreateBroadcastChannelDep,
|
|
65
65
|
CreateMessageChannelDep,
|
|
66
66
|
} from "../Worker.ts";
|
|
67
|
-
import type {
|
|
67
|
+
import type {
|
|
68
|
+
CreateDbWorkerDep,
|
|
69
|
+
DatabaseHeldError,
|
|
70
|
+
UnsupportedDbVersionError,
|
|
71
|
+
} from "./Db.ts";
|
|
68
72
|
import type {
|
|
69
73
|
AppOwner,
|
|
70
74
|
Owner,
|
|
@@ -76,6 +80,7 @@ import type {
|
|
|
76
80
|
import { createOwnerWebSocketTransport } from "./Owner.ts";
|
|
77
81
|
import {
|
|
78
82
|
encodeDbChange,
|
|
83
|
+
type ProtocolChangeTooLargeError,
|
|
79
84
|
type ProtocolError,
|
|
80
85
|
type ProtocolInvalidDataError,
|
|
81
86
|
type ProtocolQuotaError,
|
|
@@ -946,6 +951,9 @@ export type DevicePersistence = "Persisted" | "NotPersisted" | "Unknown";
|
|
|
946
951
|
*
|
|
947
952
|
* - {@link UnsupportedDbVersionError} blocks the app: the local data needs a newer
|
|
948
953
|
* version of it. Ask the user to update the app or close all its tabs.
|
|
954
|
+
* - {@link DatabaseHeldError} blocks the app: another context keeps holding the
|
|
955
|
+
* local data, as a browser can until it restarts. Ask the user to close the
|
|
956
|
+
* app's other tabs or to restart the browser, then to reload the app.
|
|
949
957
|
* - {@link OtherBuildRunningError} blocks the app while it lasts: another version
|
|
950
958
|
* of the app holds the local data. Ask the user to close the app's other
|
|
951
959
|
* tabs. It clears when the wait ends.
|
|
@@ -960,7 +968,8 @@ export type DevicePersistence = "Persisted" | "NotPersisted" | "Unknown";
|
|
|
960
968
|
* request, or sent a frame that could not be decoded, the route shows a
|
|
961
969
|
* {@link ProtocolError}. A {@link ProtocolQuotaError} needs more relay quota,
|
|
962
970
|
* then {@link Evolu.requestSync}, and a {@link ProtocolVersionError} needs an app
|
|
963
|
-
* or relay update.
|
|
971
|
+
* or relay update. A route shows an {@link UnknownError} failure when this
|
|
972
|
+
* device could not store received changes, for example on a full disk.
|
|
964
973
|
*
|
|
965
974
|
* A received change that was not created with the owner's encryption key, was
|
|
966
975
|
* altered afterwards, or cannot be decoded by this app version is skipped,
|
|
@@ -985,10 +994,21 @@ export type DevicePersistence = "Persisted" | "NotPersisted" | "Unknown";
|
|
|
985
994
|
* {@link DecryptWithXChaCha20Poly1305Error} and your code creates or shares the
|
|
986
995
|
* owner, check the owner's keys.
|
|
987
996
|
*
|
|
997
|
+
* A {@link ProtocolChangeTooLargeError} skip is a change no protocol message can
|
|
998
|
+
* hold: one this device saved before {@link maxMutationSize} existed, or a
|
|
999
|
+
* crafted one received from a relay. Every route through a relay that lacks it
|
|
1000
|
+
* shows it after each sync, and everything else still syncs, but changes
|
|
1001
|
+
* received from one relay reach the others only after a reconnect or
|
|
1002
|
+
* {@link Evolu.requestSync}. The change is not recovered, and replacing relays
|
|
1003
|
+
* does not help.
|
|
1004
|
+
*
|
|
988
1005
|
* @group Core
|
|
989
1006
|
*/
|
|
990
1007
|
export type EvoluError =
|
|
991
|
-
|
|
1008
|
+
| DatabaseHeldError
|
|
1009
|
+
| OtherBuildRunningError
|
|
1010
|
+
| UnknownError
|
|
1011
|
+
| UnsupportedDbVersionError;
|
|
992
1012
|
|
|
993
1013
|
/**
|
|
994
1014
|
* The largest {@link Mutation}, in bytes.
|
|
@@ -1061,9 +1081,10 @@ export interface EvoluErrorDep {
|
|
|
1061
1081
|
* refusal to each tab once, including tabs that connect later, and starts no
|
|
1062
1082
|
* replacement database workers. After all instances release that tenant and
|
|
1063
1083
|
* it is disposed when idle, creating another instance retries startup and may
|
|
1064
|
-
* send the refusal again. On the web, a
|
|
1065
|
-
*
|
|
1066
|
-
* outside any query-loading boundary, so pending queries do
|
|
1084
|
+
* send the refusal again. On the web, a tab refused with
|
|
1085
|
+
* {@link UnsupportedDbVersionError} first reloads once instead. Show that
|
|
1086
|
+
* blocking message outside any query-loading boundary, so pending queries do
|
|
1087
|
+
* not hide it.
|
|
1067
1088
|
*
|
|
1068
1089
|
* An {@link UnknownError} leaves Evolu in an unknown state, so the app can
|
|
1069
1090
|
* only ask the user to close the tab. A failed shared worker closes itself,
|
|
@@ -1088,6 +1109,8 @@ export interface EvoluErrorDep {
|
|
|
1088
1109
|
* switch (error.type) {
|
|
1089
1110
|
* case "UnsupportedDbVersionError":
|
|
1090
1111
|
* return "Your data requires a newer version of this app. Please update it.";
|
|
1112
|
+
* case "DatabaseHeldError":
|
|
1113
|
+
* return "Your data is still in use elsewhere. Close this app's other tabs or restart the browser, then reload this page.";
|
|
1091
1114
|
* case "OtherBuildRunningError":
|
|
1092
1115
|
* return "This app is open in another tab with a different version. Close that tab to continue.";
|
|
1093
1116
|
* case "UnknownError":
|
|
@@ -1708,7 +1731,11 @@ export const createEvolu =
|
|
|
1708
1731
|
// gets it already batched and cannot reach the call site; quarantine,
|
|
1709
1732
|
// which holds changes stored for sync; and skipping it in sync, which
|
|
1710
1733
|
// keeps range fingerprints disagreeing. Received changes are not
|
|
1711
|
-
// checked
|
|
1734
|
+
// checked. Changes saved before this limit can be too large for any
|
|
1735
|
+
// protocol message, as can crafted changes on a relay. Their
|
|
1736
|
+
// fingerprints disagree either way, so sync skips them, which ends
|
|
1737
|
+
// every round instead of retrying forever. A client reports such a
|
|
1738
|
+
// change as its route's skippedError, and a relay logs it. They are not
|
|
1712
1739
|
// recovered, which needs a history-aware design.
|
|
1713
1740
|
//
|
|
1714
1741
|
// Local-only tables never sync, so they have no limit.
|