@vrtmrz/livesync-commonlib 0.2.1 → 0.2.3

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 (70) hide show
  1. package/README.md +60 -0
  2. package/dist/API/processSetting.d.ts +1 -0
  3. package/dist/ContentSplitter/ContentSplitterRabinKarp.js +2 -17
  4. package/dist/ContentSplitter/ContentSplitterV1.js +9 -19
  5. package/dist/ContentSplitter/ContentSplitterV2.js +9 -19
  6. package/dist/common/models/setting.const.defaults.js +1 -0
  7. package/dist/common/models/setting.const.qr.js +2 -0
  8. package/dist/common/models/setting.policy.d.ts +4 -0
  9. package/dist/common/models/setting.policy.js +1 -0
  10. package/dist/common/models/setting.type.d.ts +4 -0
  11. package/dist/common/models/tweak.definition.d.ts +1 -0
  12. package/dist/common/settingConstants.d.ts +1 -0
  13. package/dist/common/settingConstants.js +4 -0
  14. package/dist/common/utils.d.ts +33 -3
  15. package/dist/common/utils.js +12 -44
  16. package/dist/common/utils.object.js +2 -1
  17. package/dist/concurrency/keyedPendingWork.d.ts +13 -0
  18. package/dist/concurrency/keyedPendingWork.js +27 -0
  19. package/dist/conflicts/conflictCheckQueue.d.ts +45 -0
  20. package/dist/conflicts/conflictCheckQueue.js +108 -0
  21. package/dist/conflicts.d.ts +3 -0
  22. package/dist/conflicts.js +4 -0
  23. package/dist/diagnostics/integrity.js +11 -12
  24. package/dist/interfaces/FileHandler.d.ts +2 -0
  25. package/dist/interfaces/ServiceModule.d.ts +42 -24
  26. package/dist/interfaces/ServiceModule.js +4 -3
  27. package/dist/interfaces/StorageAccess.d.ts +2 -0
  28. package/dist/interfaces/StorageEventManager.d.ts +2 -0
  29. package/dist/interfaces/StorageEventManager.js +4 -0
  30. package/dist/managers/StorageEventManager.d.ts +4 -3
  31. package/dist/managers/StorageEventManager.js +24 -6
  32. package/dist/pouchdb/LiveSyncLocalDB.js +1 -0
  33. package/dist/pouchdb/chunks.d.ts +16 -0
  34. package/dist/pouchdb/pouchdb-browser.js +2 -84
  35. package/dist/pouchdb/pouchdb-http.js +2 -84
  36. package/dist/pouchdb/purgeMulti.d.ts +4 -0
  37. package/dist/pouchdb/purgeMulti.js +89 -0
  38. package/dist/replication/journal/JournalSyncCore.js +6 -1
  39. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -0
  40. package/dist/replication/trystero/TrysteroReplicator.js +3 -4
  41. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +3 -2
  42. package/dist/replication/trystero/peerPatterns.d.ts +1 -0
  43. package/dist/replication/trystero/peerPatterns.js +6 -0
  44. package/dist/rpc/RpcRoom.d.ts +5 -1
  45. package/dist/rpc/RpcRoom.js +80 -105
  46. package/dist/serviceFeatures/offlineScanner.d.ts +8 -8
  47. package/dist/serviceFeatures/offlineScanner.js +25 -21
  48. package/dist/serviceFeatures/prepareDatabaseForUse.d.ts +4 -3
  49. package/dist/serviceFeatures/prepareDatabaseForUse.js +5 -4
  50. package/dist/serviceFeatures/remoteConfig.d.ts +10 -5
  51. package/dist/serviceModules/ServiceFileAccessBase.d.ts +1 -0
  52. package/dist/serviceModules/ServiceFileAccessBase.js +3 -0
  53. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +1 -0
  54. package/dist/serviceModules/ServiceFileHandlerBase.js +23 -7
  55. package/dist/services/base/DatabaseEventService.d.ts +1 -1
  56. package/dist/services/base/IService.d.ts +3 -3
  57. package/dist/services/base/VaultScanResult.d.ts +5 -0
  58. package/dist/services/base/VaultService.d.ts +1 -1
  59. package/dist/services/implements/injectable/InjectableConflictService.d.ts +5 -5
  60. package/dist/services/lib/HandlerUtils.d.ts +8 -6
  61. package/dist/services/lib/HandlerUtils.js +14 -0
  62. package/dist/worker/bgWorker.js +1 -1
  63. package/docs/conflict-resolution.md +30 -0
  64. package/docs/development.md +46 -0
  65. package/docs/offline-scanner.md +12 -4
  66. package/docs/proven-in-use.md +63 -0
  67. package/docs/releasing.md +2 -0
  68. package/docs/service-feature-composition.md +56 -9
  69. package/docs/storage-events-and-reflection.md +6 -2
  70. package/package.json +10 -2
