@vrtmrz/livesync-commonlib 0.1.44 → 0.2.1
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 +80 -4
- package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
- package/dist/API/DirectFileManipulatorV2.js +3 -0
- package/dist/API/processSetting.d.ts +1 -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.defaults.js +1 -0
- package/dist/common/models/setting.const.preferred.js +1 -3
- package/dist/common/models/setting.const.qr.js +1 -0
- package/dist/common/models/setting.policy.d.ts +4 -0
- package/dist/common/models/setting.policy.js +1 -0
- package/dist/common/models/setting.type.d.ts +2 -0
- package/dist/common/models/shared.definition.d.ts +20 -0
- package/dist/common/models/tweak.definition.d.ts +1 -0
- package/dist/common/settingConstants.d.ts +1 -0
- package/dist/common/settingConstants.js +6 -2
- package/dist/interfaces/FileHandler.d.ts +5 -1
- package/dist/interfaces/StorageEventManager.d.ts +5 -0
- package/dist/interfaces/StorageEventManager.js +7 -0
- package/dist/managers/EntryManager/EntryManagerImpls.js +3 -2
- package/dist/managers/StorageEventManager.d.ts +19 -10
- package/dist/managers/StorageEventManager.js +259 -189
- 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/LiveSyncAbstractReplicator.d.ts +2 -0
- package/dist/replication/LiveSyncAbstractReplicator.js +5 -2
- package/dist/replication/RemoteResource.d.ts +23 -2
- package/dist/replication/RemoteResource.js +6 -2
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +14 -0
- package/dist/replication/couchdb/LiveSyncReplicator.js +171 -3
- package/dist/replication/couchdb/fastForward.d.ts +19 -0
- package/dist/replication/couchdb/fastForward.js +119 -0
- package/dist/replication/couchdb/replicationProgress.d.ts +6 -0
- package/dist/replication/couchdb/replicationProgress.js +105 -0
- package/dist/replication/index.d.ts +4 -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/replication/trystero/TrysteroReplicator.d.ts +2 -0
- package/dist/serviceFeatures/offlineScanner.js +19 -7
- package/dist/serviceModules/Rebuilder.js +41 -15
- package/dist/serviceModules/ServiceFileAccessBase.js +5 -0
- package/dist/serviceModules/ServiceFileHandlerBase.d.ts +8 -5
- package/dist/serviceModules/ServiceFileHandlerBase.js +75 -38
- 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/AppLifecycleService.js +1 -1
- package/dist/services/base/ControlService.js +6 -2
- package/dist/services/base/FileProcessingService.d.ts +7 -1
- package/dist/services/base/FileProcessingService.js +9 -1
- package/dist/services/base/IService.d.ts +19 -19
- package/dist/services/base/ReplicatorService.d.ts +1 -0
- 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/services/lib/HandlerUtils.d.ts +60 -0
- package/dist/services/lib/HandlerUtils.js +46 -0
- 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/offline-scanner.md +15 -13
- package/docs/proven-in-use.md +38 -4
- package/docs/releasing.md +20 -13
- package/docs/settings-lifecycle.md +39 -5
- package/docs/storage-events-and-reflection.md +109 -10
- package/package.json +13 -18
- 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
|
@@ -404,7 +404,7 @@ Are you sure you wish to proceed?`;
|
|
|
404
404
|
await this.suspendReflectingDatabase(!autoResume);
|
|
405
405
|
await this.control.applySettings();
|
|
406
406
|
await this.resetLocalDatabase();
|
|
407
|
-
this.clearFastFetchCheckpoint();
|
|
407
|
+
await this.clearFastFetchCheckpoint();
|
|
408
408
|
await delay(1e3);
|
|
409
409
|
if (isRemediationModeActive(this.setting.currentSettings())) {
|
|
410
410
|
this._log(
|
|
@@ -470,10 +470,11 @@ Are you sure you wish to proceed?`;
|
|
|
470
470
|
await this.checkPathIdentitiesBeforeReconstruction(false);
|
|
471
471
|
await checkRemoteFeaturesForInitialSync(remote, authHeader, customHeaders, settings);
|
|
472
472
|
this.appLifecycle.resetIsReady();
|
|
473
|
-
let checkpoint = this.getFastFetchCheckpoint(remote);
|
|
473
|
+
let checkpoint = await this.getFastFetchCheckpoint(remote);
|
|
474
474
|
await this.suspendReflectingDatabase();
|
|
475
475
|
await this.control.applySettings();
|
|
476
476
|
let since = checkpoint?.sequence ?? "0";
|
|
477
|
+
let startsFresh = !checkpoint;
|
|
477
478
|
if (checkpoint) {
|
|
478
479
|
this._log(
|
|
479
480
|
`Resuming fast database fetch from sequence: ${checkpoint.sequence}`,
|
|
@@ -497,16 +498,19 @@ Are you sure you wish to proceed?`;
|
|
|
497
498
|
LOG_LEVEL_NOTICE,
|
|
498
499
|
"fetch-init-resume"
|
|
499
500
|
);
|
|
500
|
-
this.clearFastFetchCheckpoint();
|
|
501
|
+
await this.clearFastFetchCheckpoint();
|
|
501
502
|
await this.resetLocalDatabase();
|
|
502
503
|
await delay(1e3);
|
|
503
504
|
localDB = this.database.localDatabase.localDatabase;
|
|
504
505
|
since = "0";
|
|
506
|
+
startsFresh = true;
|
|
505
507
|
}
|
|
506
508
|
const securitySeed = await this.replicator.createRemoteResource(REMOTE_RESOURCE_KINDS.SECURITY_SEED, settings);
|
|
507
509
|
if (!securitySeed) {
|
|
508
510
|
throw new Error("The selected provider cannot supply a Security Seed for Fast Fetch.");
|
|
509
511
|
}
|
|
512
|
+
let fastForwardResource;
|
|
513
|
+
let fastForward;
|
|
510
514
|
try {
|
|
511
515
|
const enc = getConfiguredFunctionsForEncryption(
|
|
512
516
|
settings.passphrase,
|
|
@@ -515,6 +519,13 @@ Are you sure you wish to proceed?`;
|
|
|
515
519
|
() => securitySeed.read(),
|
|
516
520
|
settings.E2EEAlgorithm
|
|
517
521
|
);
|
|
522
|
+
if (startsFresh && !settings.doNotSuspendOnFetching && (await localDB.info()).doc_count === 0) {
|
|
523
|
+
fastForwardResource = await this.replicator.createRemoteResource(
|
|
524
|
+
REMOTE_RESOURCE_KINDS.INITIAL_FETCH_FAST_FORWARD,
|
|
525
|
+
this.setting.currentSettings()
|
|
526
|
+
);
|
|
527
|
+
fastForward = await fastForwardResource?.prepare();
|
|
528
|
+
}
|
|
518
529
|
for (let attempt = 0; ; attempt++) {
|
|
519
530
|
try {
|
|
520
531
|
await fetchChangesForInitialSync(
|
|
@@ -531,13 +542,17 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
531
542
|
"fetch-init-progress"
|
|
532
543
|
);
|
|
533
544
|
},
|
|
534
|
-
(sequence) =>
|
|
535
|
-
|
|
545
|
+
async (sequence) => {
|
|
546
|
+
await this.saveFastFetchCheckpoint(remote, sequence);
|
|
547
|
+
fastForward?.recordCheckpoint(sequence);
|
|
548
|
+
},
|
|
549
|
+
customHeaders,
|
|
550
|
+
fastForward?.recordStoredRevisions
|
|
536
551
|
);
|
|
537
552
|
break;
|
|
538
553
|
} catch (ex) {
|
|
539
554
|
if (!isRetryableStreamingFetchFailure(ex) || attempt >= FAST_FETCH_RETRY_DELAYS.length) throw ex;
|
|
540
|
-
checkpoint = this.getFastFetchCheckpoint(remote);
|
|
555
|
+
checkpoint = await this.getFastFetchCheckpoint(remote);
|
|
541
556
|
since = checkpoint?.sequence ?? since;
|
|
542
557
|
this._log(
|
|
543
558
|
`Fast fetch interrupted. Retrying from sequence: ${since}`,
|
|
@@ -547,6 +562,13 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
547
562
|
await delay(FAST_FETCH_RETRY_DELAYS[attempt]);
|
|
548
563
|
}
|
|
549
564
|
}
|
|
565
|
+
if (fastForward) {
|
|
566
|
+
const advanced = await fastForward.fastForward();
|
|
567
|
+
this._log(
|
|
568
|
+
advanced ? "Advanced the ordinary replication checkpoints through the verified initial Fast Fetch." : "The initial Fast Fetch did not advance ordinary replication checkpoints.",
|
|
569
|
+
LOG_LEVEL_VERBOSE
|
|
570
|
+
);
|
|
571
|
+
}
|
|
550
572
|
const allDocs = await localDB.allDocs({ include_docs: false });
|
|
551
573
|
this._log(
|
|
552
574
|
`Fast database fetch completed. Total documents in local database: ${allDocs.total_rows}`,
|
|
@@ -559,9 +581,13 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
559
581
|
throw new Error("Fast Fetch could not be finalised.");
|
|
560
582
|
}
|
|
561
583
|
}
|
|
562
|
-
this.clearFastFetchCheckpoint();
|
|
584
|
+
await this.clearFastFetchCheckpoint();
|
|
563
585
|
} finally {
|
|
564
|
-
|
|
586
|
+
try {
|
|
587
|
+
await fastForwardResource?.dispose();
|
|
588
|
+
} finally {
|
|
589
|
+
await securitySeed.dispose();
|
|
590
|
+
}
|
|
565
591
|
}
|
|
566
592
|
}
|
|
567
593
|
/**
|
|
@@ -627,8 +653,8 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
627
653
|
}
|
|
628
654
|
this.events.emitEvent(EVENT_DATABASE_REBUILT);
|
|
629
655
|
}
|
|
630
|
-
getFastFetchCheckpoint(remote) {
|
|
631
|
-
const rawCheckpoint = this.setting.
|
|
656
|
+
async getFastFetchCheckpoint(remote) {
|
|
657
|
+
const rawCheckpoint = await this.setting.config.getValue(FAST_FETCH_CHECKPOINT_KEY);
|
|
632
658
|
if (!rawCheckpoint) return void 0;
|
|
633
659
|
try {
|
|
634
660
|
const checkpoint = JSON.parse(rawCheckpoint);
|
|
@@ -640,11 +666,11 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
640
666
|
}
|
|
641
667
|
} catch {
|
|
642
668
|
}
|
|
643
|
-
this.clearFastFetchCheckpoint();
|
|
669
|
+
await this.clearFastFetchCheckpoint();
|
|
644
670
|
return void 0;
|
|
645
671
|
}
|
|
646
|
-
saveFastFetchCheckpoint(remote, sequence) {
|
|
647
|
-
this.setting.
|
|
672
|
+
async saveFastFetchCheckpoint(remote, sequence) {
|
|
673
|
+
await this.setting.config.setValue(
|
|
648
674
|
FAST_FETCH_CHECKPOINT_KEY,
|
|
649
675
|
JSON.stringify({
|
|
650
676
|
remote,
|
|
@@ -653,8 +679,8 @@ Total bytes fetched: ${sizeToHumanReadable(progress.totalBytes)}`,
|
|
|
653
679
|
})
|
|
654
680
|
);
|
|
655
681
|
}
|
|
656
|
-
clearFastFetchCheckpoint() {
|
|
657
|
-
this.setting.
|
|
682
|
+
async clearFastFetchCheckpoint() {
|
|
683
|
+
await this.setting.config.delete(FAST_FETCH_CHECKPOINT_KEY);
|
|
658
684
|
}
|
|
659
685
|
}
|
|
660
686
|
export {
|
|
@@ -15,6 +15,11 @@ class ServiceFileAccessBase extends ServiceModuleBase {
|
|
|
15
15
|
this.vaultAccess = services.vaultAccess;
|
|
16
16
|
services.appLifecycle.onFirstInitialise.addHandler(this._everyOnFirstInitialize.bind(this));
|
|
17
17
|
services.fileProcessing.commitPendingFileEvents.addHandler(this._everyCommitPendingFileEvent.bind(this));
|
|
18
|
+
services.appLifecycle.onBeforeUnload.addHandler(async () => {
|
|
19
|
+
if (this.vaultManager.stopWatching) await this.vaultManager.stopWatching();
|
|
20
|
+
else await this.vaultManager.waitForIdle();
|
|
21
|
+
return true;
|
|
22
|
+
});
|
|
18
23
|
}
|
|
19
24
|
restoreState() {
|
|
20
25
|
return this.vaultManager.restoreState();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AnyEntry, FilePath, FilePathWithPrefix, MetaEntry, UXFileInfo, UXFileInfoStub, UXInternalFileInfoStub } from "../common/types.js";
|
|
2
2
|
import type { LiveSyncEventHub } from "../hub/hub.js";
|
|
3
|
-
import type { IFileHandler } from "../interfaces/FileHandler.js";
|
|
3
|
+
import type { IFileHandler, StoreFileOptions } from "../interfaces/FileHandler.js";
|
|
4
4
|
import { ServiceModuleBase } from "./ServiceModuleBase.js";
|
|
5
5
|
import type { APIService } from "../services/base/APIService.js";
|
|
6
6
|
import type { DatabaseFileAccess } from "../interfaces/DatabaseFileAccess.js";
|
|
@@ -74,7 +74,8 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
|
|
|
74
74
|
/**
|
|
75
75
|
* Read the raw provenance value used by an optimistic storage snapshot.
|
|
76
76
|
* Unlike `getProvenance`, this does not validate or remove the referenced
|
|
77
|
-
* database revision
|
|
77
|
+
* database revision. Recovery callers require a readable value so a store
|
|
78
|
+
* failure cannot be mistaken for absent provenance.
|
|
78
79
|
*/
|
|
79
80
|
private readProvenanceSnapshot;
|
|
80
81
|
/**
|
|
@@ -99,7 +100,7 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
|
|
|
99
100
|
readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
|
|
100
101
|
tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
|
|
101
102
|
private infoToStub;
|
|
102
|
-
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean): Promise<boolean>;
|
|
103
|
+
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean, options?: StoreFileOptions): Promise<boolean>;
|
|
103
104
|
storeFileToDBWithBaseRevision(info: UXFileInfoStub | UXFileInfo | FilePathWithPrefix, baseRevision: string, createIfDifferent?: boolean): Promise<boolean>;
|
|
104
105
|
/**
|
|
105
106
|
* Store current storage content below an exact live revision. A differing
|
|
@@ -110,11 +111,13 @@ export declare abstract class ServiceFileHandlerBase extends ServiceModuleBase<S
|
|
|
110
111
|
/**
|
|
111
112
|
* Save one storage snapshot while the caller holds its document lock.
|
|
112
113
|
* Ordinary saves read once inside the lock and leave retries disabled.
|
|
113
|
-
* Restored
|
|
114
|
-
*
|
|
114
|
+
* Restored start-up events and recovery scans retain bounded rereads
|
|
115
|
+
* because a watcher event may not cover a concurrent edit.
|
|
115
116
|
*/
|
|
116
117
|
private storeFileToDBFromRevision;
|
|
117
118
|
deleteFileFromDB(info: UXFileInfoStub | UXInternalFileInfoStub | FilePath): Promise<boolean>;
|
|
119
|
+
/** Complete a validated storage deletion when its database effect is already represented. */
|
|
120
|
+
private deleteFileFromDBIfPresent;
|
|
118
121
|
/**
|
|
119
122
|
* Record a storage deletion under the caller's document lock, preserving
|
|
120
123
|
* the displayed conflict branch when its provenance is known.
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { LOG_LEVEL_INFO, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "octagonal-wheels/common/logger";
|
|
2
2
|
import { serialized } from "octagonal-wheels/concurrency/lock";
|
|
3
3
|
import {
|
|
4
|
+
createBlob,
|
|
4
5
|
getDocDataAsArray,
|
|
5
6
|
isDocContentSame,
|
|
6
|
-
readAsBlob,
|
|
7
7
|
readContent
|
|
8
8
|
} from "../common/utils.js";
|
|
9
9
|
import { computeFileContentHash } from "../common/fileContentHash.js";
|
|
@@ -158,7 +158,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
158
158
|
* A valid saved content hash can prove unchanged bytes after revision
|
|
159
159
|
* pruning. It cannot reconstruct ancestry for an edited file.
|
|
160
160
|
*/
|
|
161
|
-
async classifyStorageContent(file, currentEntry, preferredPath, recorded, currentLoaded) {
|
|
161
|
+
async classifyStorageContent(file, currentEntry, preferredPath, recorded, currentLoaded, requireReadableContent = false) {
|
|
162
162
|
const path = preferredPath ?? file.path;
|
|
163
163
|
const recordedRevision = recorded?.revision;
|
|
164
164
|
if (recordedRevision) {
|
|
@@ -173,6 +173,9 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
173
173
|
}
|
|
174
174
|
if (!base) {
|
|
175
175
|
const meta = await this.db.fetchEntryMeta(file, recordedRevision, true);
|
|
176
|
+
if (requireReadableContent && meta && !meta.deleted && !meta._deleted) {
|
|
177
|
+
throw new Error(`Could not read the recorded file revision for ${file.path}`);
|
|
178
|
+
}
|
|
176
179
|
if ((!meta || meta.deleted || meta._deleted) && !hasContentHash(recorded)) await this.deleteProvenance(path);
|
|
177
180
|
}
|
|
178
181
|
}
|
|
@@ -189,13 +192,15 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
189
192
|
/**
|
|
190
193
|
* Read the raw provenance value used by an optimistic storage snapshot.
|
|
191
194
|
* Unlike `getProvenance`, this does not validate or remove the referenced
|
|
192
|
-
* database revision
|
|
195
|
+
* database revision. Recovery callers require a readable value so a store
|
|
196
|
+
* failure cannot be mistaken for absent provenance.
|
|
193
197
|
*/
|
|
194
|
-
async readProvenanceSnapshot(path) {
|
|
198
|
+
async readProvenanceSnapshot(path, requireReadable = false) {
|
|
195
199
|
try {
|
|
196
200
|
const record = await this.fileReflectionProvenance?.get(this.getProvenancePath(path));
|
|
197
201
|
return record ? { ...record } : void 0;
|
|
198
|
-
} catch {
|
|
202
|
+
} catch (error) {
|
|
203
|
+
if (requireReadable) throw error;
|
|
199
204
|
return void 0;
|
|
200
205
|
}
|
|
201
206
|
}
|
|
@@ -203,20 +208,20 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
203
208
|
* Check that storage still has the same path, bytes, timestamp, and
|
|
204
209
|
* provenance as a previously captured snapshot.
|
|
205
210
|
*/
|
|
206
|
-
async currentStorageSnapshotMatches(file, revision) {
|
|
211
|
+
async currentStorageSnapshotMatches(file, revision, requireReadableProvenance = false) {
|
|
207
212
|
const current = await this.storage.getStub(file.path);
|
|
208
213
|
if (!current || isFolderInfo(current) || current.path !== file.path) return false;
|
|
209
214
|
const readCurrent = await this.readFileFromStub(current);
|
|
210
|
-
return readCurrent.stat.mtime === file.stat.mtime && await isDocContentSame(readCurrent.body, file.body) && sameProvenance(await this.readProvenanceSnapshot(file.path), revision);
|
|
215
|
+
return readCurrent.stat.mtime === file.stat.mtime && await isDocContentSame(readCurrent.body, file.body) && sameProvenance(await this.readProvenanceSnapshot(file.path, requireReadableProvenance), revision);
|
|
211
216
|
}
|
|
212
217
|
/**
|
|
213
218
|
* Publish provenance after a durable database write if storage is unchanged.
|
|
214
219
|
* A failed revalidation leaves the database revision stored and asks the
|
|
215
220
|
* caller to reread storage before proceeding.
|
|
216
221
|
*/
|
|
217
|
-
async finaliseStoredRevision(file, observedProvenance, storedRevision) {
|
|
222
|
+
async finaliseStoredRevision(file, observedProvenance, storedRevision, requireReadableProvenance = false) {
|
|
218
223
|
const evidence = await this.contentEvidence(file.body);
|
|
219
|
-
if (await this.currentStorageSnapshotMatches(file, observedProvenance)) {
|
|
224
|
+
if (await this.currentStorageSnapshotMatches(file, observedProvenance, requireReadableProvenance)) {
|
|
220
225
|
await this.setProvenance(file.path, storedRevision, file.stat.mtime, file.body, evidence);
|
|
221
226
|
return true;
|
|
222
227
|
} else {
|
|
@@ -293,10 +298,10 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
293
298
|
const file = typeof info === "string" ? await this.storage.getFileStub(info) : info;
|
|
294
299
|
return file;
|
|
295
300
|
}
|
|
296
|
-
async storeFileToDB(info, force = false, onlyChunks = false) {
|
|
301
|
+
async storeFileToDB(info, force = false, onlyChunks = false, options = {}) {
|
|
297
302
|
return await this.serializedByFileEventPaths(
|
|
298
303
|
[getStoragePathFromUXFileInfo(info)],
|
|
299
|
-
() => this.storeFileToDBFromRevision(info, force, onlyChunks)
|
|
304
|
+
() => this.storeFileToDBFromRevision(info, force, onlyChunks, void 0, options.revalidateStorage)
|
|
300
305
|
);
|
|
301
306
|
}
|
|
302
307
|
async storeFileToDBWithBaseRevision(info, baseRevision, createIfDifferent = true) {
|
|
@@ -366,8 +371,8 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
366
371
|
/**
|
|
367
372
|
* Save one storage snapshot while the caller holds its document lock.
|
|
368
373
|
* Ordinary saves read once inside the lock and leave retries disabled.
|
|
369
|
-
* Restored
|
|
370
|
-
*
|
|
374
|
+
* Restored start-up events and recovery scans retain bounded rereads
|
|
375
|
+
* because a watcher event may not cover a concurrent edit.
|
|
371
376
|
*/
|
|
372
377
|
async storeFileToDBFromRevision(info, force = false, onlyChunks = false, preferredBasePath, retryIfStorageChanges = false, snapshotRetry = 0) {
|
|
373
378
|
const file = await this.infoToStub(info);
|
|
@@ -387,32 +392,36 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
387
392
|
return await this.db.createChunks(readFile2, force, true);
|
|
388
393
|
}
|
|
389
394
|
const observedProvenance = await this.readProvenanceSnapshot(
|
|
390
|
-
preferredBasePath ?? file.path
|
|
395
|
+
preferredBasePath ?? file.path,
|
|
396
|
+
retryIfStorageChanges
|
|
391
397
|
);
|
|
392
398
|
const readFile = await this.readFileFromStub(file);
|
|
393
399
|
const entry = await this.db.fetchEntry(file, void 0, true, true, true);
|
|
394
400
|
const currentEntry = entry || await this.db.fetchEntryMeta(file, void 0, true);
|
|
395
401
|
const conflictedRevs = await this.db.getConflictedRevs(file);
|
|
396
402
|
const isConflicted = conflictedRevs.length > 0;
|
|
397
|
-
|
|
403
|
+
const currentIsLive = currentEntry && !currentEntry.deleted && !currentEntry._deleted;
|
|
404
|
+
if (!force && (currentIsLive || retryIfStorageChanges)) {
|
|
405
|
+
if (retryIfStorageChanges && currentIsLive && !entry) return false;
|
|
398
406
|
const classification = await this.classifyStorageContent(
|
|
399
407
|
readFile,
|
|
400
408
|
currentEntry,
|
|
401
409
|
preferredBasePath,
|
|
402
410
|
observedProvenance,
|
|
403
|
-
entry ? { _rev: entry._rev, data: readContent(entry) } : void 0
|
|
411
|
+
entry ? { _rev: entry._rev, data: readContent(entry) } : void 0,
|
|
412
|
+
retryIfStorageChanges
|
|
404
413
|
);
|
|
405
|
-
const upgradeCurrentRecord = classification.kind === "unchanged" && !isConflicted && classification.revision === currentEntry._rev && !hasContentHash(observedProvenance);
|
|
414
|
+
const upgradeCurrentRecord = classification.kind === "unchanged" && !isConflicted && currentEntry && !currentEntry.deleted && !currentEntry._deleted && classification.revision === currentEntry._rev && !hasContentHash(observedProvenance);
|
|
406
415
|
const evidence = upgradeCurrentRecord || classification.kind === "matching-leaf" && classification.revision ? await this.contentEvidence(readFile.body) : void 0;
|
|
407
|
-
const snapshotMatches = retryIfStorageChanges || upgradeCurrentRecord ? await this.currentStorageSnapshotMatches(readFile, observedProvenance) : true;
|
|
416
|
+
const snapshotMatches = retryIfStorageChanges || upgradeCurrentRecord ? await this.currentStorageSnapshotMatches(readFile, observedProvenance, retryIfStorageChanges) : true;
|
|
408
417
|
if (retryIfStorageChanges && !snapshotMatches) {
|
|
409
418
|
return await this.retryCurrentStorage(file.path, preferredBasePath, snapshotRetry);
|
|
410
419
|
}
|
|
411
420
|
if (classification.kind === "unchanged") {
|
|
412
421
|
if (isConflicted) {
|
|
413
422
|
await this.queueConflictCheckFor(file.path);
|
|
414
|
-
} else if (classification.revision !== currentEntry._rev) {
|
|
415
|
-
if (!
|
|
423
|
+
} else if (!currentEntry || currentEntry.deleted || currentEntry._deleted || classification.revision !== currentEntry._rev) {
|
|
424
|
+
if (!currentEntry) return false;
|
|
416
425
|
return await this.dbToStorageCore(file.path, file);
|
|
417
426
|
} else {
|
|
418
427
|
if (upgradeCurrentRecord && snapshotMatches) {
|
|
@@ -430,7 +439,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
430
439
|
const storedRevision2 = classification.kind === "edited" ? await this.db.storeWithBaseRevision(readFile, classification.revision, true) : await this.db.storeIndependentRevision(readFile, true);
|
|
431
440
|
if (storedRevision2 === false) return false;
|
|
432
441
|
if (preferredBasePath && preferredBasePath !== file.path) await this.deleteProvenance(preferredBasePath);
|
|
433
|
-
if (retryIfStorageChanges && !await this.finaliseStoredRevision(readFile, observedProvenance, storedRevision2)) {
|
|
442
|
+
if (retryIfStorageChanges && !await this.finaliseStoredRevision(readFile, observedProvenance, storedRevision2, true)) {
|
|
434
443
|
return await this.retryCurrentStorage(file.path, preferredBasePath, snapshotRetry);
|
|
435
444
|
}
|
|
436
445
|
if (!retryIfStorageChanges) await this.setProvenance(file.path, storedRevision2, readFile.stat.mtime, readFile.body);
|
|
@@ -519,6 +528,12 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
519
528
|
() => this.deleteFileFromDBCore(info)
|
|
520
529
|
);
|
|
521
530
|
}
|
|
531
|
+
/** Complete a validated storage deletion when its database effect is already represented. */
|
|
532
|
+
async deleteFileFromDBIfPresent(info) {
|
|
533
|
+
const entry = await this.db.fetchEntryMeta(info, void 0, true);
|
|
534
|
+
if (!entry || entry.deleted || entry._deleted) return true;
|
|
535
|
+
return await this.deleteFileFromDBCore(info);
|
|
536
|
+
}
|
|
522
537
|
/**
|
|
523
538
|
* Record a storage deletion under the caller's document lock, preserving
|
|
524
539
|
* the displayed conflict branch when its provenance is known.
|
|
@@ -892,17 +907,17 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
892
907
|
this._log(`File ${path} is not exist on the database`, LOG_LEVEL_VERBOSE);
|
|
893
908
|
return false;
|
|
894
909
|
}
|
|
910
|
+
const docData = readContent(docRead);
|
|
911
|
+
const reflectedContent = createBlob(docData);
|
|
895
912
|
if (!settings.processSizeMismatchedFiles) {
|
|
896
|
-
if (docRead.size != 0 && docRead.size !==
|
|
913
|
+
if (docRead.size != 0 && docRead.size !== reflectedContent.size) {
|
|
897
914
|
this._log(
|
|
898
|
-
`File ${path} seems to be corrupted! Writing prevented. (${docRead.size} != ${
|
|
915
|
+
`File ${path} seems to be corrupted! Writing prevented. (${docRead.size} != ${reflectedContent.size})`,
|
|
899
916
|
LOG_LEVEL_NOTICE
|
|
900
917
|
);
|
|
901
918
|
return false;
|
|
902
919
|
}
|
|
903
920
|
}
|
|
904
|
-
const docData = readContent(docRead);
|
|
905
|
-
const reflectedContent = readAsBlob(docRead);
|
|
906
921
|
const reflectedEvidence = await this.contentEvidence(reflectedContent);
|
|
907
922
|
if (allowExistingConflicts && existDoc && !force) {
|
|
908
923
|
const readFile = await this.readFileFromStub(existDoc);
|
|
@@ -1080,14 +1095,23 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
1080
1095
|
const relatedPath = type === "RENAME" ? eventItem.oldPath : type === "DELETE" ? eventItem.renameTarget : void 0;
|
|
1081
1096
|
const eventPaths = relatedPath === void 0 ? [path] : [path, relatedPath];
|
|
1082
1097
|
return await this.serializedByFileEventPaths(eventPaths, async () => {
|
|
1083
|
-
if (!await this.isCurrentPathSelected(path))
|
|
1098
|
+
if (!await this.isCurrentPathSelected(path)) {
|
|
1099
|
+
return shouldBeIgnored(path) || !await this.vault.isTargetFile(path, { skipCaseCollisionCheck: true });
|
|
1100
|
+
}
|
|
1084
1101
|
switch (type) {
|
|
1085
1102
|
case "CREATE":
|
|
1086
|
-
case "CHANGED":
|
|
1087
|
-
|
|
1103
|
+
case "CHANGED": {
|
|
1104
|
+
const current = this.getExactCurrentFile(await this.storage.getStub(path), path);
|
|
1105
|
+
if (!current || this.vault.isFileSizeTooLarge(current.stat.size)) return true;
|
|
1106
|
+
return await this.storeFileToDBFromRevision(current);
|
|
1107
|
+
}
|
|
1088
1108
|
case "DELETE":
|
|
1089
|
-
|
|
1090
|
-
|
|
1109
|
+
try {
|
|
1110
|
+
if (!await this.canRecordStorageDeletion(item)) return true;
|
|
1111
|
+
} catch {
|
|
1112
|
+
return false;
|
|
1113
|
+
}
|
|
1114
|
+
return await this.deleteFileFromDBIfPresent(item.args.file);
|
|
1091
1115
|
case "RENAME":
|
|
1092
1116
|
return await this.renameFileInDBCore(
|
|
1093
1117
|
item.args.file,
|
|
@@ -1137,7 +1161,7 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
1137
1161
|
} catch (ex) {
|
|
1138
1162
|
this._log(`Could not validate deletion of ${path}; preserved the database entry`, LOG_LEVEL_NOTICE);
|
|
1139
1163
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
|
1140
|
-
|
|
1164
|
+
throw ex;
|
|
1141
1165
|
}
|
|
1142
1166
|
}
|
|
1143
1167
|
/**
|
|
@@ -1222,15 +1246,19 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
1222
1246
|
action = await this._planRestoredFileEvent(item, isSameDocument);
|
|
1223
1247
|
} catch (ex) {
|
|
1224
1248
|
this.logRestoredEventValidationFailure(path, ex);
|
|
1225
|
-
return
|
|
1249
|
+
return false;
|
|
1226
1250
|
}
|
|
1227
1251
|
switch (action.kind) {
|
|
1228
1252
|
case "none":
|
|
1229
1253
|
return true;
|
|
1230
|
-
case "
|
|
1231
|
-
return
|
|
1254
|
+
case "unresolved":
|
|
1255
|
+
return false;
|
|
1256
|
+
case "store": {
|
|
1257
|
+
const stored = await this.storeFileToDBFromRevision(action.file, false, false, void 0, true);
|
|
1258
|
+
return stored && action.complete !== false;
|
|
1259
|
+
}
|
|
1232
1260
|
case "delete":
|
|
1233
|
-
return await this.
|
|
1261
|
+
return await this.deleteFileFromDBIfPresent(action.path);
|
|
1234
1262
|
case "rename":
|
|
1235
1263
|
return await this.renameFileInDBCore(action.file, action.oldPath, true);
|
|
1236
1264
|
}
|
|
@@ -1266,6 +1294,10 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
1266
1294
|
if (!currentTarget2 || !await this.isCurrentFileSelected(currentTarget2)) {
|
|
1267
1295
|
return { kind: "none" };
|
|
1268
1296
|
}
|
|
1297
|
+
const entry = await this.db.fetchEntryMeta(newPath, void 0, true);
|
|
1298
|
+
if (entry && !entry.deleted && !entry._deleted && this.storage.normalisePath(this.getPath(entry)) === this.storage.normalisePath(newPath)) {
|
|
1299
|
+
return { kind: "store", file: currentTarget2 };
|
|
1300
|
+
}
|
|
1269
1301
|
return { kind: "rename", file: currentTarget2, oldPath };
|
|
1270
1302
|
}
|
|
1271
1303
|
const currentTargetItem = await this.storage.getStub(newPath);
|
|
@@ -1283,11 +1315,16 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
1283
1315
|
return { kind: "none" };
|
|
1284
1316
|
}
|
|
1285
1317
|
if (!sourceWasInspected || currentSourceItem !== null || !await this.canApplyRestoredDeletion(oldPath)) {
|
|
1318
|
+
return { kind: "store", file: currentTarget, complete: sourceWasInspected };
|
|
1319
|
+
}
|
|
1320
|
+
const sourceEntry = await this.db.fetchEntryMeta(oldPath, void 0, true);
|
|
1321
|
+
if (!sourceEntry || sourceEntry.deleted || sourceEntry._deleted) {
|
|
1286
1322
|
return { kind: "store", file: currentTarget };
|
|
1287
1323
|
}
|
|
1288
1324
|
return { kind: "rename", file: currentTarget, oldPath };
|
|
1289
1325
|
}
|
|
1290
|
-
if (!sourceWasInspected
|
|
1326
|
+
if (!sourceWasInspected) return { kind: "unresolved" };
|
|
1327
|
+
if (currentSourceItem !== null || !await this.canApplyRestoredDeletion(oldPath)) {
|
|
1291
1328
|
return { kind: "none" };
|
|
1292
1329
|
}
|
|
1293
1330
|
return { kind: "delete", path: oldPath };
|
|
@@ -1324,12 +1361,12 @@ class ServiceFileHandlerBase extends ServiceModuleBase {
|
|
|
1324
1361
|
return await this.isCurrentPathSelected(path);
|
|
1325
1362
|
} catch (ex) {
|
|
1326
1363
|
this.logRestoredEventValidationFailure(path, ex);
|
|
1327
|
-
|
|
1364
|
+
throw ex;
|
|
1328
1365
|
}
|
|
1329
1366
|
}
|
|
1330
1367
|
logRestoredEventValidationFailure(path, ex) {
|
|
1331
1368
|
this._log(
|
|
1332
|
-
`Could not validate the saved storage operation for ${path} against current storage; the
|
|
1369
|
+
`Could not validate the saved storage operation for ${path} against current storage; the operation remains pending for recovery`,
|
|
1333
1370
|
LOG_LEVEL_NOTICE
|
|
1334
1371
|
);
|
|
1335
1372
|
this._log(ex, LOG_LEVEL_VERBOSE);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import type { IConfigService } from "./base/IService.js";
|
|
1
2
|
import { InjectableVaultServiceCompat } from "./implements/injectable/InjectableVaultService.js";
|
|
2
3
|
import { ServiceContext } from "./base/ServiceBase.js";
|
|
3
4
|
import { InjectableServiceHub } from "./implements/injectable/InjectableServiceHub.js";
|
|
4
5
|
import { ControlService } from "./base/ControlService.js";
|
|
5
6
|
import type { AppLifecycleService } from "./base/AppLifecycleService.js";
|
|
6
|
-
import type { ConfigService } from "./base/ConfigService.js";
|
|
7
7
|
import type { ReplicatorService } from "./base/ReplicatorService.js";
|
|
8
8
|
import type { UIService } from "./implements/base/UIService.js";
|
|
9
9
|
import type { InjectableAPIService } from "./implements/injectable/InjectableAPIService.js";
|
|
@@ -12,7 +12,7 @@ import type { ObsidianLiveSyncSettings } from "../common/types.js";
|
|
|
12
12
|
export type BrowserServiceHostDependencies<T extends ServiceContext> = {
|
|
13
13
|
API: InjectableAPIService<T>;
|
|
14
14
|
appLifecycle: AppLifecycleService<T>;
|
|
15
|
-
config:
|
|
15
|
+
config: IConfigService;
|
|
16
16
|
control: ControlService<T>;
|
|
17
17
|
replicator: ReplicatorService<T>;
|
|
18
18
|
};
|
|
@@ -29,6 +29,8 @@ export interface BrowserServiceHost<T extends ServiceContext> {
|
|
|
29
29
|
export type BrowserServiceHubOptions<T extends ServiceContext> = {
|
|
30
30
|
context?: T;
|
|
31
31
|
host: BrowserServiceHost<T>;
|
|
32
|
+
/** Exact-key storage selected and owned by the application. */
|
|
33
|
+
deviceValues: IConfigService;
|
|
32
34
|
openKeyValueDatabase?: KeyValueDatabaseFactory;
|
|
33
35
|
onDisplayLanguageChanged?: (language: ObsidianLiveSyncSettings["displayLanguage"]) => void;
|
|
34
36
|
};
|
|
@@ -11,7 +11,6 @@ import { InjectableTestService } from "./implements/injectable/InjectableTestSer
|
|
|
11
11
|
import { InjectableTweakValueService } from "./implements/injectable/InjectableTweakValueService.js";
|
|
12
12
|
import { InjectableVaultServiceCompat } from "./implements/injectable/InjectableVaultService.js";
|
|
13
13
|
import { ServiceContext } from "./base/ServiceBase.js";
|
|
14
|
-
import { ConfigServiceBrowserCompat } from "./implements/browser/ConfigServiceBrowserCompat.js";
|
|
15
14
|
import { InjectableServiceHub } from "./implements/injectable/InjectableServiceHub.js";
|
|
16
15
|
import { BrowserDatabaseService, BrowserKeyValueDBService } from "./implements/browser/BrowserDatabaseService.js";
|
|
17
16
|
import { ControlService } from "./base/ControlService.js";
|
|
@@ -34,6 +33,7 @@ class BrowserServiceHub extends InjectableServiceHub {
|
|
|
34
33
|
const fileProcessing = new InjectableFileProcessingService(context);
|
|
35
34
|
const setting = new InjectableSettingService(context, {
|
|
36
35
|
APIService: API,
|
|
36
|
+
deviceValues: options.deviceValues,
|
|
37
37
|
onDisplayLanguageChanged: options.onDisplayLanguageChanged
|
|
38
38
|
});
|
|
39
39
|
const appLifecycle = new BrowserAppLifecycleService(context, { settingService: setting });
|
|
@@ -60,10 +60,7 @@ class BrowserServiceHub extends InjectableServiceHub {
|
|
|
60
60
|
setting,
|
|
61
61
|
API
|
|
62
62
|
});
|
|
63
|
-
const config =
|
|
64
|
-
settingService: setting,
|
|
65
|
-
APIService: API
|
|
66
|
-
});
|
|
63
|
+
const config = setting.config;
|
|
67
64
|
const replicator = new InjectableReplicatorService(context, {
|
|
68
65
|
settingService: setting,
|
|
69
66
|
appLifecycleService: appLifecycle,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IConfigService } from "./base/IService.js";
|
|
1
2
|
import { ServiceContext } from "./base/ServiceBase.js";
|
|
2
3
|
import { InjectableServiceHub } from "./implements/injectable/InjectableServiceHub.js";
|
|
3
4
|
import type { DatabaseService } from "./base/DatabaseService.js";
|
|
@@ -9,6 +10,8 @@ export declare class HeadlessServiceHub<T extends ServiceContext> extends Inject
|
|
|
9
10
|
constructor(_context: T | undefined, overrideServiceConstructor: {
|
|
10
11
|
/** PouchDB with the adapters required by this headless runtime. */
|
|
11
12
|
pouchDB: PouchDBConstructor;
|
|
13
|
+
/** Exact-key storage selected and owned by the application. */
|
|
14
|
+
deviceValues: IConfigService;
|
|
12
15
|
database?: Constructor<DatabaseService<T>>;
|
|
13
16
|
openKeyValueDatabase?: KeyValueDatabaseFactory;
|
|
14
17
|
onDisplayLanguageChanged?: (language: ObsidianLiveSyncSettings["displayLanguage"]) => void;
|
|
@@ -11,7 +11,6 @@ import { InjectableTestService } from "./implements/injectable/InjectableTestSer
|
|
|
11
11
|
import { InjectableTweakValueService } from "./implements/injectable/InjectableTweakValueService.js";
|
|
12
12
|
import { InjectableVaultServiceCompat } from "./implements/injectable/InjectableVaultService.js";
|
|
13
13
|
import { ServiceContext } from "./base/ServiceBase.js";
|
|
14
|
-
import { ConfigServiceBrowserCompat } from "./implements/browser/ConfigServiceBrowserCompat.js";
|
|
15
14
|
import { InjectableServiceHub } from "./implements/injectable/InjectableServiceHub.js";
|
|
16
15
|
import { UIService } from "./implements/base/UIService.js";
|
|
17
16
|
import { HeadlessAPIService } from "./implements/headless/HeadlessAPIService.js";
|
|
@@ -56,6 +55,7 @@ class HeadlessServiceHub extends InjectableServiceHub {
|
|
|
56
55
|
const fileProcessing = new InjectableFileProcessingService(context);
|
|
57
56
|
const setting = new InjectableSettingService(context, {
|
|
58
57
|
APIService: API,
|
|
58
|
+
deviceValues: overrideServiceConstructor.deviceValues,
|
|
59
59
|
onDisplayLanguageChanged: overrideServiceConstructor.onDisplayLanguageChanged
|
|
60
60
|
});
|
|
61
61
|
const appLifecycle = new HeadlessAppLifecycleService(context, {
|
|
@@ -85,10 +85,7 @@ class HeadlessServiceHub extends InjectableServiceHub {
|
|
|
85
85
|
vault,
|
|
86
86
|
setting
|
|
87
87
|
});
|
|
88
|
-
const config =
|
|
89
|
-
settingService: setting,
|
|
90
|
-
APIService: API
|
|
91
|
-
});
|
|
88
|
+
const config = setting.config;
|
|
92
89
|
const replicator = new InjectableReplicatorService(context, {
|
|
93
90
|
settingService: setting,
|
|
94
91
|
appLifecycleService: appLifecycle,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import type { IConfigService } from "./base/IService.js";
|
|
1
2
|
import type { UIService } from "./implements/base/UIService.js";
|
|
2
|
-
import type { ConfigService } from "./base/ConfigService.js";
|
|
3
3
|
import type { TestService } from "./base/TestService.js";
|
|
4
4
|
import type { VaultService } from "./base/VaultService.js";
|
|
5
5
|
import type { TweakValueService } from "./base/TweakValueService.js";
|
|
@@ -34,7 +34,7 @@ export type ServiceInstances<T extends ServiceContext = ServiceContext> = {
|
|
|
34
34
|
vault?: VaultService<T>;
|
|
35
35
|
test?: TestService<T>;
|
|
36
36
|
ui?: UIService<T>;
|
|
37
|
-
config?:
|
|
37
|
+
config?: IConfigService;
|
|
38
38
|
keyValueDB?: KeyValueDBService<T>;
|
|
39
39
|
control?: ControlService<T>;
|
|
40
40
|
};
|
|
@@ -56,7 +56,7 @@ export declare abstract class ServiceHub<T extends ServiceContext = ServiceConte
|
|
|
56
56
|
protected abstract _vault: VaultService<T>;
|
|
57
57
|
protected abstract _test: TestService<T>;
|
|
58
58
|
protected abstract _ui: UIService<T>;
|
|
59
|
-
protected abstract _config:
|
|
59
|
+
protected abstract _config: IConfigService;
|
|
60
60
|
protected abstract _keyValueDB: KeyValueDBService<T>;
|
|
61
61
|
protected abstract _control: ControlService<T>;
|
|
62
62
|
protected _injected: ServiceInstances<T>;
|
|
@@ -76,7 +76,7 @@ export declare abstract class ServiceHub<T extends ServiceContext = ServiceConte
|
|
|
76
76
|
get vault(): VaultService<T>;
|
|
77
77
|
get test(): TestService<T>;
|
|
78
78
|
get UI(): UIService<T>;
|
|
79
|
-
get config():
|
|
79
|
+
get config(): IConfigService;
|
|
80
80
|
get keyValueDB(): KeyValueDBService<T>;
|
|
81
81
|
get control(): ControlService<T>;
|
|
82
82
|
}
|
|
@@ -60,7 +60,7 @@ class AppLifecycleService extends ServiceBase {
|
|
|
60
60
|
* Event triggered before the plug-in is unloaded.
|
|
61
61
|
* This is typically used to perform any necessary cleanup or save state before the plug-in is unloaded.
|
|
62
62
|
*/
|
|
63
|
-
onBeforeUnload = handlers().
|
|
63
|
+
onBeforeUnload = handlers().allSettled("onBeforeUnload");
|
|
64
64
|
/**
|
|
65
65
|
* Event triggered when the plug-in is being unloaded.
|
|
66
66
|
*
|