@vrtmrz/livesync-commonlib 0.2.0 → 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.
Files changed (41) hide show
  1. package/README.md +43 -0
  2. package/dist/API/processSetting.d.ts +1 -0
  3. package/dist/common/models/setting.const.defaults.js +1 -0
  4. package/dist/common/models/setting.const.qr.js +1 -0
  5. package/dist/common/models/setting.policy.d.ts +4 -0
  6. package/dist/common/models/setting.policy.js +1 -0
  7. package/dist/common/models/setting.type.d.ts +2 -0
  8. package/dist/common/models/shared.definition.d.ts +20 -0
  9. package/dist/common/models/tweak.definition.d.ts +1 -0
  10. package/dist/common/settingConstants.d.ts +1 -0
  11. package/dist/common/settingConstants.js +6 -2
  12. package/dist/interfaces/FileHandler.d.ts +5 -1
  13. package/dist/interfaces/StorageEventManager.d.ts +5 -0
  14. package/dist/interfaces/StorageEventManager.js +7 -0
  15. package/dist/managers/StorageEventManager.d.ts +19 -10
  16. package/dist/managers/StorageEventManager.js +259 -189
  17. package/dist/replication/LiveSyncAbstractReplicator.d.ts +2 -0
  18. package/dist/replication/LiveSyncAbstractReplicator.js +5 -2
  19. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +6 -0
  20. package/dist/replication/couchdb/LiveSyncReplicator.js +39 -2
  21. package/dist/replication/couchdb/replicationProgress.d.ts +6 -0
  22. package/dist/replication/couchdb/replicationProgress.js +105 -0
  23. package/dist/replication/index.d.ts +1 -0
  24. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -0
  25. package/dist/serviceFeatures/offlineScanner.js +19 -7
  26. package/dist/serviceModules/ServiceFileAccessBase.js +5 -0
  27. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +8 -5
  28. package/dist/serviceModules/ServiceFileHandlerBase.js +75 -38
  29. package/dist/services/base/AppLifecycleService.js +1 -1
  30. package/dist/services/base/ControlService.js +6 -2
  31. package/dist/services/base/FileProcessingService.d.ts +7 -1
  32. package/dist/services/base/FileProcessingService.js +9 -1
  33. package/dist/services/base/IService.d.ts +9 -1
  34. package/dist/services/base/ReplicatorService.d.ts +1 -0
  35. package/dist/services/lib/HandlerUtils.d.ts +60 -0
  36. package/dist/services/lib/HandlerUtils.js +46 -0
  37. package/dist/worker/bgWorker.js +1 -1
  38. package/docs/offline-scanner.md +15 -13
  39. package/docs/settings-lifecycle.md +2 -0
  40. package/docs/storage-events-and-reflection.md +109 -10
  41. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  ---
2
- date: 2026-09-04
3
- commonlib-version: "0.1.21"
4
- self-hosted-livesync-version: "1.0.24"
2
+ date: 2026-10-25
3
+ commonlib-version: "0.2.0"
4
+ self-hosted-livesync-version: "1.0.40"
5
5
  status: unreleased
6
6
  ---
7
7
 
@@ -31,15 +31,15 @@ The tables below cover only paths which remain after identity validation, the ho
31
31
 
32
32
  The `initialized` marker records that a full-scan invocation has reached its aggregate-result boundary and that later invocations may restore stored storage-event operations. It is not a successful-scan marker: the first invocation records it after an aggregate `false` result, `true`, or an accepted partial result. An exception before the aggregate result is produced does not reach that write.
33
33
 
34
- Storage-event restoration is a bounded start-up recovery step. Restored events bypass their former batch delay, but `restoreState()` waits for their actual file operations to finish and preserves snapshot sentinel ordering before the file collections are compared. Saved events run first because a deletion or rename can carry operation intent which is no longer visible in the current storage listing. An individual restored operation failure is logged and does not prevent the scan from examining the resulting current state. The following full scan then makes the final reconciliation decision.
34
+ Storage-event restoration is a bounded start-up recovery step. Restored events bypass their former batch delay, but `restoreState()` waits for their actual handler outcomes and snapshot ordering before the file collections are compared. Saved events run first because a deletion or rename can carry operation intent which is no longer visible in the current storage listing. An individual restored operation failure remains in the pending snapshot and is logged; it does not prevent the scan from examining current state or independent paths. The following full scan then makes the final reconciliation decision.
35
35
 