@@ -72,6 +72,36 @@ Each duplicate collapse, conservative merge, or host-directed manual choice is c
72
72
 
73
73
  A host must not apply a dialogue result after either compared revision has ceased to be the current pair. It should discard that stale result, refresh its warning or dialogue state, and queue the path again when conflicts remain.
74
74
 
75
+ ## Scheduling conflict checks
76
+
77
+ The unreleased `@vrtmrz/livesync-commonlib/conflicts` entry exports
78
+ `createConflictCheckQueue`. The host supplies a `process(path, scope)` callback
79
+ and may observe pending plus active checks through `onProgress`. Concurrency
80
+ defaults to ten and includes each item's deferred cleanup. The host continues
81
+ to own active-file filtering, optional conflict handlers, revision decisions,
82
+ and serialisation of shared user interactions.
83
+
84
+ `enqueue(path)` accepts work synchronously. A duplicate pending path replaces
85
+ its earlier entry and moves to the tail; an executing check is unchanged.
86
+ An executing check may enqueue its path again, including while every execution
87
+ slot is occupied. Pending paths have no fixed capacity, and there is no delay
88
+ or batching policy.
89
+
90
+ `waitForIdle()` waits until accepted work, new arrivals, requeued checks, and
91
+ cleanup have finished. It leaves admission open for later checks and returns
92
+ `true` on quiescence. `close()` stops admission and drains accepted work.
93
+ `abort()` stops admission and further dispatch, waits for started work and
94
+ cleanup, and returns a snapshot of the unstarted paths. It does not forcibly
95
+ cancel a running callback; `waitForIdle()` returns `false` after abort.
96
+ A processor must not await its own queue's completion, closure, or abort.
97
+
98
+ An unhandled processing, cleanup, or progress-observer error stops dispatch.
99
+ Completion methods reject only after started work and cleanup settle, while
100
+ `pending` retains the unstarted paths for inspection. Catch and report an item
101
+ error inside `process` when the host deliberately intends to continue with
102
+ later items. The queue performs no implicit retry, logging, persistence, or
103
+ recovery. Enqueueing after close, abort, or failure throws.
104
+
75
105
  ## File-reflection provenance
76
106
 
77
107
  The compatibility implementation accepts an injected, device-local `FileReflectionProvenance` capability owned by the database-to-storage composition rather than by a filesystem adapter. Maintained hosts persist:
@@ -7,6 +7,7 @@ This document is for Commonlib developers. Package consumers should begin with t
7
7
  The source tree is larger than the supported package surface. Consumers may import only paths in the generated package export map:
8
8
 
9
9
  - `src/index.ts`, `src/context.ts`, `src/settings.ts`, `src/remoteConfigurations.ts`, `src/p2p/index.ts`, `src/platform/browser/index.ts`, and `src/platform/node/index.ts` define the focused entries;
10
+ - `src/conflicts.ts` defines the host-owned conflict-check queue contract; `src/concurrency/keyedPendingWork.ts` remains package-private;
10
11
  - `src/replication/index.ts` defines the focused provider-capability and interaction contract for hosts which compose replication;
11
12
  - `src/diagnostics/index.ts` defines read-only Usage and integrity inspection, report projection, and formatting; hosts own the run dialogue and output destination;
12
13
  - `src/rpc/index.ts` defines a transitional entry for the existing LiveSync P2P composition, not a stable Commonlib 1.0 contract;
@@ -37,6 +38,10 @@ Commonlib uses four related forms of composition. Choose between them by ownersh
37
38
  - A **ServiceModule** is a long-lived stateful or resource-owning object constructed by the host and shared through the typed `ServiceModules` record. Use one when several consumers need the same operational capability or lifecycle, such as storage access, file handling, or database rebuilding. State alone does not make a ServiceModule necessary.
38
39
  - An **AbstractModule** is the legacy application module layer. Existing modules may continue to own application behaviour, but new composition should not acquire the broad core merely to gain lifecycle access or shared dependencies.
39
40
 
