@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
package/README.md
CHANGED
|
@@ -14,12 +14,19 @@ This package is primarily for maintainers of those clients and for integrations
|
|
|
14
14
|
|
|
15
15
|
## Status and versioning
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Commonlib is pre-1.0. The compatibility policy described here begins with 0.2.0 and does not retroactively apply to published 0.1.x versions. Within 0.2.x, Commonlib preserves published APIs, types, behaviour, supported persisted data, and exported `compat/*` paths; intentional breaking changes use 0.3.0. Consumers may declare ranges such as `^0.2.0`; retain a lockfile and use `npm ci` for reproducible installs. An entry in the package export map means that the import is deliberate and package-tested; it does not make every semantic detail of that entry a final 1.0 guarantee.
|
|
18
18
|
|
|
19
19
|
Treat every `compat/*` subpath as a migration-only interface. A source-directory import, Git submodule, or consumer-defined TypeScript alias is not a supported package boundary.
|
|
20
20
|
|
|
21
21
|
The package is ESM-only and declares Node 20 or later. Browser entry points are built with browser conditions, but the host remains responsible for checking the availability of browser capabilities such as the File System Access API.
|
|
22
22
|
|
|
23
|
+
Fast Fetch imports all current leaf revisions listed by CouchDB, including
|
|
24
|
+
deleted conflict leaves. A host may supply the optional
|
|
25
|
+
`initial-fetch-fast-forward` resource to hand a fresh, complete import to
|
|
26
|
+
ordinary replication before resuming local writers. Existing hosts can omit
|
|
27
|
+
this optimisation. Resume cursors retain their existing meaning; this resource
|
|
28
|
+
does not provide an arbitrary-time checkpoint jump or On Demand repair.
|
|
29
|
+
|
|
23
30
|
## Package entry points
|
|
24
31
|
|
|
25
32
|
| Entry point | Intended use | Current contract status |
|
|
@@ -36,7 +43,8 @@ The package is ESM-only and declares Node 20 or later. Browser entry points are
|
|
|
36
43
|
| `@vrtmrz/livesync-commonlib/remote-configurations` | Multiple-remote profile creation, naming, and selection | Focused, package-tested pre-1.0 contract |
|
|
37
44
|
| `@vrtmrz/livesync-commonlib/settings` | New-Vault defaults, stored-setting fallbacks, and settings migration results | Focused, package-tested pre-1.0 contract |
|
|
38
45
|
| `@vrtmrz/livesync-commonlib/setup-uri` | Encrypted Setup URI generation, availability, and import | Focused, package-tested pre-1.0 contract |
|
|
39
|
-
| `@vrtmrz/livesync-commonlib/
|
|
46
|
+
| `@vrtmrz/livesync-commonlib/ui-commands` | Instance-owned typed UI command declarations and handlers | Focused, package-tested contract |
|
|
47
|
+
| `@vrtmrz/livesync-commonlib/compat/*` | Exact legacy imports still required by existing clients | Migration-only; retained within 0.2.x, with removals treated as 0.3.0 breaking changes |
|
|
40
48
|
| `@vrtmrz/livesync-commonlib/package.json` | Package metadata for tooling | Metadata export, not a runtime API |
|
|
41
49
|
|
|
42
50
|
Import only the entry point required by the current runtime. Browser code must not import the Node entry.
|
|
@@ -75,6 +83,74 @@ After the Self-hosted LiveSync 1.0 work, the planned high-level composition is a
|
|
|
75
83
|
|
|
76
84
|
The factory is not implemented yet. Until it is available, service implementations exposed under `compat/*` retain their owning client's lifecycle rather than becoming a general public composition API.
|
|
77
85
|
|
|
86
|
+
## UI commands
|
|
87
|
+
|
|
88
|
+
Import `declareUICommand` and `UICommandRegistry` from `@vrtmrz/livesync-commonlib/ui-commands`. A declaration binds a stable command ID to its request and result types. Each `UIService` owns its own `commands` registry; declarations do not register global handlers.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { declareUICommand } from "@vrtmrz/livesync-commonlib/ui-commands";
|
|
92
|
+
|
|
93
|
+
const showDevices = declareUICommand<void, 'closed'>('example:show-devices');
|
|
94
|
+
const unregister = services.UI.commands.register(showDevices, async () => {
|
|
95
|
+
await openHostDeviceDialogue();
|
|
96
|
+
return 'closed';
|
|
97
|
+
});
|
|
98
|
+
const result = await services.UI.commands.invoke(showDevices, undefined);
|
|
99
|
+
// result is { status: 'handled', value: 'closed' } or { status: 'unavailable' }.
|
|
100
|
+
unregister();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
A feature releases its handler when it unloads. The host dialogue owner remains responsible for active UI work. Duplicate handlers and conflicting declarations with the same ID are rejected; handler failures propagate. Calling an unavailable or disposed registry settles immediately. `UIService` disposes its registry on the context's plug-in-unloaded event.
|
|
104
|
+
|
|
105
|
+
## Joined device records
|
|
106
|
+
|
|
107
|
+
The `/replication` entry includes the `joined-devices` finite resource contract. A provider binds each resource to one settings snapshot; its owner calls `dispose()` in `finally`. `read()` returns recorded device IDs, acceptance, versions, platform, and connection times where available. An absent milestone is explicit. Joined-device operations never initialise a missing CouchDB database. These records do not establish online presence.
|
|
108
|
+
|
|
109
|
+
`forget(id)` removes one other device from all participation maps and the accepted list, while retaining the lock state, preferred tweaks, and unknown fields. The CouchDB implementation rereads the existing milestone before editing and verifies removal after saving. CouchDB local documents do not reject stale revisions, so concurrent changes to other device records can be overwritten. A failed verification is reported to the caller for manual refresh and retry. Object Storage checks ETags with conditional writes and retries conflicts a bounded number of times. Missing records, unsupported conditional writes, and exhausted conflicts have distinct results. Forgetting is not credential revocation: an unlocked remote permits a returning device to register again.
|
|
110
|
+
|
|
111
|
+
## Replication statistics
|
|
112
|
+
|
|
113
|
+
Hosts can read the latest `services.replicator.replicationStatics.value` and
|
|
114
|
+
subscribe to that reactive source. The focused `/replication` entry exports
|
|
115
|
+
`ReplicationStatics`, `CouchDBReplicationProgress`, and
|
|
116
|
+
`ReplicationCheckpointProgress`. Existing transfer counters and `syncStatus`
|
|
117
|
+
retain their meanings.
|
|
118
|
+
|
|
119
|
+
The optional `couchDB` snapshot describes the ordinary CouchDB replication
|
|
120
|
+
attempt. Other providers omit it. Its `phase` distinguishes `one-shot`,
|
|
121
|
+
`catch-up` (the pull preceding each continuous start or reconnect),
|
|
122
|
+
`continuous`, and `idle`. Preparation can expose a phase before directional
|
|
123
|
+
progress is available. This is independent of activity, pause, or failure in
|
|
124
|
+
`syncStatus`; entering `continuous` does not establish that Vault reflection
|
|
125
|
+
has finished or that editing is safe.
|
|
126
|
+
|
|
127
|
+
Optional `pull` and `push` observations each contain:
|
|
128
|
+
|
|
129
|
+
- `checkedChanges`: changes-feed entries presented for revision comparison,
|
|
130
|
+
including both existing and missing revisions. One entry with several
|
|
131
|
+
revisions counts once; repeated entries during retries can count again.
|
|
132
|
+
This is neither a Vault file count nor an exact count of skipped transfers.
|
|
133
|
+
- `completedBatches`: batches which reached PouchDB's checkpoint notification,
|
|
134
|
+
including batches which transferred no documents.
|
|
135
|
+
- `lastSeq`: the last processed checkpoint position, preserved as a number or
|
|
136
|
+
an opaque string. It is not a remaining-work estimate, percentage, or proof
|
|
137
|
+
that a checkpoint was durably written.
|
|
138
|
+
- `checking`: observed revision comparison in the current batch, cleared by
|
|
139
|
+
a transfer notification, pause, or termination. It does not establish that
|
|
140
|
+
network transfer has not begun.
|
|
141
|
+
|
|
142
|
+
Counts and positions reset for each underlying PouchDB handler, including the
|
|
143
|
+
transition from catch-up to continuous replication and a replacement attempt.
|
|
144
|
+
Completion, failure, and cancellation retain final values with `phase: 'idle'`
|
|
145
|
+
and `checking: false`. Replacing the provider clears its previous snapshot.
|
|
146
|
+
|
|
147
|
+
Checkpoint events are accumulated internally and published periodically
|
|
148
|
+
(currently about 200 ms). Transfer, pause, and terminal boundaries flush
|
|
149
|
+
pending observations promptly. The interval is an implementation detail.
|
|
150
|
+
The observer releases its timer and listeners on termination; late callbacks
|
|
151
|
+
cannot publish into the next attempt. These observations do not modify the
|
|
152
|
+
replication options, received documents, or native checkpoints.
|
|
153
|
+
|
|
78
154
|
## Rooted storage
|
|
79
155
|
|
|
80
156
|
Both platform storage factories receive an existing root from the host. They never select a directory, request browser permission, infer a process directory, or persist a handle.
|
|
@@ -191,11 +267,11 @@ The accepted replacement direction is an asynchronously created file client with
|
|
|
191
267
|
|
|
192
268
|
Package developers should read [the developer guide](docs/development.md). The focused contracts are described in [the storage guide](docs/platform-storage.md), [the standard-I/O guide](docs/platform-standard-io.md), [the settings lifecycle guide](docs/settings-lifecycle.md), [the remote configuration profile guide](docs/remote-configurations.md), [the remote feature compatibility contract](docs/remote-feature-compatibility.md), [the conflict-resolution guide](docs/conflict-resolution.md), and [the P2P transport lifecycle guide](docs/p2p-transport-lifecycle.md).
|
|
193
269
|
|
|
194
|
-
##
|
|
270
|
+
## Maintained-host examples and evidence
|
|
195
271
|
|
|
196
272
|
The package boundary is exercised by Self-hosted LiveSync's Obsidian plug-in, CLI, WebApp, and WebPeer compositions. Their validation is not uniform: the Obsidian plug-in and CLI have the strongest device and long-running synchronisation evidence, while WebApp and WebPeer have focused adapter and runtime tests, browser smoke tests, a cross-application P2P scenario ending at the production CLI, and public Pages smoke validation.
|
|
197
273
|
|
|
198
|
-
See [
|
|
274
|
+
See [Maintained-host examples and evidence](docs/proven-in-use.md) for composition points, downstream tests, and remaining limits. This optional page records examples and evidence; it does not define package support or create a release or adoption requirement. The examples deliberately separate focused public entries which a new consumer may copy from `compat/*` imports which exist to migrate the current application.
|
|
199
275
|
|
|
200
276
|
## Licence
|
|
201
277
|
|
|
@@ -24,6 +24,8 @@ export type DirectFileManipulatorOptions = {
|
|
|
24
24
|
maxChunksInEden?: number;
|
|
25
25
|
maxTotalLengthInEden?: number;
|
|
26
26
|
maxAgeInEden?: number;
|
|
27
|
+
/** Enables synchronisation of dot-prefixed internal files. */
|
|
28
|
+
syncInternalFiles?: boolean;
|
|
27
29
|
/**
|
|
28
30
|
* @deprecated use chunkSplitterVersion instead.
|
|
29
31
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MemoryValueStore } from "@vrtmrz/value-store";
|
|
1
2
|
import {
|
|
2
3
|
DEFAULT_SETTINGS,
|
|
3
4
|
DEFAULT_SYNC_PARAMETERS,
|
|
@@ -119,6 +120,7 @@ class DirectFileManipulator {
|
|
|
119
120
|
const getSettings = () => this.settings;
|
|
120
121
|
const context = new ServiceContext();
|
|
121
122
|
this.services = new HeadlessServiceHub(context, {
|
|
123
|
+
deviceValues: new MemoryValueStore(),
|
|
122
124
|
pouchDB: PouchDB,
|
|
123
125
|
database: this.getBoundDatabaseService(() => this.options, this.runtimeOptions),
|
|
124
126
|
getPathObfuscationPassphrase: () => this.options.obfuscatePassphrase ?? false,
|
|
@@ -253,6 +255,7 @@ class DirectFileManipulator {
|
|
|
253
255
|
handleFilenameCaseSensitive: this.options.handleFilenameCaseSensitive ?? DEFAULT_SETTINGS.handleFilenameCaseSensitive,
|
|
254
256
|
usePathObfuscation: !!this.options.obfuscatePassphrase,
|
|
255
257
|
encryptInternalMetadata: this.options.encryptInternalMetadata ?? false,
|
|
258
|
+
syncInternalFiles: this.options.syncInternalFiles ?? DEFAULT_SETTINGS.syncInternalFiles,
|
|
256
259
|
E2EEAlgorithm: this.options.E2EEAlgorithm ?? E2EEAlgorithms.V2
|
|
257
260
|
}
|
|
258
261
|
};
|
|
@@ -59,6 +59,7 @@ export declare function decodeSettingsFromSetupURI(uri: string, passphrase: stri
|
|
|
59
59
|
idDerivationKey: string;
|
|
60
60
|
pathNormalizationVersion: import("../settings.js").PathNormalizationVersion;
|
|
61
61
|
liveSync: boolean;
|
|
62
|
+
batchSaveInLiveSync?: boolean;
|
|
62
63
|
syncOnSave: boolean;
|
|
63
64
|
syncOnStart: boolean;
|
|
64
65
|
syncOnFileOpen: boolean;
|
|
@@ -53,6 +53,7 @@ export declare const DoctorRegulationV0_25_0: DoctorRegulation;
|
|
|
53
53
|
export declare const DoctorRegulationV0_25_27: DoctorRegulation;
|
|
54
54
|
export declare const DoctorRegulationV1_0_0: DoctorRegulation;
|
|
55
55
|
export declare const DoctorRegulationV1_0_33: DoctorRegulation;
|
|
56
|
+
export declare const DoctorRegulationV1_0_40: DoctorRegulation;
|
|
56
57
|
export declare const DoctorRegulation: DoctorRegulation;
|
|
57
58
|
export declare function checkUnsuitableValues(setting: Partial<ObsidianLiveSyncSettings>, regulation?: DoctorRegulation): DoctorRegulation;
|
|
58
59
|
export declare const RebuildOptions: {
|
|
@@ -139,7 +139,16 @@ const DoctorRegulationV1_0_33 = {
|
|
|
139
139
|
}
|
|
140
140
|
}
|
|
141
141
|
};
|
|
142
|
-
const
|
|
142
|
+
const DoctorRegulationV1_0_40 = {
|
|
143
|
+
// Only withdraw a recommendation. Retain the consultation version so users
|
|
144
|
+
// who dismissed 1.0.33 are not prompted again for unchanged recommendations.
|
|
145
|
+
version: DoctorRegulationV1_0_33.version,
|
|
146
|
+
rules: {
|
|
147
|
+
...DoctorRegulationV1_0_33.rules,
|
|
148
|
+
customChunkSize: void 0
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
const DoctorRegulation = DoctorRegulationV1_0_40;
|
|
143
152
|
function checkUnsuitableValues(setting, regulation = DoctorRegulation) {
|
|
144
153
|
const result = {
|
|
145
154
|
version: regulation.version,
|
|
@@ -339,6 +348,7 @@ export {
|
|
|
339
348
|
DoctorRegulationV0_25_27,
|
|
340
349
|
DoctorRegulationV1_0_0,
|
|
341
350
|
DoctorRegulationV1_0_33,
|
|
351
|
+
DoctorRegulationV1_0_40,
|
|
342
352
|
RebuildOptions,
|
|
343
353
|
RuleLevel,
|
|
344
354
|
checkUnsuitableValues,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { E2EEAlgorithms } from "./setting.const.js";
|
|
2
2
|
const PREFERRED_BASE = {
|
|
3
3
|
syncMaxSizeInMB: 50,
|
|
4
|
+
customChunkSize: 0,
|
|
4
5
|
chunkSplitterVersion: "v3-rabin-karp",
|
|
5
6
|
usePluginSyncV2: true,
|
|
6
7
|
handleFilenameCaseSensitive: false,
|
|
@@ -8,21 +9,18 @@ const PREFERRED_BASE = {
|
|
|
8
9
|
};
|
|
9
10
|
const PREFERRED_SETTING_CLOUDANT = {
|
|
10
11
|
...PREFERRED_BASE,
|
|
11
|
-
customChunkSize: 0,
|
|
12
12
|
sendChunksBulkMaxSize: 1,
|
|
13
13
|
concurrencyOfReadChunksOnline: 100,
|
|
14
14
|
minimumIntervalOfReadChunksOnline: 333
|
|
15
15
|
};
|
|
16
16
|
const PREFERRED_SETTING_SELF_HOSTED = {
|
|
17
17
|
...PREFERRED_BASE,
|
|
18
|
-
customChunkSize: 60,
|
|
19
18
|
sendChunksBulkMaxSize: 1,
|
|
20
19
|
concurrencyOfReadChunksOnline: 30,
|
|
21
20
|
minimumIntervalOfReadChunksOnline: 25
|
|
22
21
|
};
|
|
23
22
|
const PREFERRED_JOURNAL_SYNC = {
|
|
24
23
|
...PREFERRED_BASE,
|
|
25
|
-
customChunkSize: 10,
|
|
26
24
|
concurrencyOfReadChunksOnline: 30,
|
|
27
25
|
minimumIntervalOfReadChunksOnline: 25
|
|
28
26
|
};
|
|
@@ -53,6 +53,10 @@ export declare const SETTINGS_PROPERTY_POLICY: {
|
|
|
53
53
|
readonly markdown: "include";
|
|
54
54
|
readonly persistence: "include";
|
|
55
55
|
};
|
|
56
|
+
readonly batchSaveInLiveSync: {
|
|
57
|
+
readonly markdown: "include";
|
|
58
|
+
readonly persistence: "include";
|
|
59
|
+
};
|
|
56
60
|
readonly syncOnSave: {
|
|
57
61
|
readonly markdown: "include";
|
|
58
62
|
readonly persistence: "include";
|
|
@@ -14,6 +14,7 @@ const SettingPolicies = {
|
|
|
14
14
|
};
|
|
15
15
|
const SETTINGS_PROPERTY_POLICY = {
|
|
16
16
|
liveSync: SettingPolicies.Default,
|
|
17
|
+
batchSaveInLiveSync: SettingPolicies.Default,
|
|
17
18
|
syncOnSave: SettingPolicies.Default,
|
|
18
19
|
syncOnStart: SettingPolicies.Default,
|
|
19
20
|
syncOnFileOpen: SettingPolicies.Default,
|
|
@@ -80,6 +80,8 @@ interface SyncMethodSettings {
|
|
|
80
80
|
* Synchronise in Live. This is an exclusive setting against other sync methods.
|
|
81
81
|
*/
|
|
82
82
|
liveSync: boolean;
|
|
83
|
+
/** Explicitly enable batched local database updates in LiveSync mode; an absent value is false. */
|
|
84
|
+
batchSaveInLiveSync?: boolean;
|
|
83
85
|
/**
|
|
84
86
|
* automatically run sync on save.
|
|
85
87
|
* File modification will trigger the sync, even if the file is not changed on the editor.
|
|
@@ -10,6 +10,24 @@ export declare const DatabaseConnectingStatuses: {
|
|
|
10
10
|
readonly JOURNAL_RECEIVE: "JOURNAL_RECEIVE";
|
|
11
11
|
};
|
|
12
12
|
export type DatabaseConnectingStatus = (typeof DatabaseConnectingStatuses)[keyof typeof DatabaseConnectingStatuses];
|
|
13
|
+
/** The transfer stage; entering continuous replication does not imply Vault reflection is complete. */
|
|
14
|
+
export type CouchDBReplicationPhase = "idle" | "one-shot" | "catch-up" | "continuous";
|
|
15
|
+
export type ReplicationCheckpointProgress = {
|
|
16
|
+
/** Changes-feed entries presented for revision comparison, including repeated entries on retries. */
|
|
17
|
+
readonly checkedChanges: number;
|
|
18
|
+
/** Batches which reached PouchDB's checkpoint notification, including batches without transfers. */
|
|
19
|
+
readonly completedBatches: number;
|
|
20
|
+
/** Opaque processed position, not a remaining-document count or proof of durable checkpoint storage. */
|
|
21
|
+
readonly lastSeq?: string | number;
|
|
22
|
+
/** Revision comparison is progressing; this does not establish that network transfer has not begun. */
|
|
23
|
+
readonly checking: boolean;
|
|
24
|
+
};
|
|
25
|
+
/** Counters reset for each underlying PouchDB attempt, including the transition from catch-up to continuous. */
|
|
26
|
+
export type CouchDBReplicationProgress = {
|
|
27
|
+
readonly phase: CouchDBReplicationPhase;
|
|
28
|
+
readonly pull?: ReplicationCheckpointProgress;
|
|
29
|
+
readonly push?: ReplicationCheckpointProgress;
|
|
30
|
+
};
|
|
13
31
|
export type ReplicationStatics = {
|
|
14
32
|
sent: number;
|
|
15
33
|
arrived: number;
|
|
@@ -18,5 +36,7 @@ export type ReplicationStatics = {
|
|
|
18
36
|
lastSyncPullSeq: number;
|
|
19
37
|
lastSyncPushSeq: number;
|
|
20
38
|
syncStatus: DatabaseConnectingStatus;
|
|
39
|
+
/** Present only when the provider supplies CouchDB checkpoint observations. */
|
|
40
|
+
couchDB?: CouchDBReplicationProgress;
|
|
21
41
|
};
|
|
22
42
|
export declare const DEFAULT_REPLICATION_STATICS: ReplicationStatics;
|
|
@@ -142,6 +142,7 @@ export declare const TweakValuesTemplate: {
|
|
|
142
142
|
enableCompression: boolean;
|
|
143
143
|
handleFilenameCaseSensitive: boolean;
|
|
144
144
|
liveSync?: boolean;
|
|
145
|
+
batchSaveInLiveSync?: boolean;
|
|
145
146
|
syncOnSave?: boolean;
|
|
146
147
|
syncOnStart?: boolean;
|
|
147
148
|
syncOnFileOpen?: boolean;
|
|
@@ -144,6 +144,10 @@ const SettingInformation = {
|
|
|
144
144
|
name: "Batch database update",
|
|
145
145
|
desc: "Reducing the frequency with which on-disk changes are reflected into the DB"
|
|
146
146
|
},
|
|
147
|
+
batchSaveInLiveSync: {
|
|
148
|
+
name: "Batch database updates in LiveSync mode",
|
|
149
|
+
desc: "Wait for a bounded local quiet period before processing file updates in LiveSync mode, reducing intermediate database updates while delaying their reflection to remote devices."
|
|
150
|
+
},
|
|
147
151
|
readChunksOnline: {
|
|
148
152
|
name: "Fetch chunks on demand",
|
|
149
153
|
desc: "(ex. Read chunks online) If this option is enabled, LiveSync reads chunks online directly instead of replicating them locally. Increasing Custom chunk size is recommended."
|
|
@@ -313,11 +317,11 @@ const SettingInformation = {
|
|
|
313
317
|
},
|
|
314
318
|
batchSaveMinimumDelay: {
|
|
315
319
|
name: "Minimum delay for batch database updating",
|
|
316
|
-
desc: "Seconds.
|
|
320
|
+
desc: "Seconds. The quiet period after the latest pending update before local database processing becomes due."
|
|
317
321
|
},
|
|
318
322
|
batchSaveMaximumDelay: {
|
|
319
323
|
name: "Maximum delay for batch database updating",
|
|
320
|
-
desc: "
|
|
324
|
+
desc: "Seconds. The maximum intentional wait from the first pending update before local database processing becomes due. This does not guarantee when database processing, network delivery, or reflection to another device will complete."
|
|
321
325
|
},
|
|
322
326
|
notifyThresholdOfRemoteStorageSize: {
|
|
323
327
|
name: "Notify when the estimated remote storage size exceeds on start up",
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { FilePath, FilePathWithPrefix, MetaEntry } from "../common/models/db.type.js";
|
|
2
2
|
import type { UXFileInfo, UXFileInfoStub, UXInternalFileInfoStub } from "../common/models/fileaccess.type.js";
|
|
3
|
+
export interface StoreFileOptions {
|
|
4
|
+
/** Recheck storage and provenance, with bounded retries for recovery scans. */
|
|
5
|
+
revalidateStorage?: boolean;
|
|
6
|
+
}
|
|
3
7
|
export interface IFileHandler {
|
|
4
8
|
readFileFromStub(file: UXFileInfoStub | UXFileInfo): Promise<UXFileInfo>;
|
|
5
9
|
/**
|
|
@@ -8,7 +12,7 @@ export interface IFileHandler {
|
|
|
8
12
|
* operation never stores a document revision or writes to storage.
|
|
9
13
|
*/
|
|
10
14
|
tryRecordUntrackedFileRevision(info: UXFileInfoStub | UXFileInfo, expectedRevision: string): Promise<boolean>;
|
|
11
|
-
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean): Promise<boolean>;
|
|
15
|
+
storeFileToDB(info: UXFileInfoStub | UXFileInfo | UXInternalFileInfoStub | FilePathWithPrefix, force?: boolean, onlyChunks?: boolean, options?: StoreFileOptions): Promise<boolean>;
|
|
12
16
|
/**
|
|
13
17
|
* Store the current storage content as a child of an exact live database revision.
|
|
14
18
|
*
|
|
@@ -13,4 +13,9 @@ export declare abstract class StorageEventManager {
|
|
|
13
13
|
abstract isWaiting(filename: FilePath): boolean;
|
|
14
14
|
abstract waitForIdle(): Promise<void>;
|
|
15
15
|
abstract restoreState(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Complete accepted work before its storage closes. Custom hosts must stop
|
|
18
|
+
* their watcher admission before invoking this default implementation.
|
|
19
|
+
*/
|
|
20
|
+
stopWatching?(): Promise<void>;
|
|
16
21
|
}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
class StorageEventManager {
|
|
2
|
+
/**
|
|
3
|
+
* Complete accepted work before its storage closes. Custom hosts must stop
|
|
4
|
+
* their watcher admission before invoking this default implementation.
|
|
5
|
+
*/
|
|
6
|
+
stopWatching() {
|
|
7
|
+
return this.waitForIdle();
|
|
8
|
+
}
|
|
2
9
|
}
|
|
3
10
|
export {
|
|
4
11
|
StorageEventManager
|
|
@@ -206,7 +206,8 @@ async function putDBEntryInternal(host, managers, note, onlyChunks, revisionTarg
|
|
|
206
206
|
}
|
|
207
207
|
function isTargetFile(host, filenameSrc) {
|
|
208
208
|
const settings = host.services.setting.currentSettings();
|
|
209
|
-
const
|
|
209
|
+
const isInternalFile = filenameSrc.startsWith(ICHeader);
|
|
210
|
+
const file = isInternalFile ? filenameSrc.substring(ICHeader.length) : filenameSrc;
|
|
210
211
|
if (file.startsWith(ICXHeader)) return true;
|
|
211
212
|
if (file.startsWith(PSCHeader)) return true;
|
|
212
213
|
if (file.startsWith(IDPrefixes.Chunk) || file.startsWith(IDPrefixes.Obfuscated)) {
|
|
@@ -217,7 +218,7 @@ function isTargetFile(host, filenameSrc) {
|
|
|
217
218
|
return false;
|
|
218
219
|
}
|
|
219
220
|
}
|
|
220
|
-
if (settings.syncOnlyRegEx) {
|
|
221
|
+
if (!isInternalFile && settings.syncOnlyRegEx) {
|
|
221
222
|
const syncOnly = getFileRegExp(settings, "syncOnlyRegEx");
|
|
222
223
|
if (syncOnly.length > 0 && !syncOnly.some((e) => e.test(file))) return false;
|
|
223
224
|
}
|
|
@@ -71,26 +71,35 @@ export declare abstract class StorageEventManagerBase<TAdapter extends IStorageE
|
|
|
71
71
|
* Restore and complete the previous storage-event snapshot when it exists.
|
|
72
72
|
*/
|
|
73
73
|
restoreState(): Promise<void>;
|
|
74
|
+
private acceptingEvents;
|
|
75
|
+
private admissions;
|
|
74
76
|
appendQueue(params: FileEvent[], ctx?: unknown): Promise<void>;
|
|
77
|
+
private appendQueueInternal;
|
|
75
78
|
protected bufferedQueuedItems: (FileEventItem | FileEventItemSentinel)[];
|
|
79
|
+
private pendingSnapshot;
|
|
80
|
+
private operations;
|
|
81
|
+
private latestByPath;
|
|
82
|
+
private nextSequence;
|
|
83
|
+
private snapshotTail;
|
|
84
|
+
private dispatching;
|
|
85
|
+
private retainEvent;
|
|
86
|
+
private reportOperationFailure;
|
|
87
|
+
private failOperation;
|
|
76
88
|
enqueue(newItem: FileEventItem): void;
|
|
77
|
-
/**
|
|
78
|
-
* Immediately take snapshot.
|
|
79
|
-
*/
|
|
80
89
|
private _triggerTakeSnapshot;
|
|
81
|
-
/**
|
|
82
|
-
* Trigger taking snapshot after throttled period.
|
|
83
|
-
*/
|
|
84
90
|
triggerTakeSnapshot: import("octagonal-wheels/function").ThrottledFunction<() => void>;
|
|
85
91
|
protected concurrentProcessing: import("octagonal-wheels/concurrency/semaphore_v2").SemaphoreObject;
|
|
86
92
|
protected _waitingMap: Map<string, WaitInfo>;
|
|
87
|
-
private
|
|
93
|
+
private pendingOperations;
|
|
94
|
+
private releaseOperations;
|
|
95
|
+
private waitForOperations;
|
|
88
96
|
/**
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* @returns
|
|
97
|
+
* Complete the finite set of operations accepted before this call.
|
|
98
|
+
* Later edits cannot replace or extend that set while it is being flushed.
|
|
92
99
|
*/
|
|
93
100
|
waitForIdle(): Promise<void>;
|
|
101
|
+
/** Stop watcher admission before draining or preserving accepted work. */
|
|
102
|
+
stopWatching(): Promise<void>;
|
|
94
103
|
/**
|
|
95
104
|
* Proceed waiting for the given key immediately.
|
|
96
105
|
*/
|