@vrtmrz/livesync-commonlib 0.1.24 → 0.1.26
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/API/processSetting.d.ts +1 -1
- package/dist/API/processSetting.js +10 -6
- package/dist/common/ConnectionString.js +15 -8
- package/dist/common/models/setting.const.qr.js +5 -0
- package/dist/common/models/setting.lifecycle.js +5 -3
- package/dist/common/models/setting.p2p.d.ts +10 -0
- package/dist/common/models/setting.p2p.js +13 -0
- package/dist/common/models/setting.type.d.ts +10 -0
- package/dist/common/models/tweak.definition.d.ts +5 -0
- package/dist/common/settingConstants.d.ts +5 -0
- package/dist/common/types.d.ts +3 -3
- package/dist/common/types.js +6 -0
- package/dist/common/utils.js +4 -1
- package/dist/interfaces/DatabaseFileAccess.d.ts +31 -3
- package/dist/interfaces/FileReflectionProvenance.d.ts +2 -2
- package/dist/managers/EntryManager/EntryManager.d.ts +4 -2
- package/dist/managers/EntryManager/EntryManager.js +9 -4
- package/dist/managers/EntryManager/EntryManagerImpls.d.ts +12 -4
- package/dist/managers/EntryManager/EntryManagerImpls.js +27 -9
- package/dist/p2p/P2PService.d.ts +7 -1
- package/dist/p2p/P2PService.js +13 -7
- package/dist/p2p/index.d.ts +3 -1
- package/dist/p2p/index.js +6 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +4 -2
- package/dist/pouchdb/LiveSyncLocalDB.js +8 -4
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +22 -1
- package/dist/replication/trystero/P2PRoomSessionOwner.js +193 -13
- package/dist/replication/trystero/TrysteroReplicator.js +9 -3
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +1 -0
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +10 -3
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +6 -3
- package/dist/replication/trystero/useP2PReplicatorFeature.d.ts +6 -1
- package/dist/replication/trystero/useP2PReplicatorFeature.js +7 -4
- package/dist/rpc/transports/trysteroUtils.d.ts +2 -2
- package/dist/rpc/transports/trysteroUtils.js +32 -2
- package/dist/serviceFeatures/remoteConfig.js +3 -0
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +26 -2
- package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +57 -5
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +119 -1
- package/dist/serviceModules/ServiceFileHandlerBase.js +450 -70
- package/dist/services/base/SettingService.d.ts +1 -0
- package/dist/services/base/SettingService.js +37 -9
- package/docs/conflict-resolution.md +28 -15
- package/docs/development.md +3 -3
- package/docs/p2p-transport-lifecycle.md +86 -4
- package/docs/proven-in-use.md +31 -1
- package/package.json +1 -1
|
@@ -37,5 +37,5 @@ type ErasureProperties = keyof ObsidianLiveSyncSettings;
|
|
|
37
37
|
* @returns Generated setup URI
|
|
38
38
|
*/
|
|
39
39
|
export declare function encodeSettingsToSetupURI(settingString: ObsidianLiveSyncSettings, passphrase: string, removeProperties?: ErasureProperties[], skipDefaultValue?: boolean): Promise<string>;
|
|
40
|
-
export declare function decodeSettingsFromSetupURI(uri: string, passphrase: string): Promise<false | ObsidianLiveSyncSettings
|
|
40
|
+
export declare function decodeSettingsFromSetupURI(uri: string, passphrase: string): Promise<false | Omit<ObsidianLiveSyncSettings, "P2P_iceServers" | "P2P_iceServersExpiresAt">>;
|
|
41
41
|
export {};
|
|
@@ -4,11 +4,13 @@ import { decodeAnyArray, encodeAnyArray } from "octagonal-wheels/object";
|
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_SETTINGS,
|
|
6
6
|
KeyIndexOfSettings,
|
|
7
|
-
LOG_LEVEL_NOTICE
|
|
7
|
+
LOG_LEVEL_NOTICE,
|
|
8
|
+
omitP2PRuntimeSettings
|
|
8
9
|
} from "../common/types.js";
|
|
9
10
|
import { decryptString, encryptString } from "../encryption/stringEncryption.js";
|
|
10
11
|
import { LOG_LEVEL_VERBOSE, Logger } from "octagonal-wheels/common/logger";
|
|
11
12
|
function encodeSettingsToQRCodeData(settings) {
|
|
13
|
+
settings = omitP2PRuntimeSettings(settings);
|
|
12
14
|
const fullIndexes = Object.entries(KeyIndexOfSettings);
|
|
13
15
|
let maxIndex = 0;
|
|
14
16
|
for (const [, index] of fullIndexes) {
|
|
@@ -48,7 +50,7 @@ function decodeSettingsFromQRCodeData(qr) {
|
|
|
48
50
|
LOG_LEVEL_VERBOSE
|
|
49
51
|
);
|
|
50
52
|
}
|
|
51
|
-
return newSettings;
|
|
53
|
+
return omitP2PRuntimeSettings(newSettings);
|
|
52
54
|
}
|
|
53
55
|
var OutputFormat = /* @__PURE__ */ ((OutputFormat2) => {
|
|
54
56
|
OutputFormat2[OutputFormat2["SVG"] = 0] = "SVG";
|
|
@@ -99,8 +101,11 @@ const necessaryErasureProperties = [
|
|
|
99
101
|
];
|
|
100
102
|
async function encodeSettingsToSetupURI(settingString, passphrase, removeProperties = ["pluginSyncExtendedSetting"], skipDefaultValue = false) {
|
|
101
103
|
const setting = {
|
|
102
|
-
...settingString
|
|
104
|
+
...omitP2PRuntimeSettings(settingString)
|
|
103
105
|
};
|
|
106
|
+
delete setting.P2P_managedType;
|
|
107
|
+
delete setting.P2P_managedId;
|
|
108
|
+
delete setting.P2P_managedToken;
|
|
104
109
|
if (skipDefaultValue) {
|
|
105
110
|
const keys = Object.keys(setting);
|
|
106
111
|
for (const k of keys) {
|
|
@@ -123,10 +128,9 @@ async function decodeSettingsFromSetupURI(uri, passphrase) {
|
|
|
123
128
|
const encryptedSetting = uri.substring(configURIBase.length);
|
|
124
129
|
const decrypted = await decryptString(decodeURIComponent(encryptedSetting), passphrase);
|
|
125
130
|
try {
|
|
126
|
-
return JSON.parse(decrypted);
|
|
127
|
-
} catch
|
|
131
|
+
return omitP2PRuntimeSettings(JSON.parse(decrypted));
|
|
132
|
+
} catch {
|
|
128
133
|
Logger(`Failed to parse settings from decrypted data`, LOG_LEVEL_NOTICE);
|
|
129
|
-
Logger(e, LOG_LEVEL_VERBOSE);
|
|
130
134
|
return false;
|
|
131
135
|
}
|
|
132
136
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
normaliseP2PConnectionPath,
|
|
3
|
+
normaliseP2PMaxWirePayloadBytes
|
|
4
|
+
} from "./models/setting.p2p.js";
|
|
2
5
|
const PROXY_SCHEME = "https";
|
|
3
6
|
function parseSlsUri(uriString) {
|
|
4
7
|
const match = uriString.match(/^sls\+([^:]+):(.*)$/);
|
|
5
8
|
if (!match) {
|
|
6
|
-
throw new Error(
|
|
9
|
+
throw new Error("Unsupported URI");
|
|
7
10
|
}
|
|
8
11
|
const subscheme = match[1];
|
|
9
12
|
const rest = match[2];
|
|
@@ -20,7 +23,7 @@ class ConnectionStringParser {
|
|
|
20
23
|
static parse(uriString) {
|
|
21
24
|
const match = uriString.match(/^sls\+([^:]+):/);
|
|
22
25
|
if (!match) {
|
|
23
|
-
throw new Error(
|
|
26
|
+
throw new Error("Unsupported URI");
|
|
24
27
|
}
|
|
25
28
|
const subscheme = match[1];
|
|
26
29
|
if (subscheme === "p2p") {
|
|
@@ -43,7 +46,7 @@ class ConnectionStringParser {
|
|
|
43
46
|
settings: this.parseS3(url)
|
|
44
47
|
};
|
|
45
48
|
default:
|
|
46
|
-
throw new Error(
|
|
49
|
+
throw new Error("Unsupported protocol");
|
|
47
50
|
}
|
|
48
51
|
}
|
|
49
52
|
/**
|
|
@@ -126,9 +129,7 @@ class ConnectionStringParser {
|
|
|
126
129
|
}
|
|
127
130
|
static parseP2P(uriString) {
|
|
128
131
|
const match = uriString.match(/^sls\+p2p:\/\/([^?#]+)(?:\?([^#]*))?(?:#(.*))?$/);
|
|
129
|
-
if (!match)
|
|
130
|
-
throw new Error(`Invalid P2P URI: ${uriString}`);
|
|
131
|
-
}
|
|
132
|
+
if (!match) throw new Error("Invalid P2P URI");
|
|
132
133
|
const authority = match[1];
|
|
133
134
|
const queryString = match[2] || "";
|
|
134
135
|
let userinfo = "";
|
|
@@ -158,7 +159,10 @@ class ConnectionStringParser {
|
|
|
158
159
|
P2P_turnUsername: searchParams.get("turnUser") || "",
|
|
159
160
|
P2P_turnCredential: searchParams.get("turnPass") || "",
|
|
160
161
|
P2P_maxWirePayloadBytes: normaliseP2PMaxWirePayloadBytes(Number(searchParams.get("maxWirePayloadBytes"))),
|
|
161
|
-
P2P_connectionPath: normaliseP2PConnectionPath(searchParams.get("connectionPath"))
|
|
162
|
+
P2P_connectionPath: normaliseP2PConnectionPath(searchParams.get("connectionPath")),
|
|
163
|
+
P2P_managedType: searchParams.get("managedType") || void 0,
|
|
164
|
+
P2P_managedId: searchParams.get("managedId") || void 0,
|
|
165
|
+
P2P_managedToken: searchParams.get("token") || void 0
|
|
162
166
|
};
|
|
163
167
|
}
|
|
164
168
|
static serializeP2P(settings) {
|
|
@@ -176,6 +180,9 @@ class ConnectionStringParser {
|
|
|
176
180
|
String(normaliseP2PMaxWirePayloadBytes(settings.P2P_maxWirePayloadBytes))
|
|
177
181
|
);
|
|
178
182
|
searchParams.set("connectionPath", normaliseP2PConnectionPath(settings.P2P_connectionPath));
|
|
183
|
+
if (settings.P2P_managedType) searchParams.set("managedType", settings.P2P_managedType);
|
|
184
|
+
if (settings.P2P_managedId) searchParams.set("managedId", settings.P2P_managedId);
|
|
185
|
+
if (settings.P2P_managedToken) searchParams.set("token", settings.P2P_managedToken);
|
|
179
186
|
const credentials = settings.P2P_passphrase ? `:${encodeURIComponent(settings.P2P_passphrase)}@` : "";
|
|
180
187
|
const host = encodeURIComponent(settings.P2P_roomID);
|
|
181
188
|
const query = searchParams.toString() ? `?${searchParams.toString()}` : "";
|
|
@@ -159,6 +159,11 @@ const KeyIndexOfSettings = {
|
|
|
159
159
|
P2P_turnServers: 150,
|
|
160
160
|
P2P_turnUsername: 151,
|
|
161
161
|
P2P_turnCredential: 152,
|
|
162
|
+
P2P_managedType: -1,
|
|
163
|
+
P2P_managedId: -1,
|
|
164
|
+
P2P_managedToken: -1,
|
|
165
|
+
P2P_iceServers: -1,
|
|
166
|
+
P2P_iceServersExpiresAt: -1,
|
|
162
167
|
syncInternalFileOverwritePatterns: 153,
|
|
163
168
|
useOnlyLocalChunk: 154,
|
|
164
169
|
maxMTimeForReflectEvents: 155,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
SETTING_VERSION_INITIAL,
|
|
5
5
|
SETTING_VERSION_SUPPORT_CASE_INSENSITIVE
|
|
6
6
|
} from "./setting.const.js";
|
|
7
|
+
import { omitP2PRuntimeSettings } from "./setting.p2p.js";
|
|
7
8
|
const SettingsMigrationReviewCodes = {
|
|
8
9
|
LegacyUpdatePending: "legacy-update-review-pending",
|
|
9
10
|
FutureSchema: "settings-schema-newer-than-runtime",
|
|
@@ -22,18 +23,19 @@ const SETTINGS_MIGRATIONS = [
|
|
|
22
23
|
}
|
|
23
24
|
];
|
|
24
25
|
function cloneSettings(source, fallbacks) {
|
|
26
|
+
const persistableSource = omitP2PRuntimeSettings(source);
|
|
25
27
|
return {
|
|
26
28
|
...fallbacks,
|
|
27
|
-
...
|
|
29
|
+
...persistableSource,
|
|
28
30
|
remoteConfigurations: Object.fromEntries(
|
|
29
|
-
Object.entries(
|
|
31
|
+
Object.entries(persistableSource.remoteConfigurations ?? fallbacks.remoteConfigurations).map(([id, config]) => [
|
|
30
32
|
id,
|
|
31
33
|
{ ...config }
|
|
32
34
|
])
|
|
33
35
|
),
|
|
34
36
|
pluginSyncExtendedSetting: {
|
|
35
37
|
...fallbacks.pluginSyncExtendedSetting,
|
|
36
|
-
...
|
|
38
|
+
...persistableSource.pluginSyncExtendedSetting
|
|
37
39
|
}
|
|
38
40
|
};
|
|
39
41
|
}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { type P2PConnectionPath } from "./setting.const.js";
|
|
2
|
+
import type { P2PConnectionInfo, P2PSyncSetting } from "./setting.type.js";
|
|
3
|
+
/** Report whether the selected P2P settings request host-managed TURN credentials. */
|
|
4
|
+
export declare function hasManagedP2PTurnConfiguration(settings: Partial<Pick<P2PConnectionInfo, "P2P_managedType">>): boolean;
|
|
5
|
+
/** Remove connection-only ICE credentials before settings cross a serialisation boundary. */
|
|
6
|
+
export declare function omitP2PRuntimeSettings<T extends Partial<P2PSyncSetting>>(settings: T): Omit<T, "P2P_iceServers" | "P2P_iceServersExpiresAt">;
|
|
7
|
+
/**
|
|
8
|
+
* Report whether a P2P profile has a usable manual TURN endpoint or requests
|
|
9
|
+
* host-managed TURN credentials. STUN-only profiles remain false.
|
|
10
|
+
*/
|
|
11
|
+
export declare function hasP2PTurnConfiguration(settings: Partial<Pick<P2PConnectionInfo, "P2P_turnServers" | "P2P_managedType">>): boolean;
|
|
2
12
|
/**
|
|
3
13
|
* Return a safe outgoing RPC wire-payload bound for Trystero.
|
|
4
14
|
*
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { P2PConnectionPaths, P2PMessageSizePresets } from "./setting.const.js";
|
|
2
|
+
function hasManagedP2PTurnConfiguration(settings) {
|
|
3
|
+
return typeof settings.P2P_managedType === "string" && settings.P2P_managedType.trim().length > 0;
|
|
4
|
+
}
|
|
5
|
+
function omitP2PRuntimeSettings(settings) {
|
|
6
|
+
const { P2P_iceServers: _iceServers, P2P_iceServersExpiresAt: _expiresAt, ...persistable } = settings;
|
|
7
|
+
return persistable;
|
|
8
|
+
}
|
|
9
|
+
function hasP2PTurnConfiguration(settings) {
|
|
10
|
+
return hasManagedP2PTurnConfiguration(settings) || typeof settings.P2P_turnServers === "string" && hasValidP2PTurnServerUrl(settings.P2P_turnServers);
|
|
11
|
+
}
|
|
2
12
|
function normaliseP2PMaxWirePayloadBytes(value) {
|
|
3
13
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < P2PMessageSizePresets.MaximumCompatibility || value > P2PMessageSizePresets.Standard) {
|
|
4
14
|
return P2PMessageSizePresets.Standard;
|
|
@@ -31,10 +41,13 @@ function hasValidP2PTurnServerUrl(value) {
|
|
|
31
41
|
return splitP2PTurnServerUrls(value).some(isValidP2PTurnServerUrl);
|
|
32
42
|
}
|
|
33
43
|
export {
|
|
44
|
+
hasManagedP2PTurnConfiguration,
|
|
45
|
+
hasP2PTurnConfiguration,
|
|
34
46
|
hasValidP2PTurnServerUrl,
|
|
35
47
|
isValidP2PTurnServerUrl,
|
|
36
48
|
normaliseP2PConnectionPath,
|
|
37
49
|
normaliseP2PMaxWirePayloadBytes,
|
|
50
|
+
omitP2PRuntimeSettings,
|
|
38
51
|
splitP2PRelayUrls,
|
|
39
52
|
splitP2PTurnServerUrls
|
|
40
53
|
};
|
|
@@ -528,6 +528,12 @@ export interface P2PConnectionInfo {
|
|
|
528
528
|
* The TURN credential (password, secret, etc...) for the P2P connection.
|
|
529
529
|
*/
|
|
530
530
|
P2P_turnCredential: string;
|
|
531
|
+
/** Host-defined managed TURN provider type. */
|
|
532
|
+
P2P_managedType?: string;
|
|
533
|
+
/** Host-defined managed TURN provider identifier. */
|
|
534
|
+
P2P_managedId?: string;
|
|
535
|
+
/** Host-defined managed TURN provider token. */
|
|
536
|
+
P2P_managedToken?: string;
|
|
531
537
|
/**
|
|
532
538
|
* Maximum serialised RPC wire payload sent through Trystero before
|
|
533
539
|
* Commonlib splits it. Applies to outgoing messages.
|
|
@@ -552,6 +558,10 @@ export interface P2PSyncSetting extends P2PConnectionInfo {
|
|
|
552
558
|
P2P_AutoAcceptingPeers: string;
|
|
553
559
|
P2P_AutoDenyingPeers: string;
|
|
554
560
|
P2P_IsHeadless?: boolean;
|
|
561
|
+
/** ICE servers prepared for one room connection. Never persist or share this value. */
|
|
562
|
+
P2P_iceServers?: readonly RTCIceServer[];
|
|
563
|
+
/** Absolute Unix timestamp in milliseconds for the prepared ICE servers. */
|
|
564
|
+
P2P_iceServersExpiresAt?: number;
|
|
555
565
|
}
|
|
556
566
|
/**
|
|
557
567
|
* Interface representing the settings for a remote type.
|
|
@@ -261,6 +261,8 @@ export declare const TweakValuesTemplate: {
|
|
|
261
261
|
P2P_AutoAcceptingPeers?: string;
|
|
262
262
|
P2P_AutoDenyingPeers?: string;
|
|
263
263
|
P2P_IsHeadless?: boolean;
|
|
264
|
+
P2P_iceServers?: readonly RTCIceServer[];
|
|
265
|
+
P2P_iceServersExpiresAt?: number;
|
|
264
266
|
P2P_Enabled?: boolean;
|
|
265
267
|
P2P_relays?: string;
|
|
266
268
|
P2P_roomID?: string;
|
|
@@ -272,6 +274,9 @@ export declare const TweakValuesTemplate: {
|
|
|
272
274
|
P2P_turnServers?: string;
|
|
273
275
|
P2P_turnUsername?: string;
|
|
274
276
|
P2P_turnCredential?: string;
|
|
277
|
+
P2P_managedType?: string;
|
|
278
|
+
P2P_managedId?: string;
|
|
279
|
+
P2P_managedToken?: string;
|
|
275
280
|
P2P_maxWirePayloadBytes?: number;
|
|
276
281
|
P2P_connectionPath?: import("./setting.const.js").P2PConnectionPath;
|
|
277
282
|
P2P_useDiagRTC?: boolean;
|
|
@@ -185,6 +185,8 @@ export declare const AllSettingDefault: {
|
|
|
185
185
|
P2P_AutoAcceptingPeers: string;
|
|
186
186
|
P2P_AutoDenyingPeers: string;
|
|
187
187
|
P2P_IsHeadless?: boolean;
|
|
188
|
+
P2P_iceServers?: readonly RTCIceServer[];
|
|
189
|
+
P2P_iceServersExpiresAt?: number;
|
|
188
190
|
P2P_Enabled: boolean;
|
|
189
191
|
P2P_relays: string;
|
|
190
192
|
P2P_roomID: string;
|
|
@@ -196,6 +198,9 @@ export declare const AllSettingDefault: {
|
|
|
196
198
|
P2P_turnServers: string;
|
|
197
199
|
P2P_turnUsername: string;
|
|
198
200
|
P2P_turnCredential: string;
|
|
201
|
+
P2P_managedType?: string;
|
|
202
|
+
P2P_managedId?: string;
|
|
203
|
+
P2P_managedToken?: string;
|
|
199
204
|
P2P_maxWirePayloadBytes?: number;
|
|
200
205
|
P2P_connectionPath?: import("./types.js").P2PConnectionPath;
|
|
201
206
|
P2P_useDiagRTC?: boolean;
|
package/dist/common/types.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { AUTO_MERGED, CANCELLED, LEAVE_TO_SUBSEQUENT, MISSING_OR_ERROR, NOT_CONF
|
|
|
9
9
|
import { IDPrefixes, LEAF_WAIT_ONLY_REMOTE, LEAF_WAIT_TIMEOUT, LEAF_WAIT_TIMEOUT_SEQUENTIAL_REPLICATOR, MAX_DOC_SIZE, MAX_DOC_SIZE_BIN, PREFIX_CHUNK, PREFIX_ENCRYPTED_CHUNK, PREFIX_OBFUSCATED, RECENT_MODIFIED_DOCS_QTY, REPLICATION_BUSY_TIMEOUT, SALT_OF_ID, SALT_OF_PASSPHRASE, SEED_MURMURHASH, VER } from "./models/shared.const.behabiour.js";
|
|
10
10
|
import { AutoAccepting, type BucketSyncSetting, type ChunkSplitterVersion, type ConfigPassphraseStore, type CouchDBConnection, type E2EEAlgorithm, type EncryptionSettings, type HashAlgorithm, type HasSettings, type LocalDBSettings, type ObsidianLiveSyncSettings, type P2PConnectionInfo, type P2PSyncSetting, type PluginSyncSettingEntry, type RemoteDBSettings, type RemoteType, type RemoteTypeSettings, type SYNC_MODE } from "./models/setting.type.js";
|
|
11
11
|
import { ChunkAlgorithmNames, ChunkAlgorithms, CURRENT_SETTING_VERSION, E2EEAlgorithmNames, E2EEAlgorithms, HashAlgorithms, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, RemoteTypes, SETTING_VERSION_INITIAL, SETTING_VERSION_SUPPORT_CASE_INSENSITIVE, MODE_AUTOMATIC, MODE_PAUSED, MODE_SELECTIVE, MODE_SHINY, P2PConnectionPaths, P2PMessageSizePresets, type P2PConnectionPath } from "./models/setting.const.js";
|
|
12
|
-
import { hasValidP2PTurnServerUrl, isValidP2PTurnServerUrl, normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes, splitP2PTurnServerUrls } from "./models/setting.p2p.js";
|
|
12
|
+
import { hasManagedP2PTurnConfiguration, hasP2PTurnConfiguration, hasValidP2PTurnServerUrl, omitP2PRuntimeSettings, isValidP2PTurnServerUrl, normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes, splitP2PTurnServerUrls } from "./models/setting.p2p.js";
|
|
13
13
|
import { PREFERRED_BASE, PREFERRED_JOURNAL_SYNC, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED } from "./models/setting.const.preferred.js";
|
|
14
14
|
import { P2P_DEFAULT_SETTINGS, DEFAULT_SETTINGS, NEW_VAULT_SETTINGS, SETTINGS_SCHEMA_DEFAULTS, createNewVaultSettings } from "./models/setting.const.defaults.js";
|
|
15
15
|
import { prepareSettingsForLoad, SettingsMigrationReviewCodes, type PreparedSettings, type SettingsMigrationReviewCode, type SettingsMigrationReviewReason, type SettingsMigrationState } from "./models/setting.lifecycle.js";
|
|
@@ -39,7 +39,7 @@ export { SETTING_VERSION_INITIAL, SETTING_VERSION_SUPPORT_CASE_INSENSITIVE, CURR
|
|
|
39
39
|
export type { BucketSyncSetting, LocalDBSettings };
|
|
40
40
|
export { RemoteTypes, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, P2PConnectionPaths, P2PMessageSizePresets, type RemoteType, AutoAccepting, };
|
|
41
41
|
export type { P2PConnectionInfo, P2PConnectionPath, P2PSyncSetting };
|
|
42
|
-
export { hasValidP2PTurnServerUrl, isValidP2PTurnServerUrl, normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes, splitP2PTurnServerUrls, };
|
|
42
|
+
export { hasManagedP2PTurnConfiguration, hasP2PTurnConfiguration, hasValidP2PTurnServerUrl, omitP2PRuntimeSettings, isValidP2PTurnServerUrl, normaliseP2PConnectionPath, normaliseP2PMaxWirePayloadBytes, splitP2PTurnServerUrls, };
|
|
43
43
|
export { P2P_DEFAULT_SETTINGS };
|
|
44
44
|
export type { RemoteTypeSettings };
|
|
45
45
|
export { E2EEAlgorithmNames, E2EEAlgorithms, type E2EEAlgorithm };
|
|
@@ -116,4 +116,4 @@ export { DOCID_SYNC_PARAMETERS };
|
|
|
116
116
|
export { DOCID_JOURNAL_SYNC_PARAMETERS };
|
|
117
117
|
export type { SyncParameters };
|
|
118
118
|
export { DEFAULT_SYNC_PARAMETERS };
|
|
119
|
-
export { SETTING_KEY_P2P_DEVICE_NAME, configURIBase, configURIBaseQR, SuffixDatabaseName, ExtraSuffixIndexedDB };
|
|
119
|
+
export { SETTING_KEY_P2P_DEVICE_NAME, configURIBase, configURIBaseQR, SuffixDatabaseName, ExtraSuffixIndexedDB, };
|
package/dist/common/types.js
CHANGED
|
@@ -64,7 +64,10 @@ import {
|
|
|
64
64
|
P2PMessageSizePresets
|
|
65
65
|
} from "./models/setting.const.js";
|
|
66
66
|
import {
|
|
67
|
+
hasManagedP2PTurnConfiguration,
|
|
68
|
+
hasP2PTurnConfiguration,
|
|
67
69
|
hasValidP2PTurnServerUrl,
|
|
70
|
+
omitP2PRuntimeSettings,
|
|
68
71
|
isValidP2PTurnServerUrl,
|
|
69
72
|
normaliseP2PConnectionPath,
|
|
70
73
|
normaliseP2PMaxWirePayloadBytes,
|
|
@@ -236,11 +239,14 @@ export {
|
|
|
236
239
|
configURIBaseQR,
|
|
237
240
|
configurationNames,
|
|
238
241
|
createNewVaultSettings,
|
|
242
|
+
hasManagedP2PTurnConfiguration,
|
|
243
|
+
hasP2PTurnConfiguration,
|
|
239
244
|
hasValidP2PTurnServerUrl,
|
|
240
245
|
isMetaEntry,
|
|
241
246
|
isValidP2PTurnServerUrl,
|
|
242
247
|
normaliseP2PConnectionPath,
|
|
243
248
|
normaliseP2PMaxWirePayloadBytes,
|
|
249
|
+
omitP2PRuntimeSettings,
|
|
244
250
|
prepareSettingsForLoad,
|
|
245
251
|
splitP2PTurnServerUrls,
|
|
246
252
|
statusDisplay
|
package/dist/common/utils.js
CHANGED
|
@@ -462,7 +462,10 @@ function pickP2PSyncSettings(setting) {
|
|
|
462
462
|
P2P_turnUsername: setting.P2P_turnUsername,
|
|
463
463
|
P2P_turnCredential: setting.P2P_turnCredential,
|
|
464
464
|
P2P_maxWirePayloadBytes: normaliseP2PMaxWirePayloadBytes(setting.P2P_maxWirePayloadBytes),
|
|
465
|
-
P2P_connectionPath: normaliseP2PConnectionPath(setting.P2P_connectionPath)
|
|
465
|
+
P2P_connectionPath: normaliseP2PConnectionPath(setting.P2P_connectionPath),
|
|
466
|
+
P2P_managedType: setting.P2P_managedType,
|
|
467
|
+
P2P_managedId: setting.P2P_managedId,
|
|
468
|
+
P2P_managedToken: setting.P2P_managedToken
|
|
466
469
|
};
|
|
467
470
|
}
|
|
468
471
|
function wrapByDefault(func, onError) {
|
|
@@ -2,7 +2,10 @@ import type { FilePathWithPrefix, LoadedEntry, MetaEntry, UXFileInfo, UXFileInfo
|
|
|
2
2
|
export interface DatabaseFileAccess {
|
|
3
3
|
delete: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string) => Promise<boolean>;
|
|
4
4
|
store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Store a child of the exact supplied revision, even after that branch has advanced.
|
|
7
|
+
* An undefined base uses the current winner. Return the created revision, or `false`.
|
|
8
|
+
*/
|
|
6
9
|
storeWithBaseRevision: (file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean) => Promise<string | false>;
|
|
7
10
|
/**
|
|
8
11
|
* Store a file as a child of an exact revision only while that revision remains a live leaf.
|
|
@@ -14,8 +17,18 @@ export interface DatabaseFileAccess {
|
|
|
14
17
|
* write without creating a Metadata successor.
|
|
15
18
|
*/
|
|
16
19
|
storeWithLiveBaseRevision: (file: UXFileInfo, baseRevision: string, skipCheck?: boolean) => Promise<string | false>;
|
|
20
|
+
/**
|
|
21
|
+
* Preserve content of unknown ancestry as a fresh parentless revision of the same document.
|
|
22
|
+
* Existing branches remain intact; no historical byte match is used as the parent.
|
|
23
|
+
* Return the created revision, or `false` when no Metadata revision was stored.
|
|
24
|
+
*/
|
|
25
|
+
storeIndependentRevision: (file: UXFileInfo, skipCheck?: boolean) => Promise<string | false>;
|
|
17
26
|
storeAsConflictedRevision: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<boolean>;
|
|
18
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Store a sibling of `currentRev` below its parent and return the created revision.
|
|
29
|
+
* Return `false` if that parent is unavailable. Use `storeIndependentRevision`
|
|
30
|
+
* when the storage content has no known ancestor.
|
|
31
|
+
*/
|
|
19
32
|
storeAsConflictedRevisionWithResult: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<string | false>;
|
|
20
33
|
/** Store a user deletion as a visible logical-deletion child of an exact revision. */
|
|
21
34
|
storeDeletionWithBaseRevision: (file: UXFileInfoStub | FilePathWithPrefix, baseRevision: string) => Promise<string | false>;
|
|
@@ -24,9 +37,24 @@ export interface DatabaseFileAccess {
|
|
|
24
37
|
hasContentInRevisionHistory: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string) => Promise<boolean>;
|
|
25
38
|
/** Return every available revision whose content exactly matches the supplied bytes. */
|
|
26
39
|
findContentRevisions: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string) => Promise<string[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Return current, non-deleted leaf revisions whose decoded content matches the supplied bytes.
|
|
42
|
+
* Include the winning and conflicting leaves, but never their ancestors. Read only locally
|
|
43
|
+
* available Chunks, without requesting remote data or waiting for missing Chunks to arrive.
|
|
44
|
+
*
|
|
45
|
+
* A unique match can recover provenance; several matches avoid a duplicate write but leave
|
|
46
|
+
* its origin ambiguous. An empty result means no match was confirmed, including when data
|
|
47
|
+
* could not be read. This read neither records provenance nor reserves a leaf for a later write.
|
|
48
|
+
*/
|
|
49
|
+
findLiveContentRevisions: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer) => Promise<string[]>;
|
|
27
50
|
fetch: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, waitForReady?: boolean, skipCheck?: boolean) => Promise<UXFileInfo | false>;
|
|
28
51
|
fetchEntryFromMeta: (meta: MetaEntry, waitForReady?: boolean, skipCheck?: boolean) => Promise<LoadedEntry | false>;
|
|
29
52
|
fetchEntryMeta: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, skipCheck?: boolean) => Promise<MetaEntry | false>;
|
|
30
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Load a revision's content, or return `false` when it cannot be loaded.
|
|
55
|
+
* `localOnly` overrides `waitForReady`: missing Chunks cause failure without remote requests
|
|
56
|
+
* or waiting for delivery, so provenance checks can use only content already available locally.
|
|
57
|
+
*/
|
|
58
|
+
fetchEntry: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, waitForReady?: boolean, skipCheck?: boolean, localOnly?: boolean) => Promise<LoadedEntry | false>;
|
|
31
59
|
getConflictedRevs: (file: UXFileInfoStub | FilePathWithPrefix) => Promise<string[]>;
|
|
32
60
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { FilePathWithPrefix } from "../common/types.js";
|
|
2
2
|
import type { SimpleStore } from "../common/utils.js";
|
|
3
3
|
export type FileReflectionProvenanceRecord = {
|
|
4
|
-
/** Exact
|
|
4
|
+
/** Exact revision most recently saved from or reflected in storage; the base for subsequent edits. */
|
|
5
5
|
revision: string;
|
|
6
|
-
/** Raw modification time
|
|
6
|
+
/** Raw modification time of the saved snapshot or storage observed after reflection. */
|
|
7
7
|
observedStorageMtime?: number;
|
|
8
8
|
};
|
|
9
9
|
export interface FileReflectionProvenance {
|
|
@@ -30,10 +30,12 @@ export declare class EntryManager {
|
|
|
30
30
|
isTargetFile(filenameSrc: string): boolean;
|
|
31
31
|
prepareChunk(piece: string): Promise<GeneratedChunk>;
|
|
32
32
|
getDBEntryMeta(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, includeDeleted?: boolean): Promise<false | LoadedEntry>;
|
|
33
|
-
getDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean): Promise<false | LoadedEntry>;
|
|
34
|
-
getDBEntryFromMeta(meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean): Promise<false | LoadedEntry>;
|
|
33
|
+
getDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean, localOnly?: boolean): Promise<false | LoadedEntry>;
|
|
34
|
+
getDBEntryFromMeta(meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean, localOnly?: boolean): Promise<false | LoadedEntry>;
|
|
35
35
|
deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
|
|
36
36
|
storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
|
|
37
37
|
putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
|
|
38
38
|
putDBEntryWithLiveBaseRevision(note: SavingEntry, baseRevision: string, onlyChunks?: boolean): Promise<false | PouchDB.Core.Response>;
|
|
39
|
+
/** Store unknown ancestry as a fresh root; return the Metadata write result or `false`. */
|
|
40
|
+
putDBEntryAsIndependentRoot(note: SavingEntry): Promise<false | PouchDB.Core.Response>;
|
|
39
41
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
prepareChunk,
|
|
11
11
|
putDBEntry,
|
|
12
12
|
putDBEntryWithLiveBaseRevision,
|
|
13
|
+
putDBEntryAsIndependentRoot,
|
|
13
14
|
storeDeletionByPathAtRevision
|
|
14
15
|
} from "./EntryManagerImpls.js";
|
|
15
16
|
class EntryManager {
|
|
@@ -57,11 +58,11 @@ class EntryManager {
|
|
|
57
58
|
async getDBEntryMeta(path, opt, includeDeleted = false) {
|
|
58
59
|
return await getDBEntryMetaByPath(this.serviceHost, this, path, opt, includeDeleted);
|
|
59
60
|
}
|
|
60
|
-
async getDBEntry(path, opt, dump = false, waitForReady = true, includeDeleted = false) {
|
|
61
|
-
return await getDBEntryByPath(this.serviceHost, this, path, opt, dump, waitForReady, includeDeleted);
|
|
61
|
+
async getDBEntry(path, opt, dump = false, waitForReady = true, includeDeleted = false, localOnly = false) {
|
|
62
|
+
return await getDBEntryByPath(this.serviceHost, this, path, opt, dump, waitForReady, includeDeleted, localOnly);
|
|
62
63
|
}
|
|
63
|
-
async getDBEntryFromMeta(meta, dump = false, waitForReady = true) {
|
|
64
|
-
return await getDBEntryFromMeta(this.serviceHost, this, meta, dump, waitForReady);
|
|
64
|
+
async getDBEntryFromMeta(meta, dump = false, waitForReady = true, localOnly = false) {
|
|
65
|
+
return await getDBEntryFromMeta(this.serviceHost, this, meta, dump, waitForReady, localOnly);
|
|
65
66
|
}
|
|
66
67
|
async deleteDBEntry(path, opt) {
|
|
67
68
|
return await deleteDBEntryByPath(this.serviceHost, this, path, opt);
|
|
@@ -75,6 +76,10 @@ class EntryManager {
|
|
|
75
76
|
async putDBEntryWithLiveBaseRevision(note, baseRevision, onlyChunks) {
|
|
76
77
|
return await putDBEntryWithLiveBaseRevision(this.serviceHost, this, note, baseRevision, onlyChunks);
|
|
77
78
|
}
|
|
79
|
+
/** Store unknown ancestry as a fresh root; return the Metadata write result or `false`. */
|
|
80
|
+
async putDBEntryAsIndependentRoot(note) {
|
|
81
|
+
return await putDBEntryAsIndependentRoot(this.serviceHost, this, note);
|
|
82
|
+
}
|
|
78
83
|
}
|
|
79
84
|
export {
|
|
80
85
|
EntryManager
|
|
@@ -20,20 +20,28 @@ export declare function putDBEntry(host: NecessaryServicesInterfaces<"path" | "s
|
|
|
20
20
|
* base has already been advanced, so the caller receives `false` and can retry from fresh state.
|
|
21
21
|
*/
|
|
22
22
|
export declare function putDBEntryWithLiveBaseRevision(host: NecessaryServicesInterfaces<"path" | "setting", never>, managers: NecessaryManagers<"localDatabase" | "chunkManager" | "hashManager" | "splitter">, note: SavingEntry, baseRevision: string, onlyChunks?: boolean): Promise<false | PouchDB.Core.Response>;
|
|
23
|
+
/**
|
|
24
|
+
* Preserve content with unknown ancestry as a fresh parentless revision of the same document.
|
|
25
|
+
* Store the body as Chunks as usual, then insert a Metadata root with a random revision ID using
|
|
26
|
+
* `new_edits: false`. This retains existing branches without reusing an identical historical root.
|
|
27
|
+
* Return the Metadata write result, or `false` when the entry cannot be stored.
|
|
28
|
+
*/
|
|
29
|
+
export declare function putDBEntryAsIndependentRoot(host: NecessaryServicesInterfaces<"path" | "setting", never>, managers: NecessaryManagers<"localDatabase" | "chunkManager" | "hashManager" | "splitter">, note: SavingEntry): Promise<false | PouchDB.Core.Response>;
|
|
23
30
|
export declare function isTargetFile(host: NecessaryServicesInterfaces<"setting", never>, filenameSrc: string): boolean;
|
|
24
31
|
export declare function prepareChunk({ chunkManager, hashManager }: NecessaryManagers<"chunkManager" | "hashManager">, piece: string): Promise<GeneratedChunk>;
|
|
25
32
|
export declare function getDBEntryMetaByPath(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase }: NecessaryManagers<"localDatabase">, path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, includeDeleted?: boolean): Promise<false | LoadedEntry>;
|
|
26
33
|
export declare function isLegacyNote(meta: LoadedEntry | MetaEntry): boolean;
|
|
27
34
|
export declare function canUseOnDemandChunking(settings: ObsidianLiveSyncSettings): boolean;
|
|
28
35
|
/**
|
|
29
|
-
* Decide how to retrieve chunks based on settings and
|
|
36
|
+
* Decide how to retrieve chunks based on settings and the caller's read policy.
|
|
30
37
|
* `waitForReady` allows an already-observable finite delivery lifecycle to finish.
|
|
38
|
+
* `localOnly` overrides both settings and waiting for provenance comparisons.
|
|
31
39
|
*/
|
|
32
|
-
export declare function computeChunkRetrievalMethod(waitForReady: boolean, settings: ObsidianLiveSyncSettings): {
|
|
40
|
+
export declare function computeChunkRetrievalMethod(waitForReady: boolean, settings: ObsidianLiveSyncSettings, localOnly?: boolean): {
|
|
33
41
|
waitForDelivery: boolean;
|
|
34
42
|
preventRemoteRequest: boolean;
|
|
35
43
|
};
|
|
36
|
-
export declare function getDBEntryFromMeta(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase, chunkManager }: NecessaryManagers<"localDatabase" | "chunkManager">, meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean): Promise<false | (import("../../common/types.js").DatabaseEntry & EntryBase & import("../../common/types.js").EntryWithEden & {
|
|
44
|
+
export declare function getDBEntryFromMeta(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase, chunkManager }: NecessaryManagers<"localDatabase" | "chunkManager">, meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean, localOnly?: boolean): Promise<false | (import("../../common/types.js").DatabaseEntry & EntryBase & import("../../common/types.js").EntryWithEden & {
|
|
37
45
|
path: FilePathWithPrefix;
|
|
38
46
|
data: string | string[];
|
|
39
47
|
type: import("../../common/models/db.type.js").EntryTypes["NOTE_LEGACY"];
|
|
@@ -55,7 +63,7 @@ export declare function getDBEntryFromMeta(host: NecessaryServicesInterfaces<"pa
|
|
|
55
63
|
data: string | string[];
|
|
56
64
|
datatype: import("../../common/types.js").EntryTypeNotes;
|
|
57
65
|
} & PouchDB.Core.IdMeta)>;
|
|
58
|
-
export declare function getDBEntryByPath(host: NecessaryServicesInterfaces<"path" | "setting", never>, managers: NecessaryManagers<"localDatabase" | "chunkManager">, path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean): Promise<false | (import("../../common/types.js").DatabaseEntry & EntryBase & import("../../common/types.js").EntryWithEden & {
|
|
66
|
+
export declare function getDBEntryByPath(host: NecessaryServicesInterfaces<"path" | "setting", never>, managers: NecessaryManagers<"localDatabase" | "chunkManager">, path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean, localOnly?: boolean): Promise<false | (import("../../common/types.js").DatabaseEntry & EntryBase & import("../../common/types.js").EntryWithEden & {
|
|
59
67
|
path: FilePathWithPrefix;
|
|
60
68
|
data: string | string[];
|
|
61
69
|
type: import("../../common/models/db.type.js").EntryTypes["NOTE_LEGACY"];
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "../../common/types.js";
|
|
7
7
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
|
8
8
|
import { createTextBlob, getFileRegExp, isTextBlob } from "../../common/utils.js";
|
|
9
|
+
import { compatGlobal } from "../../common/coreEnvFunctions.js";
|
|
9
10
|
import { isErrorOfMissingDoc } from "../../pouchdb/utils_couchdb.js";
|
|
10
11
|
import { stripAllPrefixes } from "../../string_and_binary/path.js";
|
|
11
12
|
import { ICHeader, ICXHeader, PSCHeader } from "../../common/models/fileaccess.const.js";
|
|
@@ -126,6 +127,9 @@ async function putDBEntryWithLiveBaseRevision(host, managers, note, baseRevision
|
|
|
126
127
|
throw ex;
|
|
127
128
|
}
|
|
128
129
|
}
|
|
130
|
+
async function putDBEntryAsIndependentRoot(host, managers, note) {
|
|
131
|
+
return await putDBEntryInternal(host, managers, note, false, { mode: "independent-root" });
|
|
132
|
+
}
|
|
129
133
|
async function putDBEntryInternal(host, managers, note, onlyChunks, revisionTarget) {
|
|
130
134
|
const { localDatabase, chunkManager, splitter } = managers;
|
|
131
135
|
const filename = host.services.path.id2path(note._id, note);
|
|
@@ -163,9 +167,9 @@ async function putDBEntryInternal(host, managers, note, onlyChunks, revisionTarg
|
|
|
163
167
|
eden: {}
|
|
164
168
|
};
|
|
165
169
|
return await serialized("file:" + filename, async () => {
|
|
166
|
-
if (revisionTarget.mode
|
|
170
|
+
if (revisionTarget.mode === "force-base" || revisionTarget.mode === "live-base") {
|
|
167
171
|
newDoc._rev = revisionTarget.baseRevision;
|
|
168
|
-
} else {
|
|
172
|
+
} else if (revisionTarget.mode === "latest") {
|
|
169
173
|
try {
|
|
170
174
|
const old = await localDatabase.get(newDoc._id);
|
|
171
175
|
newDoc._rev = old._rev;
|
|
@@ -176,6 +180,15 @@ async function putDBEntryInternal(host, managers, note, onlyChunks, revisionTarg
|
|
|
176
180
|
}
|
|
177
181
|
}
|
|
178
182
|
}
|
|
183
|
+
if (revisionTarget.mode === "independent-root") {
|
|
184
|
+
const randomBytes = compatGlobal.crypto.getRandomValues(new Uint8Array(16));
|
|
185
|
+
const rootId = Array.from(randomBytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
186
|
+
newDoc._rev = `1-${rootId}`;
|
|
187
|
+
const imported = { ...newDoc, _revisions: { start: 1, ids: [rootId] } };
|
|
188
|
+
await localDatabase.bulkDocs([imported], { new_edits: false });
|
|
189
|
+
const stored = await localDatabase.get(newDoc._id, { rev: newDoc._rev });
|
|
190
|
+
return stored._rev === newDoc._rev ? { id: newDoc._id, ok: true, rev: newDoc._rev } : false;
|
|
191
|
+
}
|
|
179
192
|
const r = revisionTarget.mode === "live-base" ? await localDatabase.put(newDoc) : await localDatabase.put(newDoc, { force: true });
|
|
180
193
|
if (r.ok) {
|
|
181
194
|
return r;
|
|
@@ -324,7 +337,10 @@ function canFetchRemotely(settings) {
|
|
|
324
337
|
}
|
|
325
338
|
return true;
|
|
326
339
|
}
|
|
327
|
-
function computeChunkRetrievalMethod(waitForReady, settings) {
|
|
340
|
+
function computeChunkRetrievalMethod(waitForReady, settings, localOnly = false) {
|
|
341
|
+
if (localOnly) {
|
|
342
|
+
return { waitForDelivery: false, preventRemoteRequest: true };
|
|
343
|
+
}
|
|
328
344
|
const isOnDemandFetchEnabled = canFetchRemotely(settings);
|
|
329
345
|
if (!waitForReady) {
|
|
330
346
|
if (isOnDemandFetchEnabled) {
|
|
@@ -343,7 +359,7 @@ function computeChunkRetrievalMethod(waitForReady, settings) {
|
|
|
343
359
|
preventRemoteRequest: !isOnDemandFetchEnabled
|
|
344
360
|
};
|
|
345
361
|
}
|
|
346
|
-
async function respondEntryFromMeta({ localDatabase, chunkManager }, settings, filename, meta, dump, waitForReady) {
|
|
362
|
+
async function respondEntryFromMeta({ localDatabase, chunkManager }, settings, filename, meta, dump, waitForReady, localOnly = false) {
|
|
347
363
|
const dispFilename = stripAllPrefixes(filename);
|
|
348
364
|
const deleted = meta.deleted ?? meta._deleted ?? void 0;
|
|
349
365
|
if (dump) {
|
|
@@ -371,7 +387,7 @@ async function respondEntryFromMeta({ localDatabase, chunkManager }, settings, f
|
|
|
371
387
|
}));
|
|
372
388
|
edenChunks = Object.fromEntries(chunks2.map((e) => [e._id, e]));
|
|
373
389
|
}
|
|
374
|
-
const { waitForDelivery, preventRemoteRequest } = computeChunkRetrievalMethod(waitForReady, settings);
|
|
390
|
+
const { waitForDelivery, preventRemoteRequest } = computeChunkRetrievalMethod(waitForReady, settings, localOnly);
|
|
375
391
|
const childrenKeys = [...meta.children];
|
|
376
392
|
const chunks = await chunkManager.read(
|
|
377
393
|
childrenKeys,
|
|
@@ -421,7 +437,7 @@ async function respondEntryFromMeta({ localDatabase, chunkManager }, settings, f
|
|
|
421
437
|
}
|
|
422
438
|
return false;
|
|
423
439
|
}
|
|
424
|
-
async function getDBEntryFromMeta(host, { localDatabase, chunkManager }, meta, dump = false, waitForReady = true) {
|
|
440
|
+
async function getDBEntryFromMeta(host, { localDatabase, chunkManager }, meta, dump = false, waitForReady = true, localOnly = false) {
|
|
425
441
|
const filename = host.services.path.id2path(meta._id, meta);
|
|
426
442
|
if (!isTargetFile(host, filename)) {
|
|
427
443
|
return false;
|
|
@@ -438,15 +454,16 @@ async function getDBEntryFromMeta(host, { localDatabase, chunkManager }, meta, d
|
|
|
438
454
|
filename,
|
|
439
455
|
meta,
|
|
440
456
|
dump,
|
|
441
|
-
waitForReady
|
|
457
|
+
waitForReady,
|
|
458
|
+
localOnly
|
|
442
459
|
);
|
|
443
460
|
}
|
|
444
461
|
return false;
|
|
445
462
|
}
|
|
446
|
-
async function getDBEntryByPath(host, managers, path, opt, dump = false, waitForReady = true, includeDeleted = false) {
|
|
463
|
+
async function getDBEntryByPath(host, managers, path, opt, dump = false, waitForReady = true, includeDeleted = false, localOnly = false) {
|
|
447
464
|
const meta = await getDBEntryMetaByPath(host, managers, path, opt, includeDeleted);
|
|
448
465
|
if (meta) {
|
|
449
|
-
return await getDBEntryFromMeta(host, managers, meta, dump, waitForReady);
|
|
466
|
+
return await getDBEntryFromMeta(host, managers, meta, dump, waitForReady, localOnly);
|
|
450
467
|
} else {
|
|
451
468
|
return false;
|
|
452
469
|
}
|
|
@@ -540,6 +557,7 @@ export {
|
|
|
540
557
|
isTargetFile,
|
|
541
558
|
prepareChunk,
|
|
542
559
|
putDBEntry,
|
|
560
|
+
putDBEntryAsIndependentRoot,
|
|
543
561
|
putDBEntryWithLiveBaseRevision,
|
|
544
562
|
storeDeletionByPathAtRevision
|
|
545
563
|
};
|