41
+ `NecessaryServices<T, U, THub>` selects dependencies from a chosen Service Hub source, defaulting to `ServiceHub`, and retains `context` only when that source exposes it. Declare additional collaborators in the feature's own dependency type. `createServiceFeature()` preserves the supplied function's complete signature and result, but does not infer dependency keys from its body; annotate the host parameter when a narrow dependency contract is intended. Unannotated legacy callbacks retain the default `ServiceHub` contract for compatibility.
42
+
43
+ Keep operation host types focused on the dependencies the operation uses, and let the composer add the Services required for logging, lifecycle access, or handler registration. Where an exported compatibility host must remain broad, use narrower private host types for the implementation. The examples in [Service feature composition](service-feature-composition.md) show these separate boundaries.
44
+
40
45
  When a feature needs state, separate the component which owns that state, its transitions, and its invariants from the code which connects it to application lifecycle events and downstream effects. Give the stateful component narrow collaborators or callbacks rather than the complete Service Hub or application core. Keep application-level handler registration and host-specific presentation in the surrounding serviceFeature. A handler or effect which is part of the state contract, such as querying unresolved errors or reporting their first occurrence, may remain inside the stateful component when its input and output ports are explicit and bounded.
41
46
 
42
47
  Settings storage follows the same ownership boundary. Each host selects one exact-key asynchronous `ValueStore & ValueDeleter` at its composition root and supplies it as the setting service's required `deviceValues` dependency. `setting.deviceValues` is that root; `setting.config` wraps it with the existing Vault and database-suffix scope, and the Service Hub `config` property aliases the same view. Commonlib does not discover browser globals or provide a module-global fallback. Keep existing local keys on the exact-key root, preserve configuration key names, and await Value Store operations. See [Settings lifecycle](settings-lifecycle.md#host-storage-for-device-values-and-configuration) for the key mapping and host example.
@@ -149,6 +154,37 @@ Commonlib publishes the typed English messages which it owns and uses them when
149
154
 
150
155
  The [maintained-host examples and evidence](proven-in-use.md) are optional context, not a support entitlement or release or adoption gate. When that page is updated, distinguish current use, historical use, Commonlib-owned results, downstream tests, and real-runtime observations, and keep each claim within the evidence.
151
156
 
157
+ ### Testing an unpublished Octagonal Wheels dependency
158
+
159
+ The current integration pins the published Octagonal Wheels 0.1.56 release and
160
+ uses the ordinary package gate. The override below is for future unpublished
161
+ dependency changes.
162
+
163
+ Keep the repository dependency declaration and lockfile separate from a local
164
+ candidate check. After building and packing Octagonal Wheels, install that
165
+ exact tarball for source checks without saving it to either manifest:
166
+
167
+ ```bash
168
+ npm ci
169
+ npm install --no-save --ignore-scripts /path/to/octagonal-wheels-candidate.tgz
170
+ npm run check:types
171
+ npm test -- --maxWorkers=1 --fileParallelism=false
172
+ npm run test:package -- --octagonal-wheels-tarball /path/to/octagonal-wheels-candidate.tgz
173
+ ```
174
+
175
+ The package checker adds the selected tarball and an explicit npm override
176
+ only to its disposable consumer fixture. It reports the tarball's SHA-256 and
177
+ checks that Commonlib resolves that same installed package. The generated
178
+ Commonlib package retains its original dependency declaration. The option
179
+ does not publish a package or select a future release version.
180
+
181
+ The Blob migration's source tests cover legacy construction and Commonlib's
182
+ document interpretation. Its packed fixtures compile the old imports and
183
+ exercise the generated runtime. A passing candidate check qualifies that
184
+ combination of artefacts; before publication, update the declared dependency
185
+ to a released version containing the new entry, regenerate the lockfile, and
186
+ run the ordinary package gate without the candidate override.
187
+
152
188
  ## Integration tests
153
189
 
154
190
  The integration suite owns Commonlib's direct CouchDB and S3-compatible storage checks. Run it with managed, disposable CouchDB and RustFS containers:
@@ -200,6 +236,8 @@ The platform and RPC ownership decisions are accepted:
200
236
 
201
237
  The package build records each compatibility path explicitly. Do not add a wildcard export. A new compatibility path requires a real downstream import, a packed-consumer or downstream test, and a migration plan towards a focused public entry or the owning package.
202
238
 