36
- This ordering does not mean that storage unconditionally overwrites the replay result. The action matrix remains authoritative: when a replayed operation leaves an ambiguous pair, such as a storage file alongside a deleted database entry, the selected policy may deliberately return `skipped` rather than guess which side is intended. This boundary does not include events which are buffered or received later, and it must not be confused with a host's separate replication-result queue.
36
+ This ordering does not mean that storage unconditionally overwrites the replay result. The action matrix remains authoritative: when a replayed operation leaves an ambiguous pair, such as a storage file alongside a deleted database entry, the selected policy may deliberately return `skipped` rather than guess which side is intended. A failed replay does not become a completed operation; a later scan may still reconcile its current pair. This start-up recovery boundary is separate from later watcher work and a host's replication-result queue.
37
37
 
38
38
  ### Stored event interpretation
39
39
 
40
- The storage-event snapshot contains operations which the previous runtime observed but had not begun or which were still waiting for their batch boundary. It is not a file-content journal and does not discover changes made while the application was stopped. An operation which had already been removed from the queue and entered file processing is also outside the persisted boundary. The following full scan remains responsible for observing the current storage state across all of these gaps.
40
+ The storage-event snapshot retains every accepted operation while it is buffered, waiting for a batch boundary, or in a handler. It remains there until the operation has a durable local result, is proved already represented or safely covered by a successful successor, or ends in a skip explicitly allowed by policy. A `false` handler result or exception leaves the saved operation pending. A failed initial snapshot prevents the handler from starting; a later snapshot failure is reported and cannot be treated as completion. Ordered snapshot writes prevent an older asynchronous save from replacing a newer pending view. The snapshot records operation intent, not file content, and cannot recover bytes or events which the host never persisted or delivered; the full scan continues to observe current storage state.
41
41
 
42
- A restored event is an operation intent which must be revalidated against the current exact storage path before it can cause a database side effect. Its saved file stub, timestamps, and optional editor cache describe the earlier observation; they are not authoritative file content after a restart. Whenever an inclusion is still applicable, the handler reads a fresh stub and content from current storage. Whenever an ordinary deletion's expected absence is contradicted by a current storage item, that deletion is suppressed and the full scan evaluates that current item instead.
42
+ A restored event is an operation intent which must be revalidated against the current exact storage path before it can cause a database side effect. Its saved file stub, timestamps, and optional editor cache describe the earlier observation; they are not authoritative file content after a restart. Whenever an inclusion is still applicable, the handler reads a fresh stub and current content when the operation runs. Whenever an ordinary deletion's expected absence is contradicted by a current storage item, that deletion is suppressed and the full scan evaluates that current item instead.
43
43
 
44
44
  For this check, an exact path means that the canonical path returned by storage has the saved spelling after ordinary separator normalisation. A file returned only through a case-insensitive lookup does not prove the saved inclusion path. A same-ID rename remains the explicit case-change path described below.
45
45
 
@@ -58,7 +58,7 @@ The standard-file revalidation contract is:
58
58
  | Case-only or otherwise same-ID `RENAME` | A current selected file resolves to the renamed canonical path | Update that single document's path and current content without a separate deletion |
59
59
  | Case-only or otherwise same-ID `RENAME` | The current identity does not support the saved rename | Do not replay the rename; let the full scan evaluate the current path |
60
60
 
61
- An inability to inspect the current path is not proof of absence. Revalidation therefore suppresses a destructive half when storage inspection fails, records the failure, and leaves the following scan to report whether reconciliation can proceed. Target-file policy and file-size limits are evaluated from current settings rather than inherited from the saved event.
61
+ An inability to inspect the current path is not proof of absence. Revalidation suppresses a destructive half when storage inspection fails, records the failure, and leaves the operation pending. The full scan can still examine the current state and reports any selected-pair failure under its strict or explicitly partial completion policy. Target-file policy and file-size limits are evaluated from current settings rather than inherited from the saved event.
62
62
 
