@vrtmrz/livesync-commonlib 0.2.2 → 0.3.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 +86 -1
- package/dist/API/DirectFileManipulatorV2.d.ts +7 -1
- package/dist/API/DirectFileManipulatorV2.js +46 -67
- package/dist/API/DirectFileWatchOwner.d.ts +24 -0
- package/dist/API/DirectFileWatchOwner.js +256 -0
- package/dist/API/processSetting.js +3 -3
- package/dist/ContentSplitter/ContentSplitterRabinKarp.js +2 -17
- package/dist/ContentSplitter/ContentSplitterV1.js +9 -19
- package/dist/ContentSplitter/ContentSplitterV2.js +9 -19
- package/dist/common/models/setting.type.d.ts +1 -1
- package/dist/common/models/shared.definition.configNames.js +1 -2
- package/dist/common/models/tweak.definition.d.ts +1 -1
- package/dist/common/pathNormalization.d.ts +2 -0
- package/dist/common/pathNormalization.js +7 -2
- package/dist/common/utils.d.ts +33 -3
- package/dist/common/utils.js +12 -44
- package/dist/common/utils.object.js +2 -1
- package/dist/concurrency/keyedPendingWork.d.ts +13 -0
- package/dist/concurrency/keyedPendingWork.js +27 -0
- package/dist/conflicts/conflictCheckQueue.d.ts +45 -0
- package/dist/conflicts/conflictCheckQueue.js +108 -0
- package/dist/conflicts.d.ts +3 -0
- package/dist/conflicts.js +4 -0
- package/dist/diagnostics/integrity.js +11 -12
- package/dist/interfaces/ServiceModule.d.ts +42 -24
- package/dist/interfaces/ServiceModule.js +4 -3
- package/dist/managers/ChunkFetcher.d.ts +3 -0
- package/dist/managers/ChunkFetcher.js +20 -13
- package/dist/managers/LiveSyncManagers.d.ts +2 -0
- package/dist/managers/LiveSyncManagers.js +1 -0
- package/dist/managers/StorageEventManager.d.ts +3 -3
- package/dist/managers/StorageEventManager.js +15 -6
- package/dist/managers/chunkFetchSource.d.ts +14 -0
- package/dist/managers/chunkFetchSource.js +12 -0
- package/dist/pouchdb/LiveSyncDBFunctions.js +5 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
- package/dist/pouchdb/LiveSyncLocalDB.js +14 -10
- package/dist/pouchdb/chunks.d.ts +16 -0
- package/dist/pouchdb/pathIdentityCompatibility.d.ts +8 -0
- package/dist/pouchdb/pathIdentityCompatibility.js +19 -1
- package/dist/pouchdb/pouchdb-browser.js +2 -84
- package/dist/pouchdb/pouchdb-http.js +2 -84
- package/dist/pouchdb/purgeMulti.d.ts +4 -0
- package/dist/pouchdb/purgeMulti.js +89 -0
- package/dist/replication/RemoteResource.d.ts +3 -0
- package/dist/replication/index.d.ts +2 -0
- package/dist/replication/index.js +2 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +1 -0
- package/dist/replication/journal/JournalSyncCore.js +41 -20
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +11 -3
- package/dist/replication/journal/LiveSyncJournalReplicator.js +123 -45
- package/dist/replication/trystero/P2PRoomSessionOwner.js +14 -13
- package/dist/replication/trystero/TrysteroReplicator.d.ts +7 -5
- package/dist/replication/trystero/TrysteroReplicator.js +101 -74
- package/dist/replication/trystero/TrysteroReplicator.pathIdentity.d.ts +11 -0
- package/dist/replication/trystero/TrysteroReplicator.pathIdentity.js +72 -0
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +3 -2
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +2 -0
- package/dist/replication/trystero/peerPatterns.d.ts +1 -0
- package/dist/replication/trystero/peerPatterns.js +6 -0
- package/dist/replication/trystero/useP2PReplicatorFeature.js +6 -2
- package/dist/rpc/RpcRoom.d.ts +5 -1
- package/dist/rpc/RpcRoom.js +80 -105
- package/dist/serviceFeatures/offlineScanner.d.ts +4 -4
- package/dist/serviceFeatures/offlineScanner.js +14 -15
- package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +2 -2
- package/dist/serviceFeatures/remoteConfig.d.ts +10 -5
- package/dist/serviceModules/Rebuilder.d.ts +1 -0
- package/dist/serviceModules/Rebuilder.js +33 -5
- package/dist/serviceModules/ServiceFileHandlerBase.js +2 -7
- package/dist/services/implements/injectable/InjectableConflictService.d.ts +5 -5
- package/dist/services/lib/HandlerUtils.d.ts +8 -6
- package/dist/services/lib/HandlerUtils.js +14 -0
- package/dist/worker/bgWorker.js +1 -1
- package/docs/conflict-resolution.md +30 -0
- package/docs/development.md +50 -0
- package/docs/proven-in-use.md +80 -0
- package/docs/releasing.md +4 -0
- package/docs/service-feature-composition.md +63 -10
- package/docs/unicode-path-identity.md +64 -3
- package/package.json +11 -3
|
@@ -327,20 +327,25 @@ class ChunkFetcher {
|
|
|
327
327
|
await this.resolveLocallyAvailableRetries(requestClaims);
|
|
328
328
|
let pendingIDs = await this.waitBeforeRemoteRequest(requestIDs, requestClaims, localCheckCompletionVersion);
|
|
329
329
|
if (pendingIDs.length === 0) return;
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
330
|
+
const requestCompletionVersion = this.finiteCompletionVersion;
|
|
331
|
+
let fetched;
|
|
332
|
+
if (this.options.source) {
|
|
333
|
+
fetched = await this.options.source.fetchChunks(pendingIDs);
|
|
334
|
+
} else {
|
|
335
|
+
const replicator = this.options.replicatorService.getActiveReplicator();
|
|
336
|
+
if (!replicator) {
|
|
337
|
+
Logger("No active replicator was found to request missing chunks.");
|
|
338
|
+
return;
|
|
339
339
|
}
|
|
340
|
-
|
|
340
|
+
if (!canFetchRemoteChunks(replicator)) {
|
|
341
|
+
Logger("The active replicator does not support fetching individual remote chunks.");
|
|
342
|
+
for (const chunkID of pendingIDs) {
|
|
343
|
+
this.chunkManager.emitEvent(EVENT_MISSING_CHUNK_REMOTE, chunkID);
|
|
344
|
+
}
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
fetched = await replicator.fetchRemoteChunks(pendingIDs, false);
|
|
341
348
|
}
|
|
342
|
-
const requestCompletionVersion = this.finiteCompletionVersion;
|
|
343
|
-
const fetched = await replicator.fetchRemoteChunks(pendingIDs, false);
|
|
344
349
|
pendingIDs = this.getActiveClaimIds(pendingIDs, requestClaims);
|
|
345
350
|
this.touchClaims(pendingIDs, requestClaims);
|
|
346
351
|
if (!fetched) {
|
|
@@ -354,7 +359,9 @@ class ChunkFetcher {
|
|
|
354
359
|
this.logFetchedChunks(pendingIDs, classification);
|
|
355
360
|
const { chunks, missingIds, invalidIds } = classification;
|
|
356
361
|
try {
|
|
357
|
-
if (chunks.length > 0) {
|
|
362
|
+
if (chunks.length > 0 && this.options.source) {
|
|
363
|
+
await this.options.source.storeChunks(chunks);
|
|
364
|
+
} else if (chunks.length > 0) {
|
|
358
365
|
Logger(`Writing fetched chunks (${chunks.length}) to the database...`);
|
|
359
366
|
const result = await this.chunkManager.write(
|
|
360
367
|
chunks,
|
|
@@ -2,6 +2,7 @@ import { type EntryDoc } from "../common/types.js";
|
|
|
2
2
|
import { ContentSplitter } from "../ContentSplitter/ContentSplitters.js";
|
|
3
3
|
import { ChangeManager } from "./ChangeManager.js";
|
|
4
4
|
import { ChunkFetcher } from "./ChunkFetcher.js";
|
|
5
|
+
import type { ChunkFetchSource } from "./chunkFetchSource.js";
|
|
5
6
|
import { ChunkManager } from "./ChunkManager.js";
|
|
6
7
|
import { ConflictManager } from "./ConflictManager.js";
|
|
7
8
|
import { EntryManager } from "./EntryManager/EntryManager.js";
|
|
@@ -15,6 +16,7 @@ export interface LiveSyncManagersOptions<TSettingService extends ISettingService
|
|
|
15
16
|
pathService: IPathService;
|
|
16
17
|
replicatorService: IReplicatorService;
|
|
17
18
|
APIService: IAPIService;
|
|
19
|
+
chunkFetchSource?: ChunkFetchSource;
|
|
18
20
|
}
|
|
19
21
|
export declare class LiveSyncManagers {
|
|
20
22
|
protected _pathService: IPathService;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type FileEventType, type FilePath, type UXFileInfoStub, type UXFolderInfo, type UXInternalFileInfoStub } from "../common/types.js";
|
|
2
2
|
import { type FileEventItem } from "../common/types.js";
|
|
3
|
+
import { type DeferredAction } from "octagonal-wheels/concurrency/deferredAction";
|
|
3
4
|
import type { IStorageAccessManager } from "../interfaces/StorageAccess.js";
|
|
4
5
|
import { type PromiseWithResolvers } from "octagonal-wheels/promises";
|
|
5
6
|
import { StorageEventManager, type FileEvent } from "../interfaces/StorageEventManager.js";
|
|
@@ -8,12 +9,11 @@ import type { SettingService } from "../services/base/SettingService.js";
|
|
|
8
9
|
import type { FileProcessingService } from "../services/base/FileProcessingService.js";
|
|
9
10
|
import { createInstanceLogFunction } from "../services/lib/logUtils.js";
|
|
10
11
|
import type { IStorageEventManagerAdapter } from "./adapters/index.js";
|
|
11
|
-
import { type CompatTimeoutHandle } from "../common/coreEnvFunctions.js";
|
|
12
12
|
type WaitInfo = {
|
|
13
13
|
since: number;
|
|
14
14
|
type: FileEventType;
|
|
15
15
|
canProceed: PromiseWithResolvers<boolean>;
|
|
16
|
-
|
|
16
|
+
deadline: DeferredAction;
|
|
17
17
|
event: FileEventItem;
|
|
18
18
|
};
|
|
19
19
|
declare const TYPE_SENTINEL_FLUSH = "SENTINEL_FLUSH";
|
|
@@ -88,7 +88,7 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
88
88
|
enqueue(newItem: FileEventItem): void;
|
|
89
89
|
private _triggerTakeSnapshot;
|
|
90
90
|
triggerTakeSnapshot: import("octagonal-wheels/function").ThrottledFunction<() => void>;
|
|
91
|
-
protected concurrentProcessing: import("octagonal-wheels/concurrency/
|
|
91
|
+
protected concurrentProcessing: import("octagonal-wheels/concurrency/semaphore").SemaphoreObject;
|
|
92
92
|
protected _waitingMap: Map<string, WaitInfo>;
|
|
93
93
|
private pendingOperations;
|
|
94
94
|
private releaseOperations;
|
|
@@ -10,10 +10,17 @@ import { delay, fireAndForget, isRemediationModeActive, throttle } from "../comm
|
|
|
10
10
|
import {} from "../common/types.js";
|
|
11
11
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
|
12
12
|
import { Semaphore } from "octagonal-wheels/concurrency/semaphore";
|
|
13
|
+
import {
|
|
14
|
+
createDeferredAction
|
|
15
|
+
} from "octagonal-wheels/concurrency/deferredAction";
|
|
13
16
|
import { promiseWithResolvers } from "octagonal-wheels/promises";
|
|
14
17
|
import { StorageEventManager } from "../interfaces/StorageEventManager.js";
|
|
15
18
|
import { createInstanceLogFunction } from "../services/lib/logUtils.js";
|
|
16
19
|
import { compatGlobal } from "../common/coreEnvFunctions.js";
|
|
20
|
+
const scheduleStorageTimeout = (callback, delayMs) => {
|
|
21
|
+
const handle = compatGlobal.setTimeout(callback, delayMs);
|
|
22
|
+
return () => compatGlobal.clearTimeout(handle);
|
|
23
|
+
};
|
|
17
24
|
const TYPE_SENTINEL_FLUSH = "SENTINEL_FLUSH";
|
|
18
25
|
class StorageEventManagerBase extends StorageEventManager {
|
|
19
26
|
_log;
|
|
@@ -311,7 +318,7 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
311
318
|
const waitInfo = this._waitingMap.get(key);
|
|
312
319
|
if (waitInfo) {
|
|
313
320
|
waitInfo.canProceed.resolve(true);
|
|
314
|
-
|
|
321
|
+
waitInfo.deadline.dispose();
|
|
315
322
|
this._waitingMap.delete(key);
|
|
316
323
|
}
|
|
317
324
|
this.triggerTakeSnapshot();
|
|
@@ -323,7 +330,7 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
323
330
|
const waitInfo = this._waitingMap.get(key);
|
|
324
331
|
if (waitInfo) {
|
|
325
332
|
waitInfo.canProceed.resolve(false);
|
|
326
|
-
|
|
333
|
+
waitInfo.deadline.dispose();
|
|
327
334
|
this._waitingMap.delete(key);
|
|
328
335
|
}
|
|
329
336
|
this.triggerTakeSnapshot();
|
|
@@ -355,16 +362,18 @@ class StorageEventManagerBase extends StorageEventManager {
|
|
|
355
362
|
LOG_LEVEL_DEBUG
|
|
356
363
|
);
|
|
357
364
|
}
|
|
365
|
+
const deadline = createDeferredAction(() => {
|
|
366
|
+
this._log(`Processing ${key}: Batch save timeout reached: ${event.type}`, LOG_LEVEL_DEBUG);
|
|
367
|
+
this._proceedWaiting(key);
|
|
368
|
+
}, scheduleStorageTimeout);
|
|
358
369
|
const waitInfo = {
|
|
359
370
|
since,
|
|
360
371
|
type: event.type,
|
|
361
372
|
event,
|
|
362
373
|
canProceed: resolver,
|
|
363
|
-
|
|
364
|
-
this._log(`Processing ${key}: Batch save timeout reached: ${event.type}`, LOG_LEVEL_DEBUG);
|
|
365
|
-
this._proceedWaiting(key);
|
|
366
|
-
}, nextDelay)
|
|
374
|
+
deadline
|
|
367
375
|
};
|
|
376
|
+
deadline.schedule(nextDelay);
|
|
368
377
|
this._waitingMap.set(key, waitInfo);
|
|
369
378
|
this.triggerTakeSnapshot();
|
|
370
379
|
return waitInfo;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DocumentID, EntryDoc, EntryLeaf } from "../common/types.js";
|
|
2
|
+
/** IO supplied by a host whose chunk source does not use the active replicator. */
|
|
3
|
+
export interface ChunkFetchSource {
|
|
4
|
+
/** Missing identifiers are omitted; false means that this request could not retrieve chunks. */
|
|
5
|
+
fetchChunks(ids: DocumentID[]): Promise<EntryLeaf[] | false>;
|
|
6
|
+
/** Accept fetched chunks before notifying waiting readers. */
|
|
7
|
+
storeChunks(chunks: EntryLeaf[]): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Read through the prepared direct database, including its decryption transforms.
|
|
11
|
+
* The source already owns these documents, so accepting them performs no write.
|
|
12
|
+
* Construct a new source for each database lifetime.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createDirectChunkFetchSource(database: PouchDB.Database<EntryDoc>): ChunkFetchSource;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
function createDirectChunkFetchSource(database) {
|
|
2
|
+
return {
|
|
3
|
+
async fetchChunks(ids) {
|
|
4
|
+
const result = await database.allDocs({ keys: ids, include_docs: true });
|
|
5
|
+
return result.rows.flatMap((row) => "doc" in row && row.doc ? [row.doc] : []);
|
|
6
|
+
},
|
|
7
|
+
storeChunks: () => Promise.resolve()
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
createDirectChunkFetchSource
|
|
12
|
+
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from "../common/types.js";
|
|
7
7
|
import { extractObject, isObjectDifferent, resolveWithIgnoreKnownError } from "../common/utils.js";
|
|
8
8
|
import { assessTweakCompatibility } from "../common/models/tweak.compatibility.js";
|
|
9
|
+
import { getPathNormalizationVersion } from "../common/pathNormalization.js";
|
|
9
10
|
import { usesEncryptedInternalMetadata } from "./remoteFeatureCompatibility.js";
|
|
10
11
|
function getEffectiveTweakValues(setting) {
|
|
11
12
|
return {
|
|
@@ -56,6 +57,10 @@ async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentV
|
|
|
56
57
|
if (tweakAssessment.entries.some(({ key, relation }) => key === "idDerivationVersion" && relation === "different")) {
|
|
57
58
|
return "ID_KEY_MISMATCH";
|
|
58
59
|
}
|
|
60
|
+
if (getPathNormalizationVersion(currentTweakValues) !== getPathNormalizationVersion(preferred_tweak)) {
|
|
61
|
+
recordTweakAssessment?.(tweakAssessment);
|
|
62
|
+
return ["MISMATCHED", preferred_tweak];
|
|
63
|
+
}
|
|
59
64
|
if (!setting.disableCheckingConfigMismatch) {
|
|
60
65
|
recordTweakAssessment?.(tweakAssessment);
|
|
61
66
|
if (tweakAssessment.alignment === "mismatched") {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type EntryDoc, type EntryLeaf, type Credential, type RemoteDBSettings, type DocumentID, type FilePathWithPrefix, type FilePath, type DatabaseEntry, type LoadedEntry, type MetaEntry, type SavingEntry, type diff_result_leaf } from "../common/types.js";
|
|
2
2
|
import type { LiveSyncEventHub } from "../hub/hub.js";
|
|
3
3
|
import { LiveSyncManagers } from "../managers/LiveSyncManagers.js";
|
|
4
|
+
import type { ChunkFetchSource } from "../managers/chunkFetchSource.js";
|
|
4
5
|
import type { AutoMergeResult } from "../managers/ConflictManager.js";
|
|
5
6
|
import type { IServiceHub } from "../services/base/IService.js";
|
|
6
7
|
import type { ServiceContext } from "../services/base/ServiceBase.js";
|
|
@@ -33,7 +34,12 @@ export type GeneratedChunk = {
|
|
|
33
34
|
id: DocumentID;
|
|
34
35
|
piece: string;
|
|
35
36
|
};
|
|
37
|
+
/** Optional host IO bound afresh to each prepared database instance. */
|
|
38
|
+
export interface LiveSyncLocalDBRuntimeOptions {
|
|
39
|
+
createChunkFetchSource?: (database: PouchDB.Database<EntryDoc>) => ChunkFetchSource;
|
|
40
|
+
}
|
|
36
41
|
export declare class LiveSyncLocalDB {
|
|
42
|
+
private readonly runtimeOptions;
|
|
37
43
|
auth: Credential;
|
|
38
44
|
dbname: string;
|
|
39
45
|
settings: RemoteDBSettings;
|
|
@@ -52,7 +58,7 @@ export declare class LiveSyncLocalDB {
|
|
|
52
58
|
/** Share owner retirement across every close path for one physical database lifetime. */
|
|
53
59
|
private activeReplicationClose?;
|
|
54
60
|
private databaseUnloadNotification?;
|
|
55
|
-
constructor(dbname: string, env: LiveSyncLocalDBEnv);
|
|
61
|
+
constructor(dbname: string, env: LiveSyncLocalDBEnv, runtimeOptions?: LiveSyncLocalDBRuntimeOptions);
|
|
56
62
|
/** Notify dependent services at most once for each physical database lifecycle. */
|
|
57
63
|
private notifyDatabaseUnload;
|
|
58
64
|
onunload(): void;
|
|
@@ -16,6 +16,18 @@ function getNoFromRev(rev) {
|
|
|
16
16
|
return parseInt(rev.split("-")[0]);
|
|
17
17
|
}
|
|
18
18
|
class LiveSyncLocalDB {
|
|
19
|
+
constructor(dbname, env, runtimeOptions = {}) {
|
|
20
|
+
this.runtimeOptions = runtimeOptions;
|
|
21
|
+
this.auth = {
|
|
22
|
+
username: "",
|
|
23
|
+
password: ""
|
|
24
|
+
};
|
|
25
|
+
this.dbname = dbname;
|
|
26
|
+
this.env = env;
|
|
27
|
+
this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
|
|
28
|
+
this.refreshSettings();
|
|
29
|
+
}
|
|
30
|
+
runtimeOptions;
|
|
19
31
|
auth;
|
|
20
32
|
dbname;
|
|
21
33
|
settings;
|
|
@@ -47,16 +59,6 @@ class LiveSyncLocalDB {
|
|
|
47
59
|
/** Share owner retirement across every close path for one physical database lifetime. */
|
|
48
60
|
activeReplicationClose;
|
|
49
61
|
databaseUnloadNotification;
|
|
50
|
-
constructor(dbname, env) {
|
|
51
|
-
this.auth = {
|
|
52
|
-
username: "",
|
|
53
|
-
password: ""
|
|
54
|
-
};
|
|
55
|
-
this.dbname = dbname;
|
|
56
|
-
this.env = env;
|
|
57
|
-
this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
|
|
58
|
-
this.refreshSettings();
|
|
59
|
-
}
|
|
60
62
|
/** Notify dependent services at most once for each physical database lifecycle. */
|
|
61
63
|
notifyDatabaseUnload() {
|
|
62
64
|
if (!this.databaseUnloadNotification) {
|
|
@@ -170,6 +172,7 @@ class LiveSyncLocalDB {
|
|
|
170
172
|
this.databaseUnloadNotification = void 0;
|
|
171
173
|
const manager = new LiveSyncManagers({
|
|
172
174
|
database: this.localDatabase,
|
|
175
|
+
chunkFetchSource: this.runtimeOptions?.createChunkFetchSource?.(this.localDatabase),
|
|
173
176
|
APIService: this.env.services.API,
|
|
174
177
|
pathService: this.env.services.path,
|
|
175
178
|
replicatorService: this.env.services.replicator,
|
|
@@ -185,6 +188,7 @@ class LiveSyncLocalDB {
|
|
|
185
188
|
this._log("Database initialisation was rejected by a required module.", LOG_LEVEL_NOTICE);
|
|
186
189
|
return false;
|
|
187
190
|
}
|
|
191
|
+
manager.changeManager.restartWatch();
|
|
188
192
|
this._log("Opening Database...");
|
|
189
193
|
this._log("Database info", LOG_LEVEL_VERBOSE);
|
|
190
194
|
this._log(JSON.stringify(await openedDatabase.info(), null, 2), LOG_LEVEL_VERBOSE);
|
package/dist/pouchdb/chunks.d.ts
CHANGED
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
import type { CouchDBConnection } from "../common/types.js";
|
|
2
2
|
export declare function purgeUnreferencedChunks(db: PouchDB.Database, dryRun: boolean, connSetting?: CouchDBConnection, performCompact?: boolean): Promise<number>;
|
|
3
|
+
/**
|
|
4
|
+
* Copy chunks missing from the destination, preserving their source revisions.
|
|
5
|
+
*
|
|
6
|
+
* @deprecated Obsolete purge-based cleanup compatibility helper. Retained for
|
|
7
|
+
* existing package consumers; current Self-hosted LiveSync no longer calls it.
|
|
8
|
+
* Garbage Collection V3 propagates ordinary deletion revisions instead.
|
|
9
|
+
*/
|
|
3
10
|
export declare function transferChunks(key: string, label: string, dbFrom: PouchDB.Database, dbTo: PouchDB.Database, items: {
|
|
4
11
|
id: string;
|
|
5
12
|
rev: string;
|
|
6
13
|
}[]): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Complement chunks between databases which were separately purged.
|
|
16
|
+
* @deprecated Obsolete cleanup compatibility wrapper around transferChunks.
|
|
17
|
+
* Current Self-hosted LiveSync uses its standard locked-remote recovery flow.
|
|
18
|
+
*/
|
|
7
19
|
export declare function balanceChunkPurgedDBs(local: PouchDB.Database, remote: PouchDB.Database): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* @deprecated Obsolete transferChunks compatibility wrapper with no callers in
|
|
22
|
+
* the current Commonlib or Self-hosted LiveSync source trees.
|
|
23
|
+
*/
|
|
8
24
|
export declare function fetchAllUsedChunks(local: PouchDB.Database, remote: PouchDB.Database): Promise<void>;
|
|
9
25
|
export declare function purgeChunksLocal(db: PouchDB.Database, docs: {
|
|
10
26
|
id: string;
|
|
@@ -2,6 +2,14 @@ import { type EntryDoc } from "../common/types.js";
|
|
|
2
2
|
type PathIdentitySettings = {
|
|
3
3
|
pathNormalizationVersion?: number;
|
|
4
4
|
};
|
|
5
|
+
export type PathIdentityInspection = {
|
|
6
|
+
readonly status: "empty";
|
|
7
|
+
} | {
|
|
8
|
+
readonly status: "configured";
|
|
9
|
+
readonly pathNormalizationVersion: 0 | 1;
|
|
10
|
+
};
|
|
11
|
+
/** Read the existing declaration without changing a remote or declaring a new identity. */
|
|
12
|
+
export declare function inspectDatabasePathIdentity(database: PouchDB.Database<EntryDoc>): Promise<PathIdentityInspection>;
|
|
5
13
|
export declare function assertMatchingPathIdentityFeature(document: unknown, settings: PathIdentitySettings): void;
|
|
6
14
|
/** Refuse an implicit migration. A fresh database may declare its identity before its first Metadata write. */
|
|
7
15
|
export declare function assertDatabasePathIdentity(database: PouchDB.Database<EntryDoc>, settings: PathIdentitySettings, declare?: boolean): Promise<void>;
|
|
@@ -2,11 +2,28 @@ import { VERSIONING_DOCID } from "../common/types.js";
|
|
|
2
2
|
import { getPathNormalizationVersion } from "../common/pathNormalization.js";
|
|
3
3
|
import {
|
|
4
4
|
assessRemoteFeatureDocument,
|
|
5
|
+
describeRemoteFeatureRejection,
|
|
5
6
|
NFC_PATH_IDENTITY_FEATURE,
|
|
6
7
|
REMOTE_FEATURE_GENERATION
|
|
7
8
|
} from "./remoteFeatureCompatibility.js";
|
|
8
9
|
import { isErrorOfMissingDoc } from "./utils_couchdb.js";
|
|
9
10
|
import { bumpRemoteVersion } from "./negotiation.js";
|
|
11
|
+
async function inspectDatabasePathIdentity(database) {
|
|
12
|
+
let document;
|
|
13
|
+
try {
|
|
14
|
+
document = await database.get(VERSIONING_DOCID);
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (!isErrorOfMissingDoc(error)) throw error;
|
|
17
|
+
return (await database.info()).doc_count === 0 ? { status: "empty" } : { status: "configured", pathNormalizationVersion: 0 };
|
|
18
|
+
}
|
|
19
|
+
const assessment = assessRemoteFeatureDocument(document);
|
|
20
|
+
if (assessment.status === "older-generation") return { status: "configured", pathNormalizationVersion: 0 };
|
|
21
|
+
if (assessment.status !== "supported") throw new Error(describeRemoteFeatureRejection(assessment));
|
|
22
|
+
return {
|
|
23
|
+
status: "configured",
|
|
24
|
+
pathNormalizationVersion: assessment.usedFeatures.includes(NFC_PATH_IDENTITY_FEATURE) ? 1 : 0
|
|
25
|
+
};
|
|
26
|
+
}
|
|
10
27
|
function assertMatchingPathIdentityFeature(document, settings) {
|
|
11
28
|
const assessment = assessRemoteFeatureDocument(document);
|
|
12
29
|
const declared = assessment.status === "supported" && assessment.usedFeatures.includes(NFC_PATH_IDENTITY_FEATURE);
|
|
@@ -60,5 +77,6 @@ async function assertDatabasePathIdentity(database, settings, declare = false) {
|
|
|
60
77
|
}
|
|
61
78
|
export {
|
|
62
79
|
assertDatabasePathIdentity,
|
|
63
|
-
assertMatchingPathIdentityFeature
|
|
80
|
+
assertMatchingPathIdentityFeature,
|
|
81
|
+
inspectDatabasePathIdentity
|
|
64
82
|
};
|
|
@@ -6,91 +6,9 @@ import mapreduce from "pouchdb-mapreduce";
|
|
|
6
6
|
import replication from "pouchdb-replication";
|
|
7
7
|
import find from "pouchdb-find";
|
|
8
8
|
import transform from "transform-pouch";
|
|
9
|
-
import {
|
|
10
|
-
import { adapterFun } from "pouchdb-utils";
|
|
11
|
-
import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors";
|
|
12
|
-
import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task";
|
|
9
|
+
import { installPurgeMulti } from "./purgeMulti.js";
|
|
13
10
|
PouchDB.plugin(IDBPouch).plugin(INDEXEDDBPouch).plugin(HttpPouch).plugin(mapreduce).plugin(replication).plugin(find).plugin(transform);
|
|
14
|
-
|
|
15
|
-
return db.get("_local/purges").then(function(doc) {
|
|
16
|
-
for (const [docId, rev$$1] of docs) {
|
|
17
|
-
const purgeSeq = doc.purgeSeq + 1;
|
|
18
|
-
doc.purges.push({
|
|
19
|
-
docId,
|
|
20
|
-
rev: rev$$1,
|
|
21
|
-
purgeSeq
|
|
22
|
-
});
|
|
23
|
-
if (doc.purges.length > db.purged_infos_limit) {
|
|
24
|
-
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
|
25
|
-
}
|
|
26
|
-
doc.purgeSeq = purgeSeq;
|
|
27
|
-
}
|
|
28
|
-
return doc;
|
|
29
|
-
}).catch(function(err) {
|
|
30
|
-
if (err.status !== 404) {
|
|
31
|
-
throw err;
|
|
32
|
-
}
|
|
33
|
-
return {
|
|
34
|
-
_id: "_local/purges",
|
|
35
|
-
purges: docs.map(([docId, rev$$1], idx) => ({
|
|
36
|
-
docId,
|
|
37
|
-
rev: rev$$1,
|
|
38
|
-
purgeSeq: idx
|
|
39
|
-
})),
|
|
40
|
-
purgeSeq: docs.length
|
|
41
|
-
};
|
|
42
|
-
}).then(function(doc) {
|
|
43
|
-
return db.put(doc);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
PouchDB.prototype.purgeMulti = adapterFun(
|
|
47
|
-
"_purgeMulti",
|
|
48
|
-
function(docs, callback) {
|
|
49
|
-
if (typeof this._purge === "undefined") {
|
|
50
|
-
return callback(
|
|
51
|
-
//@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext.
|
|
52
|
-
createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.")
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
const self = this;
|
|
56
|
-
const tasks = docs.map(
|
|
57
|
-
(param) => () => new Promise((res, rej) => {
|
|
58
|
-
const [docId, rev$$1] = param;
|
|
59
|
-
self._getRevisionTree(docId, (error, revs) => {
|
|
60
|
-
if (error) {
|
|
61
|
-
return res([param, error]);
|
|
62
|
-
}
|
|
63
|
-
if (!revs) {
|
|
64
|
-
return res([param, createError(MISSING_DOC)]);
|
|
65
|
-
}
|
|
66
|
-
let path;
|
|
67
|
-
try {
|
|
68
|
-
path = findPathToLeaf(revs, rev$$1);
|
|
69
|
-
} catch (error2) {
|
|
70
|
-
return res([param, error2.message || error2]);
|
|
71
|
-
}
|
|
72
|
-
self._purge(docId, path, (error2, result) => {
|
|
73
|
-
if (error2) {
|
|
74
|
-
return res([param, error2]);
|
|
75
|
-
} else {
|
|
76
|
-
return res([param, result]);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
79
|
-
});
|
|
80
|
-
})
|
|
81
|
-
);
|
|
82
|
-
(async () => {
|
|
83
|
-
const ret = await mapAllTasksWithConcurrencyLimit(1, tasks);
|
|
84
|
-
const retAll = ret.map((e) => unwrapTaskResult(e));
|
|
85
|
-
await appendPurgeSeqs(
|
|
86
|
-
self,
|
|
87
|
-
retAll.filter((e) => "ok" in e[1]).map((e) => e[0])
|
|
88
|
-
);
|
|
89
|
-
const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]]));
|
|
90
|
-
return result;
|
|
91
|
-
})().then((result) => callback(void 0, result)).catch((error) => callback(error));
|
|
92
|
-
}
|
|
93
|
-
);
|
|
11
|
+
installPurgeMulti(PouchDB);
|
|
94
12
|
export {
|
|
95
13
|
PouchDB
|
|
96
14
|
};
|
|
@@ -4,91 +4,9 @@ import mapreduce from "pouchdb-mapreduce";
|
|
|
4
4
|
import replication from "pouchdb-replication";
|
|
5
5
|
import find from "pouchdb-find";
|
|
6
6
|
import transform from "transform-pouch";
|
|
7
|
-
import {
|
|
8
|
-
import { adapterFun } from "pouchdb-utils";
|
|
9
|
-
import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors";
|
|
10
|
-
import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task";
|
|
7
|
+
import { installPurgeMulti } from "./purgeMulti.js";
|
|
11
8
|
PouchDB.plugin(HttpPouch).plugin(mapreduce).plugin(replication).plugin(find).plugin(transform);
|
|
12
|
-
|
|
13
|
-
return db.get("_local/purges").then(function(doc) {
|
|
14
|
-
for (const [docId, rev$$1] of docs) {
|
|
15
|
-
const purgeSeq = doc.purgeSeq + 1;
|
|
16
|
-
doc.purges.push({
|
|
17
|
-
docId,
|
|
18
|
-
rev: rev$$1,
|
|
19
|
-
purgeSeq
|
|
20
|
-
});
|
|
21
|
-
if (doc.purges.length > db.purged_infos_limit) {
|
|
22
|
-
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
|
23
|
-
}
|
|
24
|
-
doc.purgeSeq = purgeSeq;
|
|
25
|
-
}
|
|
26
|
-
return doc;
|
|
27
|
-
}).catch(function(err) {
|
|
28
|
-
if (err.status !== 404) {
|
|
29
|
-
throw err;
|
|
30
|
-
}
|
|
31
|
-
return {
|
|
32
|
-
_id: "_local/purges",
|
|
33
|
-
purges: docs.map(([docId, rev$$1], idx) => ({
|
|
34
|
-
docId,
|
|
35
|
-
rev: rev$$1,
|
|
36
|
-
purgeSeq: idx
|
|
37
|
-
})),
|
|
38
|
-
purgeSeq: docs.length
|
|
39
|
-
};
|
|
40
|
-
}).then(function(doc) {
|
|
41
|
-
return db.put(doc);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
PouchDB.prototype.purgeMulti = adapterFun(
|
|
45
|
-
"_purgeMulti",
|
|
46
|
-
function(docs, callback) {
|
|
47
|
-
if (typeof this._purge === "undefined") {
|
|
48
|
-
return callback(
|
|
49
|
-
//@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext.
|
|
50
|
-
createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.")
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
const self = this;
|
|
54
|
-
const tasks = docs.map(
|
|
55
|
-
(param) => () => new Promise((res, rej) => {
|
|
56
|
-
const [docId, rev$$1] = param;
|
|
57
|
-
self._getRevisionTree(docId, (error, revs) => {
|
|
58
|
-
if (error) {
|
|
59
|
-
return res([param, error]);
|
|
60
|
-
}
|
|
61
|
-
if (!revs) {
|
|
62
|
-
return res([param, createError(MISSING_DOC)]);
|
|
63
|
-
}
|
|
64
|
-
let path;
|
|
65
|
-
try {
|
|
66
|
-
path = findPathToLeaf(revs, rev$$1);
|
|
67
|
-
} catch (error2) {
|
|
68
|
-
return res([param, error2.message || error2]);
|
|
69
|
-
}
|
|
70
|
-
self._purge(docId, path, (error2, result) => {
|
|
71
|
-
if (error2) {
|
|
72
|
-
return res([param, error2]);
|
|
73
|
-
} else {
|
|
74
|
-
return res([param, result]);
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
})
|
|
79
|
-
);
|
|
80
|
-
(async () => {
|
|
81
|
-
const ret = await mapAllTasksWithConcurrencyLimit(1, tasks);
|
|
82
|
-
const retAll = ret.map((e) => unwrapTaskResult(e));
|
|
83
|
-
await appendPurgeSeqs(
|
|
84
|
-
self,
|
|
85
|
-
retAll.filter((e) => "ok" in e[1]).map((e) => e[0])
|
|
86
|
-
);
|
|
87
|
-
const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]]));
|
|
88
|
-
return result;
|
|
89
|
-
})().then((result) => callback(void 0, result)).catch((error) => callback(error));
|
|
90
|
-
}
|
|
91
|
-
);
|
|
9
|
+
installPurgeMulti(PouchDB);
|
|
92
10
|
export {
|
|
93
11
|
PouchDB
|
|
94
12
|
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { findPathToLeaf } from "pouchdb-merge";
|
|
2
|
+
import { adapterFun } from "pouchdb-utils";
|
|
3
|
+
import { createError, MISSING_DOC, UNKNOWN_ERROR } from "pouchdb-errors";
|
|
4
|
+
import { mapAllTasksWithConcurrencyLimit, unwrapTaskResult } from "octagonal-wheels/concurrency/task";
|
|
5
|
+
function appendPurgeSeqs(db, docs) {
|
|
6
|
+
return db.get("_local/purges").then(function(doc) {
|
|
7
|
+
for (const [docId, rev$$1] of docs) {
|
|
8
|
+
const purgeSeq = doc.purgeSeq + 1;
|
|
9
|
+
doc.purges.push({
|
|
10
|
+
docId,
|
|
11
|
+
rev: rev$$1,
|
|
12
|
+
purgeSeq
|
|
13
|
+
});
|
|
14
|
+
if (doc.purges.length > db.purged_infos_limit) {
|
|
15
|
+
doc.purges.splice(0, doc.purges.length - db.purged_infos_limit);
|
|
16
|
+
}
|
|
17
|
+
doc.purgeSeq = purgeSeq;
|
|
18
|
+
}
|
|
19
|
+
return doc;
|
|
20
|
+
}).catch(function(err) {
|
|
21
|
+
if (err.status !== 404) {
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
_id: "_local/purges",
|
|
26
|
+
purges: docs.map(([docId, rev$$1], idx) => ({
|
|
27
|
+
docId,
|
|
28
|
+
rev: rev$$1,
|
|
29
|
+
purgeSeq: idx
|
|
30
|
+
})),
|
|
31
|
+
purgeSeq: docs.length
|
|
32
|
+
};
|
|
33
|
+
}).then(function(doc) {
|
|
34
|
+
return db.put(doc);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function installPurgeMulti(PouchDB) {
|
|
38
|
+
PouchDB.prototype.purgeMulti = adapterFun(
|
|
39
|
+
"_purgeMulti",
|
|
40
|
+
function(docs, callback) {
|
|
41
|
+
if (typeof this._purge === "undefined") {
|
|
42
|
+
return callback(
|
|
43
|
+
//@ts-ignore: this ts-ignore might be hiding a `this` bug where we don't have "this" conext.
|
|
44
|
+
createError(UNKNOWN_ERROR, "Purge is not implemented in the " + this.adapter + " adapter.")
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const self = this;
|
|
48
|
+
const tasks = docs.map(
|
|
49
|
+
(param) => () => new Promise((res, rej) => {
|
|
50
|
+
const [docId, rev$$1] = param;
|
|
51
|
+
self._getRevisionTree(docId, (error, revs) => {
|
|
52
|
+
if (error) {
|
|
53
|
+
return res([param, error]);
|
|
54
|
+
}
|
|
55
|
+
if (!revs) {
|
|
56
|
+
return res([param, createError(MISSING_DOC)]);
|
|
57
|
+
}
|
|
58
|
+
let path;
|
|
59
|
+
try {
|
|
60
|
+
path = findPathToLeaf(revs, rev$$1);
|
|
61
|
+
} catch (error2) {
|
|
62
|
+
return res([param, error2.message || error2]);
|
|
63
|
+
}
|
|
64
|
+
self._purge(docId, path, (error2, result) => {
|
|
65
|
+
if (error2) {
|
|
66
|
+
return res([param, error2]);
|
|
67
|
+
} else {
|
|
68
|
+
return res([param, result]);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
})
|
|
73
|
+
);
|
|
74
|
+
(async () => {
|
|
75
|
+
const ret = await mapAllTasksWithConcurrencyLimit(1, tasks);
|
|
76
|
+
const retAll = ret.map((e) => unwrapTaskResult(e));
|
|
77
|
+
await appendPurgeSeqs(
|
|
78
|
+
self,
|
|
79
|
+
retAll.filter((e) => "ok" in e[1]).map((e) => e[0])
|
|
80
|
+
);
|
|
81
|
+
const result = Object.fromEntries(retAll.map((e) => [e[0][0], e[1]]));
|
|
82
|
+
return result;
|
|
83
|
+
})().then((result) => callback(void 0, result)).catch((error) => callback(error));
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
export {
|
|
88
|
+
installPurgeMulti
|
|
89
|
+
};
|