239
+ The existing `compat/services/lib` helpers `bindableFunction` and `lazyBindableFunction` return an `UnregisterFunction` from `setHandler()`. It removes only the assignment made by that registration while it remains current; a stale remover, including after an override with the same callback, and repeated removals are no-ops. Existing callers may ignore the return value, but implementations and test doubles of the handler interfaces must return a remover. Keep this contract within the existing compatibility boundary rather than presenting it as a general SDK pattern.
240
+
203
241
  After the reviewed Self-hosted LiveSync revision changes, inspect its current compatibility imports with:
204
242
 
205
243
  ```bash
@@ -211,3 +249,11 @@ The command recognises both the former `@lib/*` source aliases and current `@vrt
211
249
  The classification in `docs/migration/compatibility-classification.json` is accepted for LiveSync 1.0 planning. A destination still requires its own result and lifecycle contract before it becomes a stable public entry. Remove obsolete paths rather than documenting accidental layout as API.
212
250
 
213
251
  Release preparation and staged-publication checks are documented in [the maintainer runbook](releasing.md).
252
+
253
+
254
+ The shared value extraction uses the declared Octagonal Wheels dependency. Its
255
+ `utils.runtime-tools.unit.spec.ts` tests preserve the legacy adapter contracts;
256
+ `runtime-tools-types.ts` and `runtime-tools-runtime.mjs` check the installed
257
+ package's public duration and delimiter helpers; source tests cover the
258
+ internal JSON copy adapter. When testing a future unpublished dependency, keep
259
+ these checks tied to the same Octagonal Wheels tarball used by the downstream consumer.
@@ -1,6 +1,6 @@
1
1
  ---
2
- date: 2026-10-25
3
- commonlib-version: "0.2.0"
2
+ date: 2026-10-26
3
+ commonlib-version: "0.2.1"
4
4
  self-hosted-livesync-version: "1.0.40"
5
5
  status: unreleased
6
6
  ---
@@ -29,6 +29,14 @@ The tables below cover only paths which remain after identity validation, the ho
29
29
  6. after the first permitted invocation reaches its aggregate-result boundary, record `initialized = true`; and
30
30
  7. return the aggregate pair result under the caller's selected handling of file failures.
31
31
 
32
+ ### Startup content checking
33
+
34
+ `VaultScanOptions.strictContentCheck` is optional. `initialiseDatabase` accepts `VaultScanOptions` as its optional fifth argument, and `scanVault` accepts it as its optional fourth argument and forwards it. `performFullScan` exposes the same option through `FullScanOptions`. Omitting the option, or setting it to `true`, retains strict content comparison. Only an explicit `false` permits the scanner to pass the `canSkipUnchangedFile` hint to the file handler. Commonlib does not read a host setting or global storage; a host may select this option from a per-installation startup preference.
35
+
36
+ The hint is considered only when the normal freshness comparison reaches `EVEN`, replacing the additional body comparison at that point. It never bypasses `BASE_IS_NEW` or `TARGET_IS_NEW`; those branches retain their timestamp-directed reflection and last-seen bookkeeping. Inside `EVEN`, the hint is allowed only for a tracked file whose raw modification time exactly equals the recorded provenance mtime (not merely a value in the same platform comparison bucket), whose byte size matches the recorded value, whose tracked revision is the current live database revision, and which has no live conflicts or related queued, running, or failed file work. If tracking evidence is missing, or the host cannot establish pending-work state, the existing strict byte-comparison path remains. When the hint is accepted, the pair result is `SKIPPED`; it does not compare or claim equal bytes, write provenance, or introduce Commonlib persistent state. Incoming content, conflict handling, and logical-deletion protection retain their existing rules.
37
+
38
+ A local edit which preserves the tracked modification time and byte size may be missed by a scan using explicit `false`. Existing same-stat edit checks exercise the omitted-option strict path.
39
+
32
40
  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
41
 
34
42
  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.
@@ -140,7 +148,7 @@ Self-hosted LiveSync enables this policy only for ordinary Obsidian start-up, wh
140
148
 
141
149
  ## Files in the same timestamp bucket
142
150
 
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.
151
+ For a `both` pair which selects `NEWER_WINS` (`sync-newer`), the normal freshness comparison can reach `EVEN` for equal raw modification times or distinct times within the platform tolerance; those timestamps alone do not establish that the storage file is unchanged. Under the omitted-option strict path, the scanner 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. With explicit `strictContentCheck: false`, the scanner may request `canSkipUnchangedFile` only in the `EVEN` branch, and only when the raw mtime exactly matches the recorded provenance mtime; a value merely in the same tolerance bucket does not qualify. The `BASE_IS_NEW` and `TARGET_IS_NEW` branches retain their existing timestamp-directed reflection and last-seen bookkeeping. The existing selection, file-size, and conflict policies still apply, and the `DB_APPLY` action remains unchanged.
144
152
 
145
153
  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
154
 
@@ -158,7 +166,7 @@ Focused two-pass coverage starts with a `DB_APPLY` reflection whose file handler
158
166
 
159
167
  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`.
160
168
 
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.
169
+ The equal-time regression uses real in-memory PouchDB and the file handler with `strictContentCheck` omitted, keeping its same-stat edit assertions on the strict default path. 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.
162
170
 
163
171
  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.
164
172
 
@@ -6,6 +6,39 @@ The useful evidence is host- and contract-specific. Focused Commonlib tests esta
6
6
 
7
7
  The code paths below identify maintained Self-hosted LiveSync composition points as examples. The reviewed downstream revision and compatibility imports are recorded in [`migration/downstream-imports.json`](migration/downstream-imports.json), which is regenerated by the package-boundary tooling rather than copied into prose links. These snapshots may become historical as the maintained downstream changes; this page is optional context, not a support entitlement or release or adoption gate.
8
8
 
9
+ ## Blob construction and comparison: candidate integration
10
+
11
+ The unreleased Blob extraction delegates construction to
12
+ `octagonal-wheels/binary/blob`. Commonlib retains the old creation imports and
13
+ wraps `toBlob` as `createBlob`, while document classification, binary decoding,
14
+ and content-comparison policy remain in Commonlib. The legacy
15
+ `isDocContentSame` delegates its sliced fallback to `isBlobContentEqual`,
16
+ passing the existing Commonlib encoder as the equality predicate. Its native
17
+ IndexedDB path remains in Commonlib.
18
+
19
+ The [legacy result tests](../src/common/utils.blob.unit.spec.ts) establish the
20
+ same behaviour before and after delegation: Blob identity and MIME, text
21
+ parts and line endings, binary view bounds and snapshots, stored-document
22
+ decoding, and representation-independent byte comparison. The
23
+ [comparison contract tests](../src/common/utils.blob-comparison.unit.spec.ts)
24
+ cover size and MIME handling, sequential bounded reads, early termination,
25
+ IndexedDB availability timing, method and receiver selection, Buffer fallback,
26
+ and unchanged failures. [Composition tests](../src/common/utils.blob-delegation.unit.spec.ts)
27
+ spy on the real shared comparison to verify fallback delegation and its
28
+ absence on the native path. The
29
+ [packed type fixture](../_tools/fixtures/blob-compatibility-types.ts) and
30
+ [runtime fixture](../_tools/fixtures/blob-compatibility-runtime.mjs) exercise
31
+ the existing Commonlib imports after packaging.
32
+
33
+ Earlier candidate checks explicitly installed the selected unpublished Octagonal
34
+ Wheels tarball in the disposable consumer. The current unreleased Commonlib
35
+ integration pins the published Octagonal Wheels 0.1.56 dependency and uses the
36
+ ordinary package gate without a candidate override. Self-hosted LiveSync tests its hidden-file
37
+ loading and revision handling through the legacy entry with matching
38
+ candidate tarballs. Those consumer checks are distinct from Commonlib-owned
39
+ compatibility contracts; real-Obsidian and mobile validation of this extraction
40
+ remain pending.
41
+
9
42
  ## Obsidian plug-in
10
43
 
11
44
  The unreleased V3 Rabin–Karp correction uses proportional binary thresholds,
@@ -219,3 +252,33 @@ Use the named consumer paths as integration examples, while retaining the owners
219
252
  ## Scope and maintenance
220
253
 
221
254
  This optional page preserves examples, current integration evidence, and history. It does not define supported behaviour or create a release or adoption gate; package guarantees are described by the published contracts and release policy. When this page is updated, distinguish current use, historical use, Commonlib-owned tests, downstream tests, and real-runtime observations, and keep each claim within its evidence.
255
+
256
+
257
+ ## Shared value helpers: candidate integration
258
+
259
+ The [runtime helper tests](../src/common/utils.runtime-tools.unit.spec.ts)
260
+ characterise duration thresholds, delimiter empty parts, and JSON conversion
261
+ before and after delegation. Commonlib owns the tagged list adapter and
262
+ internal `asCopy<T>` signature. Replication services use that internal adapter
263
+ to detach settings before an operation. The shared package owns the value
264
+ operations. The packed [type fixture](../_tools/fixtures/runtime-tools-types.ts) and
265
+ [runtime fixture](../_tools/fixtures/runtime-tools-runtime.mjs) exercise those
266
+ public duration and delimiter compatibility entries. The original checks used an
267
+ unpublished dependency candidate; the current integration pins the published
268
+ Octagonal Wheels 0.1.56 release. Source tests cover the internal copy adapter.
269
+ LiveSync continues to use the existing compatibility entry points, including
270
+ duration labels in ConfigSync and tagged lists in settings. Source and packed
271
+ checks do not establish real-Obsidian rendering or device behaviour.
272
+
273
+
274
+ ## Shared multi-key locking: candidate integration
275
+
276
+ The unreleased candidate imports `withLocks` directly from
277
+ `octagonal-wheels/concurrency/lock` for offline Metadata repair and file-event
278
+ processing. Commonlib retains the `processFileEvent-` namespace, unique sorted
279
+ document IDs, and conflict hooks deferred until every lock has been released.
280
+ The [file-handler tests](../src/serviceModules/ServiceFileHandlerBase.unit.spec.ts)
281
+ exercise a rename alongside Metadata repair, including independent keys,
282
+ reversed and repeated input IDs, and callback failure. These are Commonlib-owned
283
+ composition checks using the selected dependency; Octagonal Wheels owns the
284
+ primitive's lock acquisition, result propagation, and release contracts.
package/docs/releasing.md CHANGED
@@ -38,6 +38,8 @@ When a named host-specific risk is not established by the Commonlib owner checks
38
38
 
39
39
  The Commonlib 0.2 policy begins with 0.2.0 and does not retroactively change compatibility expectations for published 0.1.x versions. Within 0.2.x, preserve published APIs, types, behaviour, supported persisted data, and exported `compat/*` paths. Intentional breaking changes belong in 0.3.0. Consumers may declare a range such as `^0.2.0`; lockfiles and `npm ci` remain the reproducible install path.
40
40
 
41
+ For 0.2.3, the documented exceptions are the `HandlerFunction.setHandler` and `LazyHandlerFunction.setHandler` return types changing from `void` to `UnregisterFunction`, and `timeDeltaToHumanReadable(Infinity)` returning `∞y` instead of `Infinityy`. The setter types now describe the existing runtime return. Custom implementations and test doubles whose setters return `void` must provide an unregister function; ordinary callers may continue to ignore the result. These are the specific 0.2.3 exceptions to the policy.
42
+
41
43
  ## Preparing a release
42
44
 
43
45
  Choose the version explicitly. For the 0.2 line, start at 0.2.0. A stable candidate can use its final version on the `next` dist-tag, then be checked by exact source revision and tarball SHA-256 before the same version is promoted to `latest`. No `-next.N` or `-rc.N` version is required; use a separate prerelease version only when it is useful for a distinct candidate. Package-proof versions are local artefacts and cannot be staged.
@@ -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
 
@@ -24,7 +24,7 @@ The choice is primarily about dependency direction and ownership. Mutable state
24
24
 
25
25
  ## Declare dependencies at the boundary
26
26
 
27
- Use `NecessaryServices` to state the exact Services and ServiceModules which a feature consumes:
27
+ Use `NecessaryServices` to state the exact Services and ServiceModules which a feature consumes. Its type parameters select service keys, module keys, and the Service Hub source; the source defaults to `ServiceHub`. The selected Services retain `context` when that source exposes it. The `NecessaryServicesInterfaces` alias continues to use `IServiceHub` without adding an implicit context to that interface contract.
28
28
 
29
29
  ```typescript
30
30
  type IndexFeatureHost = NecessaryServices<"appLifecycle" | "API", "storageAccess">;
@@ -41,6 +41,20 @@ export const useIndexFeature = createServiceFeature((host: IndexFeatureHost) =>
41
41
  });
42
42
  ```
43
43
 
44
+ Select a more specific host when a feature needs an injectable handler type or host-extended context. Collaborators outside the Service Hub and ServiceModules, such as a borrowed logger, belong in the feature's own dependency type:
45
+
46
+ ```typescript
47
+ type FeatureHost = NecessaryServices<
48
+ "appLifecycle" | "conflict",
49
+ never,
50
+ InjectableServiceHub
51
+ > & {
52
+ readonly log: LogFunction;
53
+ };
54
+ ```
55
+
56
+ `createServiceFeature()` returns the supplied function unchanged. Its declared host type, optional or additional arguments, generic or overloaded signature, and result type, including a `Promise` or focused view, remain available to callers. Dependency keys are not inferred from the callback body. An unannotated legacy callback retains the full default `ServiceHub` contract for compatibility, so annotate the host parameter when a narrow contract is intended. Existing `createServiceFeature<T, U, Result>(...)` calls remain supported. The `serviceFeature<T, U, THub>().create(...)` builder also selects a source and preserves the supplied function type. Its existing `.create<Result>(...)` call shape and `withContext()` behaviour are unchanged.
57
+
44
58
  This signature is both documentation and a compile-time boundary. Adding an unrelated Service to the host object does not make it available to the feature. If the operation needs another collaborator, add it deliberately to the declared contract.
45
59
 
46
60
  A serviceFeature is a composition function, not a runtime registry entry. Call it from the host composition after its dependencies exist:
@@ -65,13 +79,17 @@ When an operation has meaningful sequencing or failure behaviour, keep it indepe
65
79
  `prepareDatabaseForUse.ts` follows this split. Its operation accepts explicit collaborators and returns the application result:
66
80
 
67
81
  ```typescript
68
- type PrepareDatabaseHost = NecessaryServices<
69
- "appLifecycle" | "setting" | "vault" | "path" | "database" | "databaseEvents" | "fileProcessing" | "replicator",
82
+ type PrepareDatabaseOperationHost = NecessaryServices<
83
+ "appLifecycle" | "vault" | "database" | "databaseEvents" | "fileProcessing" | "replicator",
84
+ never
85
+ >;
86
+ type PrepareDatabaseFeatureHost = NecessaryServices<
87
+ "API" | "appLifecycle" | "vault" | "database" | "databaseEvents" | "fileProcessing" | "replicator",
70
88
  never
71
89
  >;
72
90
 
73
91
  export async function prepareDatabaseForUse(
74
- host: PrepareDatabaseHost,
92
+ host: PrepareDatabaseOperationHost,
75
93
  log: LogFunction,
76
94
  errorManager: UnresolvedErrorManager,
77
95
  showingNotice = false,
@@ -83,10 +101,10 @@ export async function prepareDatabaseForUse(
83
101
  }
84
102
  ```
85
103
 
86
- The corresponding composition constructs the owned error manager and registers one adapter function:
104
+ The operation does not need `setting` or `path`. The corresponding composition adds `API` to create the instance logger, constructs the owned error manager, and registers one adapter function:
87
105
 
88
106
  ```typescript
89
- export function usePrepareDatabaseForUse(host: PrepareDatabaseHost): void {
107
+ export function usePrepareDatabaseForUse(host: PrepareDatabaseFeatureHost): void {
90
108
  const log = createInstanceLogFunction("SF:prepareDatabaseForUse", host.services.API);
91
109
  const errorManager = new UnresolvedErrorManager(host.services.appLifecycle, host.services.context.events);
92
110
 
@@ -107,6 +125,35 @@ export function usePrepareDatabaseForUse(host: PrepareDatabaseHost): void {
107
125
 
108
126
  Test the operation's sequencing and failure short-circuiting separately from the handler registration. This keeps a lifecycle rename from forcing every domain test through an application-shaped fixture.
109
127
 
128
+ The offline scanner follows the same split. A full-scan operation uses only the file and database dependencies, while the composer adds `API` for its logger and `appLifecycle` for its error manager and handler registration:
129
+
130
+ ```typescript
131
+ type FullScanHost = NecessaryServices<
132
+ "setting" | "vault" | "path" | "database" | "keyValueDB",
133
+ "storageAccess" | "fileHandler"
134
+ >;
135
+
136
+ type OfflineScannerFeatureHost = NecessaryServices<
137
+ "API" | "appLifecycle" | "setting" | "vault" | "path" | "database" | "keyValueDB",
138
+ "storageAccess" | "fileHandler"
139
+ >;
140
+ ```
141
+
142
+ `performFullScan` uses the full-scan host, and its scan operations declare their own narrower service sets; none requires `fileProcessing`. `useOfflineScanner` binds the `vault.scanVault` handler with the composer host. The scanner does not require `databaseEvents` or `replicator`.
143
+
144
+ ### Separate remote-configuration operation and composition hosts
145
+
146
+ `RemoteConfigHost` remains the broad exported compatibility type for existing callers. The implementation uses private host types so each operation or composer declares only its required Services:
147
+
148
+ | Implementation boundary | Private host type | Required Services |
149
+ | --- | --- | --- |
150
+ | Remote configuration migration operation | `RemoteConfigMigrationHost` | `setting`, `API` |
151
+ | Migration feature composer | `RemoteConfigMigrationFeatureHost` | `setting`, `API`, `appLifecycle` |
152
+ | Switch-connection command | `RemoteConfigSwitchHost` | `setting`, `UI`, `control`, `API` |
153
+ | Replication command and feature composer | `RemoteConfigReplicationHost` | `setting`, `UI`, `replication`, `control`, `API` |
154
+
155
+ Keep the broad export for compatibility and use the focused private types inside the implementation.
156
+
110
157
  Database preparation and full scanning remain strict by default. A host may set `continueOnFileFailure` only when its ordinary start-up policy deliberately permits readiness after individual file-pair failures. In that case, the operation returns `completed-with-file-failures` after all later initialisation steps succeed; it does not collapse the result into `true`. That option does not admit a failed scan precondition or a later initialisation step, and explicit recovery workflows retain the default.
111
158
 
112
159
  The `scanVault` and `initialiseDatabase` services use a failure-veto result aggregator for this contract. It preserves the first non-boolean continuation result but still invokes later handlers, so a later `false` result or exception rejects the operation. `firstResult` is not suitable here because it would return the partial result before those handlers can veto it.
@@ -1,6 +1,6 @@
1
1
  ---
2
- date: 2026-10-25
3
- commonlib-version: "0.2.0"
2
+ date: 2026-10-26
3
+ commonlib-version: "0.2.1"
4
4
  self-hosted-livesync-version: "1.0.40"
5
5
  status: unreleased
6
6
  ---
@@ -48,6 +48,10 @@ and unsynchronised storage content retain their existing reflection rules.
48
48
  The watcher guard does not intercept this path or general explicit database
49
49
  deletion operations.
50
50
 
51
+ ## Startup scans and pending operations
52
+
53
+ The full scanner's optional `strictContentCheck: false` path is gated by the host's file-operation state. Any related queued, running, or failed operation blocks the `canSkipUnchangedFile` hint. The host must provide an available query before the scanner can establish that no related work exists; without that query or other required evidence, strict content comparison remains active. The hint is considered only after normal freshness comparison reaches `EVEN`, and requires the raw mtime to exactly match the recorded provenance value; a match within the platform comparison bucket is insufficient. `BASE_IS_NEW` and `TARGET_IS_NEW` retain their timestamp-directed reflection and last-seen bookkeeping. A pair returned as `SKIPPED` by this hint does not acknowledge or remove a saved event. Event work continues to follow the snapshot and handler-completion contract below. This option affects only the full-scan same-stat check; ordinary watcher `CREATE` and `CHANGED` handling, replicated writes, and logical-deletion protection retain their existing rules. See [Full offline scan](offline-scanner.md#startup-content-checking) for the full option and eligibility conditions.
54
+
51
55
  ## Pending operations and local completion
52
56
 
53
57
  Once admitted, a file operation remains pending while buffered, waiting for its
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
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",
@@ -42,6 +42,11 @@
42
42
  "import": "./dist/platform/browser/index.js",
43
43
  "default": "./dist/platform/browser/index.js"
44
44
  },
45
+ "./conflicts": {
46
+ "types": "./dist/conflicts.d.ts",
47
+ "import": "./dist/conflicts.js",
48
+ "default": "./dist/conflicts.js"
49
+ },
45
50
  "./context": {
46
51
  "types": "./dist/context.d.ts",
47
52
  "import": "./dist/context.js",
@@ -640,6 +645,9 @@
640
645
  "browser": [
641
646
  "dist/platform/browser/index.d.ts"
642
647
  ],
648
+ "conflicts": [
649
+ "dist/conflicts.d.ts"
650
+ ],
643
651
  "context": [
644
652
  "dist/context.d.ts"
645
653
  ],
@@ -1012,7 +1020,7 @@
1012
1020
  "idb": "^8.0.3",
1013
1021
  "markdown-it": "^14.3.3",
1014
1022
  "minimatch": "^10.2.6",
1015
- "octagonal-wheels": "^0.1.55",
1023
+ "octagonal-wheels": "0.1.56",
1016
1024
  "pouchdb-adapter-http": "^9.0.0",
1017
1025
  "pouchdb-adapter-idb": "^9.0.0",
1018
1026
  "pouchdb-adapter-indexeddb": "^9.0.0",