63
63
  The unreleased rename-exclusion guard retains `renameTarget` when a rename is converted to deletion during target filtering. Unlike an ordinary deletion, this intent can remove a file from synchronisation without removing it from storage. The guard distinguishes deliberate exclusion from transient case-collision rejection, preserves a recreated source, and permits a deliberately excluded same-ID destination without requiring that destination to disappear. Older snapshots without this optional field retain the absence-only rule. See [Storage events and database-to-storage reflection](storage-events-and-reflection.md) for the exact admission conditions and the separate ordinary watcher boundary.
64
64
 
@@ -138,11 +138,13 @@ By default, the complete scan returns `false` when any pair is `failed`, and ret
138
138
 
139
139
  Self-hosted LiveSync enables this policy only for ordinary Obsidian start-up, where one unavailable path must not prevent unaffected files from synchronising. Fast Setup, Fetch, Rebuild, direct scans, and CLI mirror or daemon paths retain the strict default.
140
140
 
141
- ## Equal-time files without a recorded revision
141
+ ## Files in the same timestamp bucket
142
142
 
143
- An equal modification time does not establish that the storage file is unchanged. Before completing such a pair, the scanner asks the file handler to record its current database revision if device-local provenance is absent. The handler reads the actual storage and database content under its document lock and records the revision only when both contain the same bytes, the database has no conflicting leaf, and its current revision agrees with the one observed by the scan. It rechecks the storage snapshot, database revision, and absent provenance before writing the record. This operation writes neither storage content nor a database revision.
143
+ For a `both` pair which selects `NEWER_WINS` (`sync-newer`), equal raw modification times and distinct times within the platform tolerance do not establish that the storage file is unchanged. The scanner first asks the file handler to record provenance for the current database revision only when current storage and one unambiguous live revision have matching content. The handler checks the storage bytes and database revision under the document lock, then revalidates the storage snapshot, current revision, and absent provenance before writing the record. This check writes neither storage content nor a database revision. The existing selection, file-size, and conflict policies still apply, and the `DB_APPLY` action remains unchanged.
144
144
 
145
- An existing provenance record keeps its branch identity even when the file bytes match another revision. An unreadable record, unavailable content, differing bytes, a competing branch, or a changing snapshot leaves provenance untouched. The scan still completes that equal-time pair; a later file event or incoming reflection retains the ordinary conservative conflict policy. If the local database has already advanced past the file's content, the equal-time repair cannot establish its earlier origin.
145
+ The result of `tryRecordUntrackedFileRevision()` is not an edit predicate. Its `false` can mean that provenance already exists, content differs, history conflicts, required content or provenance is unavailable, or state changed during verification. When it returns `false`, the scanner uses the ordinary, non-forced `storeFileToDB` path with bounded storage revalidation; it does not force the Vault file into the current database winner. The file handler retains a known branch when readable provenance identifies it, applies newer database state when the storage bytes match that recorded older revision, and saves a genuine edit on its recorded branch.
146
+
147
+ A valid saved content hash can prove that unchanged bytes still represent a recorded revision whose database content has been pruned. A hash cannot reconstruct ancestry for edited bytes. If the recorded base is pruned and the current bytes do not match its saved hash, the handler may preserve readable storage as an independent branch when it matches no current live leaf; it must not attach those bytes to an arbitrary database winner. This unknown-origin case is distinct from an unavailable comparison: if required storage, provenance, database content, or revalidation cannot be read, the operation remains unresolved and the pair fails for retry. Repeated storage changes are bounded and also remain failed rather than being reported as reconciled.
146
148
 
147
149
  ## Result-state verification
148
150
 
@@ -156,12 +158,12 @@ Focused two-pass coverage starts with a `DB_APPLY` reflection whose file handler
156
158
 
157
159
  Full-scan coverage also verifies that the strict default returns `false`, an ordinary-start-up scan which explicitly continues returns `completed-with-file-failures` while logging the affected path at verbose level, and a later clean scan returns `true`.
158
160
 
