@vrtmrz/livesync-commonlib 0.1.44 → 0.2.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 +37 -4
- package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
- package/dist/API/DirectFileManipulatorV2.js +3 -0
- package/dist/common/configForDoc.d.ts +1 -0
- package/dist/common/configForDoc.js +11 -1
- package/dist/common/models/db.definition.d.ts +1 -0
- package/dist/common/models/setting.const.preferred.js +1 -3
- package/dist/managers/EntryManager/EntryManagerImpls.js +3 -2
- package/dist/pouchdb/LiveSyncDBFunctions.js +4 -0
- package/dist/pouchdb/StreamingFetch.d.ts +5 -1
- package/dist/pouchdb/StreamingFetch.js +529 -65
- package/dist/replication/RemoteResource.d.ts +23 -2
- package/dist/replication/RemoteResource.js +6 -2
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +8 -0
- package/dist/replication/couchdb/LiveSyncReplicator.js +132 -1
- package/dist/replication/couchdb/fastForward.d.ts +19 -0
- package/dist/replication/couchdb/fastForward.js +119 -0
- package/dist/replication/index.d.ts +3 -1
- package/dist/replication/joinedDevices.d.ts +25 -0
- package/dist/replication/joinedDevices.js +123 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +2 -1
- package/dist/replication/journal/JournalSyncCore.js +11 -1
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +4 -0
- package/dist/replication/journal/LiveSyncJournalReplicator.js +56 -1
- package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +3 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -1
- package/dist/replication/journal/objectstore/MinioStorageAdapter.js +27 -1
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +1 -1
- package/dist/replication/trystero/P2PRoomSessionOwner.js +27 -14
- package/dist/serviceModules/Rebuilder.js +41 -15
- package/dist/services/BrowserServices.d.ts +4 -2
- package/dist/services/BrowserServices.js +2 -5
- package/dist/services/HeadlessServices.d.ts +3 -0
- package/dist/services/HeadlessServices.js +2 -5
- package/dist/services/ServiceHub.d.ts +4 -4
- package/dist/services/base/IService.d.ts +10 -18
- package/dist/services/base/ReplicatorService.remoteResourceResolver.js +1 -1
- package/dist/services/base/SettingService.d.ts +8 -18
- package/dist/services/base/SettingService.js +28 -46
- package/dist/services/implements/base/SvelteDialog.d.ts +2 -2
- package/dist/services/implements/base/UIService.d.ts +2 -0
- package/dist/services/implements/base/UIService.js +5 -0
- package/dist/services/implements/injectable/InjectableServiceHub.d.ts +4 -4
- package/dist/services/implements/injectable/InjectableServices.d.ts +2 -2
- package/dist/services/implements/injectable/InjectableSettingService.d.ts +0 -3
- package/dist/services/implements/injectable/InjectableSettingService.js +0 -11
- package/dist/string_and_binary/chunks.js +7 -5
- package/dist/uiCommands.d.ts +26 -0
- package/dist/uiCommands.js +57 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/development.md +15 -3
- package/docs/proven-in-use.md +38 -4
- package/docs/releasing.md +20 -13
- package/docs/settings-lifecycle.md +37 -5
- package/package.json +12 -17
- package/dist/services/base/ConfigService.d.ts +0 -7
- package/dist/services/base/ConfigService.js +0 -6
- package/dist/services/implements/browser/ConfigServiceBrowserCompat.d.ts +0 -16
- package/dist/services/implements/browser/ConfigServiceBrowserCompat.js +0 -23
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { RemoteDBSettings, RemotePreferredTweakResult } from "../common/types.js";
|
|
2
|
+
import type { JoinedDevicesSnapshot, ForgetJoinedDeviceResult } from "./joinedDevices.js";
|
|
2
3
|
import type { RemoteDBStatus } from "./LiveSyncAbstractReplicator.js";
|
|
3
4
|
import { type CapabilitySupport } from "./ProviderCapability.js";
|
|
5
|
+
import type { InitialFetchFastForward } from "./couchdb/fastForward.js";
|
|
4
6
|
/** Stable machine identifiers for finite provider-owned remote resources. */
|
|
5
7
|
export declare const REMOTE_RESOURCE_KINDS: Readonly<{
|
|
6
8
|
readonly CONNECTION: "connection";
|
|
9
|
+
readonly JOINED_DEVICES: "joined-devices";
|
|
7
10
|
readonly PREFERRED_TWEAK: "preferred-tweak";
|
|
8
11
|
readonly SECURITY_SEED: "security-seed";
|
|
9
12
|
readonly SYNCHRONISATION_INFORMATION: "synchronisation-information";
|
|
13
|
+
readonly INITIAL_FETCH_FAST_FORWARD: "initial-fetch-fast-forward";
|
|
10
14
|
}>;
|
|
11
15
|
export type RemoteResourceKind = (typeof REMOTE_RESOURCE_KINDS)[keyof typeof REMOTE_RESOURCE_KINDS];
|
|
12
16
|
/** Options understood by a provider's finite remote-connection resource. */
|
|
@@ -64,22 +68,39 @@ export interface SynchronisationInformationResource {
|
|
|
64
68
|
check(): Promise<boolean>;
|
|
65
69
|
dispose(): Promise<void>;
|
|
66
70
|
}
|
|
71
|
+
/** Checkpoint handoff owned exclusively by one fresh Fast Fetch. */
|
|
72
|
+
export interface InitialFetchFastForwardResource {
|
|
73
|
+
prepare(): Promise<InitialFetchFastForward | undefined>;
|
|
74
|
+
dispose(): Promise<void>;
|
|
75
|
+
}
|
|
76
|
+
/** An owned reader and editor of device records for one remote settings snapshot. */
|
|
77
|
+
export interface JoinedDevicesResource {
|
|
78
|
+
read(): Promise<JoinedDevicesSnapshot>;
|
|
79
|
+
forget(deviceId: string): Promise<ForgetJoinedDeviceResult>;
|
|
80
|
+
dispose(): Promise<void>;
|
|
81
|
+
}
|
|
67
82
|
export type RemoteResourceMap = {
|
|
83
|
+
readonly [REMOTE_RESOURCE_KINDS.JOINED_DEVICES]: JoinedDevicesResource;
|
|
68
84
|
readonly [REMOTE_RESOURCE_KINDS.CONNECTION]: RemoteConnectionProbe;
|
|
69
85
|
readonly [REMOTE_RESOURCE_KINDS.PREFERRED_TWEAK]: PreferredTweakProbe;
|
|
70
86
|
readonly [REMOTE_RESOURCE_KINDS.SECURITY_SEED]: SecuritySeedResource;
|
|
71
87
|
readonly [REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION]: SynchronisationInformationResource;
|
|
88
|
+
readonly [REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD]: InitialFetchFastForwardResource;
|
|
72
89
|
};
|
|
73
90
|
export type RemoteResourceFactoryMap = {
|
|
74
91
|
readonly [K in RemoteResourceKind]: (setting: RemoteDBSettings) => Promise<RemoteResourceMap[K]>;
|
|
75
92
|
};
|
|
76
|
-
/**
|
|
93
|
+
/** Resource support catalogue; older hosts may omit the initial-fetch optimisation. */
|
|
77
94
|
export type RemoteResourceCapabilities = {
|
|
78
|
-
readonly [K in RemoteResourceKind]: CapabilitySupport<RemoteResourceFactoryMap[K]>;
|
|
95
|
+
readonly [K in Exclude<RemoteResourceKind, typeof REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD>]: CapabilitySupport<RemoteResourceFactoryMap[K]>;
|
|
96
|
+
} & {
|
|
97
|
+
readonly [REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD]?: CapabilitySupport<RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD]>;
|
|
79
98
|
};
|
|
80
99
|
/** Explicit catalogue for providers which own no finite remote resources. */
|
|
81
100
|
export declare const NO_REMOTE_RESOURCE_CAPABILITIES: RemoteResourceCapabilities;
|
|
82
101
|
export type ConnectionProbeFactory = RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.CONNECTION];
|
|
83
102
|
export type PreferredTweakProbeFactory = RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.PREFERRED_TWEAK];
|
|
84
103
|
export type SecuritySeedResourceFactory = RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.SECURITY_SEED];
|
|
104
|
+
export type InitialFetchFastForwardResourceFactory = RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD];
|
|
85
105
|
export type SynchronisationInformationResourceFactory = RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION];
|
|
106
|
+
export type JoinedDevicesResourceFactory = RemoteResourceFactoryMap[typeof REMOTE_RESOURCE_KINDS.JOINED_DEVICES];
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { CAPABILITY_NOT_APPLICABLE } from "./ProviderCapability.js";
|
|
2
2
|
const REMOTE_RESOURCE_KINDS = Object.freeze({
|
|
3
3
|
CONNECTION: "connection",
|
|
4
|
+
JOINED_DEVICES: "joined-devices",
|
|
4
5
|
PREFERRED_TWEAK: "preferred-tweak",
|
|
5
6
|
SECURITY_SEED: "security-seed",
|
|
6
|
-
SYNCHRONISATION_INFORMATION: "synchronisation-information"
|
|
7
|
+
SYNCHRONISATION_INFORMATION: "synchronisation-information",
|
|
8
|
+
INITIAL_FETCH_FAST_FORWARD: "initial-fetch-fast-forward"
|
|
7
9
|
});
|
|
8
10
|
const NO_REMOTE_RESOURCE_CAPABILITIES = Object.freeze({
|
|
9
11
|
[REMOTE_RESOURCE_KINDS.CONNECTION]: CAPABILITY_NOT_APPLICABLE,
|
|
12
|
+
[REMOTE_RESOURCE_KINDS.JOINED_DEVICES]: CAPABILITY_NOT_APPLICABLE,
|
|
10
13
|
[REMOTE_RESOURCE_KINDS.PREFERRED_TWEAK]: CAPABILITY_NOT_APPLICABLE,
|
|
11
14
|
[REMOTE_RESOURCE_KINDS.SECURITY_SEED]: CAPABILITY_NOT_APPLICABLE,
|
|
12
|
-
[REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION]: CAPABILITY_NOT_APPLICABLE
|
|
15
|
+
[REMOTE_RESOURCE_KINDS.SYNCHRONISATION_INFORMATION]: CAPABILITY_NOT_APPLICABLE,
|
|
16
|
+
[REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD]: CAPABILITY_NOT_APPLICABLE
|
|
13
17
|
});
|
|
14
18
|
export {
|
|
15
19
|
NO_REMOTE_RESOURCE_CAPABILITIES,
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { type EntryDoc, type RemoteDBSettings, type EntryLeaf, type SyncParameters, type DatabaseEntry, type NodeData, type RemotePreferredTweakResult } from "../../common/types.js";
|
|
2
|
+
import type { InitialFetchFastForwardResource } from "../RemoteResource.js";
|
|
2
3
|
import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
|
|
3
4
|
import type { OwnedCouchDBConnection, RemoteConnectionOpenOptions } from "../../services/base/RemoteConnection.js";
|
|
4
5
|
import type { IPathService } from "../../services/base/IService.js";
|
|
5
6
|
import { type CentralCompatibilityDecisionRecorder } from "../CentralCompatibility.js";
|
|
6
7
|
import { type ReplicationOutcome } from "../ReplicatorProvider.js";
|
|
8
|
+
import { type ForgetJoinedDeviceResult, type JoinedDevicesSnapshot } from "../joinedDevices.js";
|
|
7
9
|
export type { OwnedCouchDBConnection, RemoteConnectionOpenOptions } from "../../services/base/RemoteConnection.js";
|
|
8
10
|
/**
|
|
9
11
|
* Compatibility constructor environment for the CouchDB Replicator facade.
|
|
@@ -45,6 +47,8 @@ export declare class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicato
|
|
|
45
47
|
private continuousStopRequested;
|
|
46
48
|
isMobile(): boolean;
|
|
47
49
|
constructor(env: LiveSyncCouchDBReplicatorEnv);
|
|
50
|
+
/** Own the checkpoint handoff for one fresh, suspended Fast Fetch only. */
|
|
51
|
+
createInitialFetchFastForwardResource(setting: RemoteDBSettings): InitialFetchFastForwardResource;
|
|
48
52
|
getInitialSyncParameters(setting: RemoteDBSettings): Promise<SyncParameters>;
|
|
49
53
|
getSyncParameters(setting: RemoteDBSettings): Promise<SyncParameters>;
|
|
50
54
|
putSyncParameters(setting: RemoteDBSettings, params: SyncParameters): Promise<boolean>;
|
|
@@ -152,6 +156,10 @@ export declare class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicato
|
|
|
152
156
|
compactRemote(setting: RemoteDBSettings): Promise<boolean>;
|
|
153
157
|
getRemoteStatus(setting: RemoteDBSettings): Promise<RemoteDBStatus | false>;
|
|
154
158
|
countCompromisedChunks(setting?: RemoteDBSettings): Promise<number | boolean>;
|
|
159
|
+
private connectForJoinedDevices;
|
|
160
|
+
private getOwnJoinedDeviceId;
|
|
161
|
+
getJoinedDevices(setting: RemoteDBSettings): Promise<JoinedDevicesSnapshot>;
|
|
162
|
+
forgetJoinedDevice(setting: RemoteDBSettings, deviceId: string): Promise<ForgetJoinedDeviceResult>;
|
|
155
163
|
getConnectedDeviceList(setting?: RemoteDBSettings): Promise<false | {
|
|
156
164
|
node_info: Record<string, NodeData>;
|
|
157
165
|
accepted_nodes: string[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VER,
|
|
3
3
|
MILESTONE_DOCID,
|
|
4
|
+
NODEINFO_DOCID,
|
|
4
5
|
REPLICATION_BUSY_TIMEOUT,
|
|
5
6
|
LOG_LEVEL_INFO,
|
|
6
7
|
LOG_LEVEL_NOTICE,
|
|
@@ -26,6 +27,7 @@ import { requiredRemoteFeatures, usesEncryptedInternalMetadata } from "../../pou
|
|
|
26
27
|
import { assessRemoteDocumentIds } from "../../pouchdb/remoteIdCompatibility.js";
|
|
27
28
|
import { isErrorOfMissingDoc } from "../../pouchdb/utils_couchdb.js";
|
|
28
29
|
import { preprocessOutgoing } from "../../pouchdb/encryption.js";
|
|
30
|
+
import { prepareInitialFetchFastForward } from "./fastForward.js";
|
|
29
31
|
import { ensureDatabaseIsCompatible, getEffectiveTweakValues } from "../../pouchdb/LiveSyncDBFunctions.js";
|
|
30
32
|
import {
|
|
31
33
|
LiveSyncAbstractReplicator
|
|
@@ -54,6 +56,10 @@ import {
|
|
|
54
56
|
replicationBlocked,
|
|
55
57
|
replicationFailed
|
|
56
58
|
} from "../ReplicatorProvider.js";
|
|
59
|
+
import {
|
|
60
|
+
forgetJoinedDevice as forgetJoinedDeviceFromStore,
|
|
61
|
+
projectJoinedDevices
|
|
62
|
+
} from "../joinedDevices.js";
|
|
57
63
|
const currentVersionRange = {
|
|
58
64
|
min: 0,
|
|
59
65
|
max: 2400,
|
|
@@ -123,6 +129,59 @@ class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
|
|
|
123
129
|
super(env);
|
|
124
130
|
this.env = env;
|
|
125
131
|
}
|
|
132
|
+
/** Own the checkpoint handoff for one fresh, suspended Fast Fetch only. */
|
|
133
|
+
createInitialFetchFastForwardResource(setting) {
|
|
134
|
+
const snapshot = { ...setting };
|
|
135
|
+
const identity = (value) => JSON.stringify([
|
|
136
|
+
value.couchDB_URI,
|
|
137
|
+
value.couchDB_DBNAME,
|
|
138
|
+
value.couchDB_USER,
|
|
139
|
+
value.couchDB_PASSWORD,
|
|
140
|
+
value.couchDB_CustomHeaders,
|
|
141
|
+
value.useJWT,
|
|
142
|
+
value.jwtAlgorithm,
|
|
143
|
+
value.jwtKey,
|
|
144
|
+
value.jwtExpDuration,
|
|
145
|
+
value.jwtKid,
|
|
146
|
+
value.jwtSub,
|
|
147
|
+
value.encrypt,
|
|
148
|
+
value.passphrase,
|
|
149
|
+
value.E2EEAlgorithm,
|
|
150
|
+
value.enableCompression,
|
|
151
|
+
value.pathNormalizationVersion,
|
|
152
|
+
value.readChunksOnline,
|
|
153
|
+
value.useRequestAPI
|
|
154
|
+
]);
|
|
155
|
+
const binding = identity(snapshot);
|
|
156
|
+
let connection;
|
|
157
|
+
let prepared = false;
|
|
158
|
+
let disposed = false;
|
|
159
|
+
return {
|
|
160
|
+
prepare: async () => {
|
|
161
|
+
if (prepared || disposed) return void 0;
|
|
162
|
+
prepared = true;
|
|
163
|
+
if (this.currentSettings.doNotSuspendOnFetching || snapshot.useRequestAPI) return void 0;
|
|
164
|
+
const local = this.rawDatabase;
|
|
165
|
+
const isCurrent = () => !disposed && this.rawDatabase === local && this.currentSettings.suspendFileWatching && this.currentSettings.suspendParseReplicationResult && identity(this.currentSettings) === binding;
|
|
166
|
+
if (!isCurrent()) return void 0;
|
|
167
|
+
const openedConnection = await this.openOwnedConnection(snapshot);
|
|
168
|
+
if (disposed) {
|
|
169
|
+
await openedConnection.close();
|
|
170
|
+
return void 0;
|
|
171
|
+
}
|
|
172
|
+
connection = openedConnection;
|
|
173
|
+
return await prepareInitialFetchFastForward(local, connection.db, {
|
|
174
|
+
push: {},
|
|
175
|
+
...snapshot.readChunksOnline ? { pull: { ...selectorOnDemandPull } } : {}
|
|
176
|
+
}, isCurrent);
|
|
177
|
+
},
|
|
178
|
+
dispose: async () => {
|
|
179
|
+
if (disposed) return;
|
|
180
|
+
disposed = true;
|
|
181
|
+
if (connection) await connection.close();
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
}
|
|
126
185
|
getInitialSyncParameters(setting) {
|
|
127
186
|
return Promise.resolve({
|
|
128
187
|
...DEFAULT_SYNC_PARAMETERS,
|
|
@@ -925,7 +984,8 @@ ${sendingDocs}/(${sentDocsCount} done)`,
|
|
|
925
984
|
plugin_version: this.env.services.API.getPluginVersion(),
|
|
926
985
|
vault_name: this.env.services.vault.vaultName(),
|
|
927
986
|
device_name: this.env.services.vault.getVaultName(),
|
|
928
|
-
progress
|
|
987
|
+
progress,
|
|
988
|
+
platform: this.env.services.API.getPlatform()
|
|
929
989
|
};
|
|
930
990
|
let tweakAssessment;
|
|
931
991
|
const ensure = await ensureDatabaseIsCompatible(
|
|
@@ -1608,6 +1668,77 @@ ${sendingDocs}/(${sentDocsCount} done)`,
|
|
|
1608
1668
|
}
|
|
1609
1669
|
return await this.withRemoteConnection(dbRet, countCompromisedChunks);
|
|
1610
1670
|
}
|
|
1671
|
+
async connectForJoinedDevices(setting) {
|
|
1672
|
+
let result;
|
|
1673
|
+
try {
|
|
1674
|
+
result = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), false);
|
|
1675
|
+
} catch {
|
|
1676
|
+
throw new Error("Could not connect to remote CouchDB for joined-device management.");
|
|
1677
|
+
}
|
|
1678
|
+
if (typeof result === "string") {
|
|
1679
|
+
throw new Error("Could not connect to remote CouchDB for joined-device management.");
|
|
1680
|
+
}
|
|
1681
|
+
return result;
|
|
1682
|
+
}
|
|
1683
|
+
async getOwnJoinedDeviceId() {
|
|
1684
|
+
const nodeInfo = await this.rawDatabase.get(NODEINFO_DOCID);
|
|
1685
|
+
const nodeId = nodeInfo?.nodeid;
|
|
1686
|
+
if (typeof nodeId !== "string" || nodeId.trim().length === 0) {
|
|
1687
|
+
throw new Error("The local node identity is unavailable.");
|
|
1688
|
+
}
|
|
1689
|
+
return nodeId;
|
|
1690
|
+
}
|
|
1691
|
+
async getJoinedDevices(setting) {
|
|
1692
|
+
const connection = await this.connectForJoinedDevices(setting);
|
|
1693
|
+
return await this.withRemoteConnection(connection, async (db) => {
|
|
1694
|
+
let milestone;
|
|
1695
|
+
try {
|
|
1696
|
+
milestone = await db.get(MILESTONE_DOCID);
|
|
1697
|
+
} catch (ex) {
|
|
1698
|
+
if (!isErrorOfMissingDoc(ex)) throw ex;
|
|
1699
|
+
}
|
|
1700
|
+
return projectJoinedDevices(milestone);
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
async forgetJoinedDevice(setting, deviceId) {
|
|
1704
|
+
const ownDeviceId = await this.getOwnJoinedDeviceId();
|
|
1705
|
+
const connection = await this.connectForJoinedDevices(setting);
|
|
1706
|
+
return await this.withRemoteConnection(connection, async (db) => {
|
|
1707
|
+
const store = {
|
|
1708
|
+
read: async () => {
|
|
1709
|
+
let milestone;
|
|
1710
|
+
try {
|
|
1711
|
+
milestone = await db.get(MILESTONE_DOCID);
|
|
1712
|
+
} catch (ex) {
|
|
1713
|
+
if (isErrorOfMissingDoc(ex)) return void 0;
|
|
1714
|
+
throw ex;
|
|
1715
|
+
}
|
|
1716
|
+
return { milestone, revision: milestone._rev };
|
|
1717
|
+
},
|
|
1718
|
+
write: async (milestone, revision) => {
|
|
1719
|
+
try {
|
|
1720
|
+
await db.put({ ...milestone, _rev: revision });
|
|
1721
|
+
} catch (ex) {
|
|
1722
|
+
if (ex && typeof ex === "object" && ex.status === 409) {
|
|
1723
|
+
return "conflict";
|
|
1724
|
+
}
|
|
1725
|
+
throw ex;
|
|
1726
|
+
}
|
|
1727
|
+
let verifiedMilestone;
|
|
1728
|
+
try {
|
|
1729
|
+
verifiedMilestone = await db.get(MILESTONE_DOCID);
|
|
1730
|
+
} catch {
|
|
1731
|
+
throw new Error("Could not verify joined-device removal.");
|
|
1732
|
+
}
|
|
1733
|
+
if (projectJoinedDevices(verifiedMilestone).devices.some((device) => device.id === deviceId)) {
|
|
1734
|
+
throw new Error("Could not verify joined-device removal.");
|
|
1735
|
+
}
|
|
1736
|
+
return "updated";
|
|
1737
|
+
}
|
|
1738
|
+
};
|
|
1739
|
+
return await forgetJoinedDeviceFromStore(store, deviceId, ownDeviceId);
|
|
1740
|
+
});
|
|
1741
|
+
}
|
|
1611
1742
|
async getConnectedDeviceList(setting = this.currentSettings) {
|
|
1612
1743
|
const dbRet = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
|
|
1613
1744
|
if (typeof dbRet === "string") {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EntryDoc } from "../../common/types.js";
|
|
2
|
+
type Sequence = string | number;
|
|
3
|
+
/** Evidence retained only for the currently running, fresh Fast Fetch. */
|
|
4
|
+
export interface InitialFetchFastForward {
|
|
5
|
+
recordStoredRevisions(revisions: readonly {
|
|
6
|
+
id: string;
|
|
7
|
+
rev: string;
|
|
8
|
+
}[]): void;
|
|
9
|
+
recordCheckpoint(sequence: Sequence): void;
|
|
10
|
+
fastForward(): Promise<boolean>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Prepare ordinary checkpoint pairs before a fresh import into an empty database.
|
|
14
|
+
* The importer must remain the sole writer until fastForward settles.
|
|
15
|
+
* The remote database must remain in place throughout the import and handoff:
|
|
16
|
+
* PouchDB's HTTP ID identifies a server and database name, not its incarnation.
|
|
17
|
+
*/
|
|
18
|
+
export declare function prepareInitialFetchFastForward(localDatabase: PouchDB.Database<EntryDoc>, remoteDatabase: PouchDB.Database<EntryDoc>, options: PouchDB.Replication.SyncOptions, isCurrent: () => boolean): Promise<InitialFetchFastForward | undefined>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import generateReplicationId from "pouchdb-generate-replication-id";
|
|
2
|
+
import Checkpointer from "pouchdb-checkpointer";
|
|
3
|
+
import { uuid } from "pouchdb-utils";
|
|
4
|
+
const MAX_TRACKED_REVISIONS = 1e5;
|
|
5
|
+
async function prepareInitialFetchFastForward(localDatabase, remoteDatabase, options, isCurrent) {
|
|
6
|
+
const local = localDatabase;
|
|
7
|
+
const remote = remoteDatabase;
|
|
8
|
+
if ((await local.info()).doc_count !== 0 || !isCurrent()) return void 0;
|
|
9
|
+
const localId = await local.id();
|
|
10
|
+
const remoteId = await remote.id();
|
|
11
|
+
const pairs = [
|
|
12
|
+
{ source: local, target: remote, options: { ...options, ...options.push } },
|
|
13
|
+
{ source: remote, target: local, options: { ...options, ...options.pull } }
|
|
14
|
+
];
|
|
15
|
+
const checkpoints = [];
|
|
16
|
+
for (const pair of pairs) {
|
|
17
|
+
const id = await generateReplicationId(pair.source, pair.target, pair.options);
|
|
18
|
+
for (const database of [local, remote]) {
|
|
19
|
+
try {
|
|
20
|
+
await database.get(id);
|
|
21
|
+
return void 0;
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error.status !== 404) throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const session = uuid();
|
|
27
|
+
await new Checkpointer(pair.source, pair.target, id, { cancelled: false }).writeCheckpoint(0, session);
|
|
28
|
+
checkpoints.push({
|
|
29
|
+
id,
|
|
30
|
+
session,
|
|
31
|
+
localRevision: (await local.get(id))._rev,
|
|
32
|
+
remoteRevision: (await remote.get(id))._rev
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
const imported = /* @__PURE__ */ new Set();
|
|
36
|
+
let withinBudget = true;
|
|
37
|
+
let lastSequence;
|
|
38
|
+
let consumed = false;
|
|
39
|
+
const key = (id, rev) => JSON.stringify([id, rev]);
|
|
40
|
+
return {
|
|
41
|
+
recordStoredRevisions(revisions) {
|
|
42
|
+
if (!withinBudget) return;
|
|
43
|
+
for (const revision of revisions) {
|
|
44
|
+
imported.add(key(revision.id, revision.rev));
|
|
45
|
+
if (imported.size > MAX_TRACKED_REVISIONS) {
|
|
46
|
+
imported.clear();
|
|
47
|
+
withinBudget = false;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
recordCheckpoint(sequence) {
|
|
53
|
+
lastSequence = sequence;
|
|
54
|
+
},
|
|
55
|
+
async fastForward() {
|
|
56
|
+
if (consumed) return false;
|
|
57
|
+
consumed = true;
|
|
58
|
+
if (!withinBudget || lastSequence === void 0 || !isCurrent()) return false;
|
|
59
|
+
if (await local.id() !== localId || await remote.id() !== remoteId) return false;
|
|
60
|
+
const localSequence = (await local.info()).update_seq;
|
|
61
|
+
let since = 0;
|
|
62
|
+
while (true) {
|
|
63
|
+
const changes = await local.changes({
|
|
64
|
+
since,
|
|
65
|
+
limit: 1e3,
|
|
66
|
+
style: "all_docs",
|
|
67
|
+
return_docs: true
|
|
68
|
+
});
|
|
69
|
+
for (const change of changes.results) {
|
|
70
|
+
for (const revision of change.changes) {
|
|
71
|
+
if (!imported.has(key(change.id, revision.rev))) return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (changes.results.length < 1e3) break;
|
|
75
|
+
since = changes.last_seq;
|
|
76
|
+
}
|
|
77
|
+
if (!isCurrent() || (await local.info()).update_seq !== localSequence) return false;
|
|
78
|
+
const guard = (database, id, expectedRevision, session) => ({
|
|
79
|
+
adapter: database.adapter,
|
|
80
|
+
async get(requestedId) {
|
|
81
|
+
let document;
|
|
82
|
+
try {
|
|
83
|
+
document = await database.get(requestedId);
|
|
84
|
+
} catch (cause) {
|
|
85
|
+
throw new Error("The initial-fetch checkpoint is no longer available.", { cause });
|
|
86
|
+
}
|
|
87
|
+
if (requestedId !== id || document._rev !== expectedRevision || document.session_id !== session) {
|
|
88
|
+
throw new Error("The initial-fetch checkpoint changed before fast-forwarding.");
|
|
89
|
+
}
|
|
90
|
+
return document;
|
|
91
|
+
},
|
|
92
|
+
async put(document) {
|
|
93
|
+
if (document._id !== id || document._rev !== expectedRevision) {
|
|
94
|
+
throw new Error("The initial-fetch checkpoint no longer matches its import.");
|
|
95
|
+
}
|
|
96
|
+
const response = await database.put(document);
|
|
97
|
+
expectedRevision = response.rev;
|
|
98
|
+
return response;
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
for (const [index, checkpoint] of checkpoints.entries()) {
|
|
102
|
+
if (!isCurrent() || (await local.info()).update_seq !== localSequence) return false;
|
|
103
|
+
const guardedLocal = guard(local, checkpoint.id, checkpoint.localRevision, checkpoint.session);
|
|
104
|
+
const guardedRemote = guard(remote, checkpoint.id, checkpoint.remoteRevision, checkpoint.session);
|
|
105
|
+
const source = index === 0 ? guardedLocal : guardedRemote;
|
|
106
|
+
const target = index === 0 ? guardedRemote : guardedLocal;
|
|
107
|
+
await new Checkpointer(source, target, checkpoint.id, { cancelled: false }).writeCheckpoint(
|
|
108
|
+
index === 0 ? localSequence : lastSequence,
|
|
109
|
+
checkpoint.session
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
imported.clear();
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export {
|
|
118
|
+
prepareInitialFetchFastForward
|
|
119
|
+
};
|
|
@@ -3,10 +3,12 @@ export type { ReplicatorInstance } from "./ReplicatorInstance.js";
|
|
|
3
3
|
export type { ActiveReplicatorContext, CapabilitySupport, ContinuousReplicationRequest, ContinuousReplicationTrigger, ContinuousRunner, InteractionAuthority, InteractionPermissions, NotApplicableCapability, NotImplementedCapability, NoInteraction, PermittedInteractionPermissions, ReplicationBlockReason, ReplicationAttemptFailure, ReplicationCancelled, ReplicationCompleted, ReplicationFailed, ReplicationInteraction, ReplicationOutcome, ReplicationProgressPresentation, ReplicationReadinessRequirements, ReplicationPartial, ReplicationFailureRequest, ReplicatorConfigurationIdentity, ReplicatorProviderDefinition, ReplicatorProviderDefinitionMap, StopActiveTransferRunner, SupportedCapability, UnattendedOneShotRequest, UnattendedReplicationTrigger, UnattendedOneShotRunner, UserInitiatedOneShotRequest, UserInitiatedReplicationAuthority, UserInitiatedOneShotRunner, } from "./ReplicatorProvider.js";
|
|
4
4
|
export { CAPABILITY_SUPPORT_KINDS, CAPABILITY_UNAVAILABLE_REASONS } from "./ProviderCapability.js";
|
|
5
5
|
export { NO_REMOTE_RESOURCE_CAPABILITIES, REMOTE_RESOURCE_KINDS } from "./RemoteResource.js";
|
|
6
|
-
export type { ConnectionProbeFactory, PreferredTweakProbe, PreferredTweakProbeFactory, RemoteConnectionProbe, RemoteConnectionProbeOptions, RemoteConnectionProbeResult, RemoteResourceCapabilities, RemoteResourceFactoryMap, RemoteResourceKind, RemoteResourceMap, SecuritySeedResource, SecuritySeedResourceFactory, SynchronisationInformationResource, SynchronisationInformationResourceFactory, } from "./RemoteResource.js";
|
|
6
|
+
export type { InitialFetchFastForwardResource, InitialFetchFastForwardResourceFactory, ConnectionProbeFactory, JoinedDevicesResource, JoinedDevicesResourceFactory, PreferredTweakProbe, PreferredTweakProbeFactory, RemoteConnectionProbe, RemoteConnectionProbeOptions, RemoteConnectionProbeResult, RemoteResourceCapabilities, RemoteResourceFactoryMap, RemoteResourceKind, RemoteResourceMap, SecuritySeedResource, SecuritySeedResourceFactory, SynchronisationInformationResource, SynchronisationInformationResourceFactory, } from "./RemoteResource.js";
|
|
7
|
+
export type { InitialFetchFastForward } from "./couchdb/fastForward.js";
|
|
7
8
|
export { CENTRAL_REMOTE_ADMINISTRATION_ACTIONS, CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS, CENTRAL_REMOTE_ADMINISTRATION_OBSERVATION_KINDS, CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES, applyCentralRemoteAdministrationMutation, isCentralRemoteAdministrationVerified, milestoneSatisfiesCentralRemoteAdministration, centralRemoteAdministrationVerificationFailed, centralRemoteAdministrationVerified, } from "./CentralRemoteAdministration.js";
|
|
8
9
|
export type { MilestoneCentralRemoteAdministrationObservation, CentralRemoteAdministrationAction, CentralRemoteAdministrationFailureReason, CentralRemoteAdministrationObservation, CentralRemoteAdministrationRequest, CentralRemoteAdministrationReplicator, CentralRemoteAdministrationResult, CentralRemoteAdministrationRunner, CentralRemoteAdministrationVerificationFailed, CentralRemoteAdministrationVerified, } from "./CentralRemoteAdministration.js";
|
|
9
10
|
export { CENTRAL_COMPATIBILITY_REJECTION_REASONS } from "./CentralCompatibility.js";
|
|
10
11
|
export type { CentralCompatibilityRecoveryHint, CentralCompatibilityRejectionReason } from "./CentralCompatibility.js";
|
|
11
12
|
export { assessRemoteFeatureDocument, describeRemoteFeatureRejection, ENCRYPTED_INTERNAL_METADATA_FEATURE, NFC_PATH_IDENTITY_FEATURE, REMOTE_FEATURE_GENERATION, usesEncryptedInternalMetadata, } from "../pouchdb/remoteFeatureCompatibility.js";
|
|
12
13
|
export type { RemoteFeatureAssessment } from "../pouchdb/remoteFeatureCompatibility.js";
|
|
14
|
+
export type { JoinedDevice, JoinedDevicesSnapshot, ForgetJoinedDeviceResult } from "./joinedDevices.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type EntryMilestoneInfo } from "../common/types.js";
|
|
2
|
+
export interface JoinedDevice {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly accepted: boolean;
|
|
5
|
+
readonly platform?: string;
|
|
6
|
+
readonly pluginVersion?: string;
|
|
7
|
+
readonly appVersion?: string;
|
|
8
|
+
readonly lastConnected?: number;
|
|
9
|
+
}
|
|
10
|
+
export interface JoinedDevicesSnapshot {
|
|
11
|
+
readonly milestonePresent: boolean;
|
|
12
|
+
readonly locked: boolean;
|
|
13
|
+
readonly cleaned: boolean;
|
|
14
|
+
readonly devices: readonly JoinedDevice[];
|
|
15
|
+
}
|
|
16
|
+
export declare function projectJoinedDevices(milestone: EntryMilestoneInfo | undefined): JoinedDevicesSnapshot;
|
|
17
|
+
export type ForgetJoinedDeviceResult = "forgotten" | "not-found" | "conflict" | "unsupported";
|
|
18
|
+
export interface JoinedDevicesStore {
|
|
19
|
+
read(): Promise<{
|
|
20
|
+
milestone: EntryMilestoneInfo;
|
|
21
|
+
revision?: string;
|
|
22
|
+
} | undefined>;
|
|
23
|
+
write(milestone: EntryMilestoneInfo, revision: string): Promise<"updated" | "conflict" | "unsupported">;
|
|
24
|
+
}
|
|
25
|
+
export declare function forgetJoinedDevice(store: JoinedDevicesStore, deviceId: string, ownDeviceId: string): Promise<ForgetJoinedDeviceResult>;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { DEVICE_ID_PREFERRED } from "../common/types.js";
|
|
2
|
+
function projectJoinedDevices(milestone) {
|
|
3
|
+
if (milestone === void 0) {
|
|
4
|
+
return { milestonePresent: false, locked: false, cleaned: false, devices: [] };
|
|
5
|
+
}
|
|
6
|
+
const source = milestone;
|
|
7
|
+
const nodeInfo = asRecord(source.node_info);
|
|
8
|
+
const acceptedNodes = Array.isArray(source.accepted_nodes) ? source.accepted_nodes.filter((id) => typeof id === "string") : [];
|
|
9
|
+
const accepted = new Set(acceptedNodes);
|
|
10
|
+
const ids = /* @__PURE__ */ new Set();
|
|
11
|
+
for (const id of acceptedNodes) {
|
|
12
|
+
if (id !== DEVICE_ID_PREFERRED) ids.add(id);
|
|
13
|
+
}
|
|
14
|
+
addRecordKeys(ids, source.node_info);
|
|
15
|
+
addRecordKeys(ids, source.node_chunk_info);
|
|
16
|
+
addRecordKeys(ids, source.tweak_values);
|
|
17
|
+
const devices = [...ids].filter((id) => id !== DEVICE_ID_PREFERRED).sort().map((id) => {
|
|
18
|
+
const nodeData = nodeInfo && hasOwn(nodeInfo, id) ? asRecord(nodeInfo[id]) : void 0;
|
|
19
|
+
const platform = readString(nodeData, "platform");
|
|
20
|
+
const pluginVersion = readString(nodeData, "plugin_version");
|
|
21
|
+
const appVersion = readString(nodeData, "app_version");
|
|
22
|
+
const lastConnected = readTimestamp(nodeData?.last_connected);
|
|
23
|
+
return {
|
|
24
|
+
id,
|
|
25
|
+
accepted: accepted.has(id),
|
|
26
|
+
...platform === void 0 ? {} : { platform },
|
|
27
|
+
...pluginVersion === void 0 ? {} : { pluginVersion },
|
|
28
|
+
...appVersion === void 0 ? {} : { appVersion },
|
|
29
|
+
...lastConnected === void 0 ? {} : { lastConnected }
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
milestonePresent: true,
|
|
34
|
+
locked: source.locked === true,
|
|
35
|
+
cleaned: source.cleaned === true,
|
|
36
|
+
devices
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const MAX_CONFLICT_RETRIES = 3;
|
|
40
|
+
async function forgetJoinedDevice(store, deviceId, ownDeviceId) {
|
|
41
|
+
if (ownDeviceId.length === 0) {
|
|
42
|
+
throw new Error("The current device ID must not be empty");
|
|
43
|
+
}
|
|
44
|
+
if (deviceId.length === 0) {
|
|
45
|
+
throw new Error("The device ID must not be empty");
|
|
46
|
+
}
|
|
47
|
+
if (deviceId === DEVICE_ID_PREFERRED) {
|
|
48
|
+
throw new Error("The preferred-device marker cannot be forgotten");
|
|
49
|
+
}
|
|
50
|
+
if (deviceId === ownDeviceId) {
|
|
51
|
+
throw new Error("The current device cannot be forgotten");
|
|
52
|
+
}
|
|
53
|
+
let conflictRetries = 0;
|
|
54
|
+
while (true) {
|
|
55
|
+
const current = await store.read();
|
|
56
|
+
if (current === void 0 || !hasDevice(current.milestone, deviceId)) {
|
|
57
|
+
return "not-found";
|
|
58
|
+
}
|
|
59
|
+
if (typeof current.revision !== "string") {
|
|
60
|
+
return "unsupported";
|
|
61
|
+
}
|
|
62
|
+
const updatedMilestone = removeDevice(current.milestone, deviceId);
|
|
63
|
+
const result = await store.write(updatedMilestone, current.revision);
|
|
64
|
+
if (result === "updated") return "forgotten";
|
|
65
|
+
if (result === "unsupported") return "unsupported";
|
|
66
|
+
if (conflictRetries >= MAX_CONFLICT_RETRIES) return "conflict";
|
|
67
|
+
conflictRetries++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function asRecord(value) {
|
|
71
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
72
|
+
return value;
|
|
73
|
+
}
|
|
74
|
+
function hasOwn(record, key) {
|
|
75
|
+
return Object.prototype.hasOwnProperty.call(record, key);
|
|
76
|
+
}
|
|
77
|
+
function addRecordKeys(ids, value) {
|
|
78
|
+
const record = asRecord(value);
|
|
79
|
+
if (!record) return;
|
|
80
|
+
for (const id of Object.keys(record)) {
|
|
81
|
+
if (id !== DEVICE_ID_PREFERRED) ids.add(id);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function readString(record, key) {
|
|
85
|
+
const value = record?.[key];
|
|
86
|
+
return typeof value === "string" ? value : void 0;
|
|
87
|
+
}
|
|
88
|
+
function readTimestamp(value) {
|
|
89
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
90
|
+
}
|
|
91
|
+
function hasDevice(milestone, deviceId) {
|
|
92
|
+
const source = milestone;
|
|
93
|
+
const accepted = Array.isArray(source.accepted_nodes) && source.accepted_nodes.includes(deviceId);
|
|
94
|
+
return accepted || hasMapKey(source.node_info, deviceId) || hasMapKey(source.node_chunk_info, deviceId) || hasMapKey(source.tweak_values, deviceId);
|
|
95
|
+
}
|
|
96
|
+
function hasMapKey(value, deviceId) {
|
|
97
|
+
const record = asRecord(value);
|
|
98
|
+
return record !== void 0 && hasOwn(record, deviceId);
|
|
99
|
+
}
|
|
100
|
+
function removeDevice(milestone, deviceId) {
|
|
101
|
+
const source = milestone;
|
|
102
|
+
const updated = { ...source };
|
|
103
|
+
for (const key of ["node_info", "node_chunk_info", "tweak_values"]) {
|
|
104
|
+
if (hasOwn(source, key)) {
|
|
105
|
+
updated[key] = removeMapKey(source[key], deviceId);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (hasOwn(source, "accepted_nodes") && Array.isArray(source.accepted_nodes)) {
|
|
109
|
+
updated.accepted_nodes = source.accepted_nodes.filter((id) => id !== deviceId);
|
|
110
|
+
}
|
|
111
|
+
return updated;
|
|
112
|
+
}
|
|
113
|
+
function removeMapKey(value, deviceId) {
|
|
114
|
+
const record = asRecord(value);
|
|
115
|
+
if (!record) return value;
|
|
116
|
+
const updated = { ...record };
|
|
117
|
+
delete updated[deviceId];
|
|
118
|
+
return updated;
|
|
119
|
+
}
|
|
120
|
+
export {
|
|
121
|
+
forgetJoinedDevice,
|
|
122
|
+
projectJoinedDevices
|
|
123
|
+
};
|
|
@@ -3,7 +3,7 @@ import type { ReplicationCallback, ReplicationStat } from "../LiveSyncAbstractRe
|
|
|
3
3
|
import { type SimpleStore } from "../../common/utils.js";
|
|
4
4
|
import { type CheckPointInfo } from "./JournalSyncTypes.js";
|
|
5
5
|
import type { LiveSyncJournalReplicatorEnv } from "./LiveSyncJournalReplicatorEnv.js";
|
|
6
|
-
import { type IJournalStorage, type JournalStorageReadResult } from "./objectstore/JournalStorageAdapter.js";
|
|
6
|
+
import { type IJournalStorage, type JournalStorageConditionalWriteResult, type JournalStorageReadResult } from "./objectstore/JournalStorageAdapter.js";
|
|
7
7
|
type ProcessingEntry = PouchDB.Core.PutDocument<EntryDoc> & PouchDB.Core.GetMeta;
|
|
8
8
|
export declare class JournalSyncCore {
|
|
9
9
|
_settings: BucketSyncSetting;
|
|
@@ -24,6 +24,7 @@ export declare class JournalSyncCore {
|
|
|
24
24
|
constructor(settings: BucketSyncSetting, store: SimpleStore<CheckPointInfo>, env: LiveSyncJournalReplicatorEnv, storage: IJournalStorage);
|
|
25
25
|
downloadJson<T>(key: string): Promise<T | false>;
|
|
26
26
|
downloadJsonWithResult<T>(key: string): Promise<JournalStorageReadResult<T>>;
|
|
27
|
+
uploadJsonIfUnchanged<T>(key: string, body: T, revision: string): Promise<JournalStorageConditionalWriteResult>;
|
|
27
28
|
uploadJson<T>(key: string, body: T): Promise<boolean>;
|
|
28
29
|
applyNewConfig(settings: BucketSyncSetting, store: SimpleStore<CheckPointInfo>, env: LiveSyncJournalReplicatorEnv): void;
|
|
29
30
|
updateInfo(info: Partial<ReplicationStat>): void;
|
|
@@ -127,7 +127,8 @@ class JournalSyncCore {
|
|
|
127
127
|
try {
|
|
128
128
|
return {
|
|
129
129
|
status: JournalStorageReadStatuses.AVAILABLE,
|
|
130
|
-
value: JSON.parse(new TextDecoder().decode(result.value))
|
|
130
|
+
value: JSON.parse(new TextDecoder().decode(result.value)),
|
|
131
|
+
...result.revision === void 0 ? {} : { revision: result.revision }
|
|
131
132
|
};
|
|
132
133
|
} catch (ex) {
|
|
133
134
|
Logger(`Could not parse downloaded json ${key}`);
|
|
@@ -138,6 +139,15 @@ class JournalSyncCore {
|
|
|
138
139
|
};
|
|
139
140
|
}
|
|
140
141
|
}
|
|
142
|
+
async uploadJsonIfUnchanged(key, body, revision) {
|
|
143
|
+
if (revision.trim().length === 0) {
|
|
144
|
+
throw new TypeError("A non-empty revision is required for a conditional upload");
|
|
145
|
+
}
|
|
146
|
+
const uploadIfUnchanged = this.storage.uploadIfUnchanged;
|
|
147
|
+
if (!uploadIfUnchanged) return "unsupported";
|
|
148
|
+
const data = new TextEncoder().encode(JSON.stringify(body));
|
|
149
|
+
return await uploadIfUnchanged.call(this.storage, key, data, "application/json", revision);
|
|
150
|
+
}
|
|
141
151
|
async uploadJson(key, body) {
|
|
142
152
|
try {
|
|
143
153
|
const data = new TextEncoder().encode(JSON.stringify(body));
|
|
@@ -7,6 +7,7 @@ import type { SimpleStore } from "../../common/utils.js";
|
|
|
7
7
|
import type { LiveSyncJournalReplicatorEnv } from "./LiveSyncJournalReplicatorEnv.js";
|
|
8
8
|
import { type CentralCompatibilityDecisionRecorder } from "../CentralCompatibility.js";
|
|
9
9
|
import { type ReplicationOutcome } from "../ReplicatorProvider.js";
|
|
10
|
+
import { type ForgetJoinedDeviceResult, type JoinedDevicesSnapshot } from "../joinedDevices.js";
|
|
10
11
|
export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
|
|
11
12
|
env: LiveSyncJournalReplicatorEnv;
|
|
12
13
|
get client(): JournalSyncCore;
|
|
@@ -63,6 +64,9 @@ export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicato
|
|
|
63
64
|
getRemoteStatus(setting: RemoteDBSettings): Promise<false | RemoteDBStatus>;
|
|
64
65
|
private supportsLegacyPathIdentity;
|
|
65
66
|
private assertLegacyPathIdentity;
|
|
67
|
+
private getOwnJoinedDeviceId;
|
|
68
|
+
getJoinedDevices(setting: RemoteDBSettings): Promise<JoinedDevicesSnapshot>;
|
|
69
|
+
forgetJoinedDevice(setting: RemoteDBSettings, deviceId: string): Promise<ForgetJoinedDeviceResult>;
|
|
66
70
|
getConnectedDeviceList(setting?: RemoteDBSettings): Promise<false | {
|
|
67
71
|
node_info: Record<string, NodeData>;
|
|
68
72
|
accepted_nodes: string[];
|