159
- The equal-time regression uses real in-memory PouchDB and the file handler. It checks missing and recorded provenance with unchanged files and genuine local edits, then advances the database and reflects the incoming revision. Focused handler tests cover provenance read and write failures, unreadable current content, an existing conflict, and storage or database changes during verification.
161
+ The equal-time regression uses real in-memory PouchDB and the file handler. It checks missing and recorded provenance with unchanged files and genuine local edits at both identical raw timestamps and distinct raw timestamps within one comparison bucket. It verifies that edits are preserved during the scan, then advances the database and checks that an incoming revision retains the local branch. It also verifies that a matching saved hash remains useful after its database revision is pruned, while edited bytes with an unhashed pruned base and no matching live leaf are preserved independently. Focused scanner tests verify that a failed ordinary save returns a failed pair. Focused handler tests cover provenance read and write failures, unavailable recorded-base content, an existing conflict, and storage or database changes during verification.
160
162
 
161
163
  A second two-pass case starts with a `db-only` entry skipped by the size limit. It then removes that limit and verifies that the entry is reflected to storage rather than misclassified as an offline local deletion. Coverage for an existing storage file verifies that a failed `sync-newer` reflection retains the observed storage mtime, and full-scan coverage verifies that the aggregate failure reaches its caller after scanner initialisation completes.
162
164
 
163
165
  Identity coverage verifies early quarantine when no consistent target exists, continued processing when consistent Metadata represents the same logical path, expired-deletion retention, special-namespace routing, actual-ID-first inspection, stale and ambiguous repair rejection, target-first ordering, exact-target retry, source preservation after failure, and last-seen clearing.
164
166
 
165
- Stored-event coverage verifies that current storage content replaces saved observations, a recreated path suppresses a stale deletion, distinct-ID rename halves are admitted independently from current path state, and a same-ID rename never performs a separate deletion. It also verifies that a storage-inspection failure does not become evidence for a destructive operation and that an individual replay failure does not prevent the full scan from running.
167
+ Stored-event coverage verifies that current storage content replaces saved observations, a recreated path suppresses a stale deletion, distinct-ID rename halves are admitted independently from current path state, and a same-ID rename never performs a separate deletion. It also verifies that a storage-inspection failure does not become evidence for a destructive operation, that failed work remains pending, and that an individual replay failure does not prevent the full scan from running.
166
168
 
167
169
  Maintain that interaction boundary when changing file-handler results, last-seen persistence, Fast Setup reflection, or offline deletion detection.
@@ -68,6 +68,8 @@ if (prepared.requiresSyncReview) {
68
68
 
69
69
  Explicit stored values take precedence over every fallback. A migration therefore preserves synchronisation switches and other user choices unless a migration step documents a deliberate transformation.
70
70
 
71
+ `batchSaveInLiveSync` is a separate explicit choice for bounded local database batching in LiveSync mode. Both new and existing settings default to `false`; the legacy pair `liveSync: true, batchSave: true` does not enable it. The public setting property is optional for existing host settings shapes, and a missing value remains disabled. Other modes continue to use `batchSave`. Markdown settings and QR data preserve the explicit choice, while older imports without the property do not infer it from `batchSave`. Hosts should preserve both choices during ordinary mode changes and use the shared minimum and maximum delays.
72
+
71
73
  The sleep preferences are included in Setup URI encoding. When an older Setup URI or stored setting does not contain either key, `allowSleepDuringSynchronisation` defaults to `false`, while `allowSleepDuringSynchronisationOnDesktop` defaults to `true`. The maintained Obsidian host therefore retains best-effort screen-awake protection on mobile but allows normal automatic sleep on desktop unless the user selects otherwise.
72
74
 
73
75
  An existing settings store without an explicit `handleFilenameCaseSensitive` value is normalised to `false` and saved. This preserves the effective behaviour of earlier releases, where an absent value followed the case-insensitive branch. An explicit `true` or `false` value is preserved. New Vaults also select case-insensitive handling (`false`).
@@ -1,16 +1,18 @@
1
1
  ---
2
- date: 2026-09-05
3
- commonlib-version: "0.1.23"
4
- self-hosted-livesync-version: "1.0.25"
2
+ date: 2026-10-25
3
+ commonlib-version: "0.2.0"
4
+ self-hosted-livesync-version: "1.0.40"
5
+ status: unreleased
5
6
  ---
6
7
 
7
8
  # Storage events and database-to-storage reflection
8
9
 
9
10
  This note separates storage-to-database event handling from
10
- database-to-storage reflection. It describes the Commonlib 0.1.23 guard against
11
- a stale `DELETE` notification logically deleting Metadata while the same file
12
- is still present in storage. The version fields identify the reviewed source
13
- versions.
11
+ database-to-storage reflection. It documents the current guard against a stale
12
+ `DELETE` notification logically deleting Metadata while the same file is still
13
+ present in storage, and the accepted local completion contract for queued file
14
+ operations. The version fields identify the reviewed source versions; these
15
+ changes remain unreleased.
14
16
 
15
17
  ## Processing directions
16
18
 
@@ -46,6 +48,101 @@ and unsynchronised storage content retain their existing reflection rules.
46
48
  The watcher guard does not intercept this path or general explicit database
47
49
  deletion operations.
48
50
 
51
+ ## Pending operations and local completion
52
+
53
+ Once admitted, a file operation remains pending while buffered, waiting for its
54
+ batch boundary, or running in a handler. The manager includes all three states
55
+ in its existing event snapshot. It saves snapshots in order, so an older write
56
+ cannot replace a newer view of pending work. The manager acknowledges an
57
+ ordinary file operation only after its effect is durable locally, current state proves the
58
+ effect is already represented or safely covered by a successful successor, or
59
+ the handler returns a skip explicitly allowed by policy. A handler
60
+ failure or exception leaves the saved operation pending. A failed initial
61
+ snapshot prevents the handler from starting; a later snapshot failure is
62
+ reported and cannot be treated as completion. Completion of an older operation
63
+ cannot remove a newer operation for the same path.
64
+
65
+ The event snapshot records operation intent, not authoritative current file
66
+ content. Admission does not read a file body merely to wait. When a `CREATE` or
67
+ `CHANGED` operation becomes eligible, the handler rechecks the current path and
68
+ obtains its current content for processing. Restored events are revalidated
69
+ against current storage and selection before they can affect the database. The
70
+ saved stub and any optional cache are not used as a historical file version.
71
+ The current-storage deletion checks and rename-exclusion rules below continue to
72
+ apply.
73
+
74
+ A restored deletion whose database entry is already absent or deleted completes
75
+ without another revision. A restored rename whose source deletion is already
76
+ represented reconciles the current target through the ordinary non-forced
77
+ handler. The same applies to a case-only rename when Metadata already records
78
+ the target spelling. Replaying either completed operation preserves later local
79
+ edits. If the source is still live and a target already exists, the existing
80
+ collision refusal remains in force: the snapshot does not prove that this
81
+ rename created that target, so the unresolved operation remains pending.
82
+
83
+ `commitPendingFileEvents()` requests immediate processing of the finite set of
84
+ operations accepted before the call, including work which is buffered,
85
+ waiting, or already in a handler. It waits for each actual handler result and
86
+ for the ordered snapshot update which records that result; releasing a timer is
87
+ not completion. Later events do not extend that captured boundary. A newer
88
+ coalesced operation satisfies an earlier one only when its successful result
89
+ covers the earlier intent. A failed handler or snapshot update is reported to
90
+ the caller and leaves unresolved work in the snapshot. This boundary covers
91
+ local completion under the selected handler contract only; it does not prove replication or receipt by another
92
+ Vault.
93
+
94
+ A `DELETE` or `RENAME` ordering sentinel waits for its predecessors only. It
95
+ must not wait for itself or events which follow it in the dispatch queue. Batch
96
+ timers make work eligible to run; they do not perform it. The quiet period
97
+ follows the latest eligible content update, while the maximum intentional delay
98
+ is measured from the first uncommitted update, starting when the queue call is
99
+ accepted and before asynchronous filtering or the touched-file check. Waiting
100
+ timers do not occupy a processing slot, and time spent waiting for a slot does
101
+ not restart the maximum delay. Once due, the operation acquires a slot and reads
102
+ current content for processing.
103
+
104
+ ## Optional file handlers
105
+
106
+ `INTERNAL` events and files marked `isInternal` use their owning feature's
107
+ contract. Hosts can register `processOptionalFileEventWithResult` handlers with
108
+ `firstHandled`. `HandlerOutcome<TValue = void, TVetoReason = void>` carries a
109
+ success value and a veto reason independently; a non-void payload is required
110
+ in its branch. Dispatch follows priority and registration order:
111
+
112
+ | Outcome | Dispatch and completion |
113
+ | --- | --- |
114
+ | `not-applicable` | Try the next provider. If none applies, complete the policy-approved skip. |
115
+ | `completed` | Stop and complete the event after the provider's actual save or verified no-op. |
116
+ | `veto` | Stop and retain the event with a reported refusal reason. |
117
+ | Exception or rejection | Propagate the original error and retain the event. |
118
+
119
+ A completed result stops dispatch immediately; a later provider cannot veto
120
+ it. Empty registration returns `not-applicable`. False or zero payloads remain
121
+ valid values. Providers must resolve their promise only after the operation
122
+ finishes. Snapshot acknowledgement still follows the handler result, and a
123
+ snapshot failure keeps the event recoverable.
124
+
125
+ The optional `whenFlushRequested` promise resolves when a finite completion
126
+ boundary includes this event. A provider which debounces saves uses it to
127
+ release that event's captured delay. It must wait for the corresponding save,
128
+ settle all notifications coalesced into that save, and keep later groups outside
129
+ the captured boundary. This signal is runtime state; it does not change the
130
+ persisted snapshot format.
131
+
132
+ Migrate all optional providers in a host together. Once the first typed provider
133
+ is registered, the manager uses the typed chain exclusively, even when every
134
+ provider returns `not-applicable`. A host without a registered typed provider
135
+ retains `processOptionalFileEvent` as a legacy notification contract. Its
136
+ boolean does not confirm persistence: a resolved notification completes only
137
+ that dispatch, while a rejection which reaches the manager is still retained.
138
+ The existing boolean aggregators and ordinary file contract are unchanged.
139
+
140
+ During start-up replay and settings or unload flushes, providers use actual
141
+ database readiness and initialise their required local state before saving.
142
+ They must not wait for an application-ready or resume event which occurs only
143
+ after that same flush. An unavailable prerequisite returns a veto; the manager
144
+ does not introduce an automatic retry loop for a failed operation.
145
+
49
146
  ## Event intent and current storage state
50
147
 
51
148
  | Event intent | Current storage observation | Admitted operation |
@@ -92,9 +189,11 @@ incoming logical deletion can still remove unchanged storage content.
92
189
 
93
190
  These tests do not establish the native watcher event sequence reported in
94
191
  Issue 1168. Real-runtime verification belongs to the host and must distinguish
95
- observed events from inferred causes. This change does not promise path-case
96
- convergence or full folder-rename support. It does not rewrite the queue,
97
- introduce a retry subsystem, or change the database schema.
192
+ observed events from inferred causes. The watcher guard does not promise
193
+ path-case convergence or full folder-rename support. Focused manager tests cover
194
+ finite completion boundaries, ordered snapshots, retained failures, replay, and
195
+ batch timing. This work adds no second journal, unbounded retry loop, or
196
+ database-schema change.
98
197
 
99
198
  The maintained host's real-runtime comparison is recorded in
100
199
  [Proven in maintained hosts](proven-in-use.md#obsidian-plug-in), separately
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Shared data, storage, and replication primitives for Self-hosted LiveSync clients.",
5
5
  "type": "module",
6
6
  "types": "./dist/index.d.ts",
@@ -1010,7 +1010,7 @@
1010
1010
  "events": "^3.3.0",
1011
1011
  "fflate": "^0.8.2",
1012
1012
  "idb": "^8.0.3",
1013
- "markdown-it": "^14.3.2",
1013
+ "markdown-it": "^14.3.3",
1014
1014
  "minimatch": "^10.2.6",
1015
1015
  "octagonal-wheels": "^0.1.55",
1016
1016
  "pouchdb-adapter-http": "^9.0.0",