@vrtmrz/livesync-commonlib 0.1.44 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +37 -4
  2. package/dist/API/DirectFileManipulatorV2.d.ts +2 -0
  3. package/dist/API/DirectFileManipulatorV2.js +3 -0
  4. package/dist/common/configForDoc.d.ts +1 -0
  5. package/dist/common/configForDoc.js +11 -1
  6. package/dist/common/models/db.definition.d.ts +1 -0
  7. package/dist/common/models/setting.const.preferred.js +1 -3
  8. package/dist/managers/EntryManager/EntryManagerImpls.js +3 -2
  9. package/dist/pouchdb/LiveSyncDBFunctions.js +4 -0
  10. package/dist/pouchdb/StreamingFetch.d.ts +5 -1
  11. package/dist/pouchdb/StreamingFetch.js +529 -65
  12. package/dist/replication/RemoteResource.d.ts +23 -2
  13. package/dist/replication/RemoteResource.js +6 -2
  14. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +8 -0
  15. package/dist/replication/couchdb/LiveSyncReplicator.js +132 -1
  16. package/dist/replication/couchdb/fastForward.d.ts +19 -0
  17. package/dist/replication/couchdb/fastForward.js +119 -0
  18. package/dist/replication/index.d.ts +3 -1
  19. package/dist/replication/joinedDevices.d.ts +25 -0
  20. package/dist/replication/joinedDevices.js +123 -0
  21. package/dist/replication/journal/JournalSyncCore.d.ts +2 -1
  22. package/dist/replication/journal/JournalSyncCore.js +11 -1
  23. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +4 -0
  24. package/dist/replication/journal/LiveSyncJournalReplicator.js +56 -1
  25. package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +3 -0
  26. package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -1
  27. package/dist/replication/journal/objectstore/MinioStorageAdapter.js +27 -1
  28. package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +1 -1
  29. package/dist/replication/trystero/P2PRoomSessionOwner.js +27 -14
  30. package/dist/serviceModules/Rebuilder.js +41 -15
  31. package/dist/services/BrowserServices.d.ts +4 -2
  32. package/dist/services/BrowserServices.js +2 -5
  33. package/dist/services/HeadlessServices.d.ts +3 -0
  34. package/dist/services/HeadlessServices.js +2 -5
  35. package/dist/services/ServiceHub.d.ts +4 -4
  36. package/dist/services/base/IService.d.ts +10 -18
  37. package/dist/services/base/ReplicatorService.remoteResourceResolver.js +1 -1
  38. package/dist/services/base/SettingService.d.ts +8 -18
  39. package/dist/services/base/SettingService.js +28 -46
  40. package/dist/services/implements/base/SvelteDialog.d.ts +2 -2
  41. package/dist/services/implements/base/UIService.d.ts +2 -0
  42. package/dist/services/implements/base/UIService.js +5 -0
  43. package/dist/services/implements/injectable/InjectableServiceHub.d.ts +4 -4
  44. package/dist/services/implements/injectable/InjectableServices.d.ts +2 -2
  45. package/dist/services/implements/injectable/InjectableSettingService.d.ts +0 -3
  46. package/dist/services/implements/injectable/InjectableSettingService.js +0 -11
  47. package/dist/string_and_binary/chunks.js +7 -5
  48. package/dist/uiCommands.d.ts +26 -0
  49. package/dist/uiCommands.js +57 -0
  50. package/dist/worker/bgWorker.js +1 -1
  51. package/docs/development.md +15 -3
  52. package/docs/proven-in-use.md +38 -4
  53. package/docs/releasing.md +20 -13
  54. package/docs/settings-lifecycle.md +37 -5
  55. package/package.json +12 -17
  56. package/dist/services/base/ConfigService.d.ts +0 -7
  57. package/dist/services/base/ConfigService.js +0 -6
  58. package/dist/services/implements/browser/ConfigServiceBrowserCompat.d.ts +0 -16
  59. 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
- The package is pre-1.0. Pin an exact reviewed version, and review changes before upgrading. 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.
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/compat/*` | Exact legacy imports still required by existing clients | Migration-only; paths may be removed as consumers migrate |
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,31 @@ 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
+
78
111
  ## Rooted storage
79
112
 
80
113
  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 +224,11 @@ The accepted replacement direction is an asynchronously created file client with
191
224
 
192
225
  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
226
 
194
- ## Proven in maintained hosts
227
+ ## Maintained-host examples and evidence
195
228
 
196
229
  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
230
 
198
- See [Proven in maintained hosts](docs/proven-in-use.md) for the actual composition points, downstream tests, and remaining limits. The examples deliberately separate focused public entries which a new consumer may copy from `compat/*` imports which exist to migrate the current application.
231
+ 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
232
 
200
233
  ## Licence
201
234
 
@@ -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
  };
@@ -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 DoctorRegulation = DoctorRegulationV1_0_33;
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,
@@ -20,6 +20,7 @@ export interface DeviceInfo {
20
20
  */
21
21
  plugin_version: string;
22
22
  progress: string;
23
+ platform?: string;
23
24
  }
24
25
  export interface NodeData extends DeviceInfo {
25
26
  /**
@@ -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
  };
@@ -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 file = filenameSrc.startsWith(ICHeader) ? filenameSrc.substring(ICHeader.length) : filenameSrc;
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
  }
@@ -86,6 +86,10 @@ async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentV
86
86
  writeMilestone = true;
87
87
  }
88
88
  }
89
+ if (nodeDeviceInfo.platform !== void 0 && info.platform !== nodeDeviceInfo.platform) {
90
+ remoteMilestone.node_info[deviceNodeID].platform = nodeDeviceInfo.platform;
91
+ writeMilestone = true;
92
+ }
89
93
  const diffLastConnected = now - (remoteMilestone.node_info[deviceNodeID].last_connected || 0);
90
94
  if (diffLastConnected > 6e4) {
91
95
  remoteMilestone.node_info[deviceNodeID].last_connected = now;
@@ -54,6 +54,10 @@ export type FetchChangesForInitialSyncProgress = {
54
54
  * @param decryptFunction Function to decrypt each document.
55
55
  * @param since Sequence ID to start fetching changes from (default is '0').
56
56
  * @param customHeaders Additional request headers required by the CouchDB endpoint or its reverse proxy.
57
+ * @param onStoredRevisions Observes revision identities after their complete local batch is persisted.
57
58
  */
58
- export declare function fetchChangesForInitialSync(downloadToDB: PouchDB.Database, remoteDbUrl: string, authHeader: string, decryptFunction: (doc: EntryDoc) => Promise<AnyEntry | EntryLeaf>, since?: number | string, onProgress?: (progress: FetchChangesForInitialSyncProgress) => void, onCheckpoint?: (sequence: DBSequence) => void | Promise<void>, customHeaders?: Record<string, string>): Promise<void>;
59
+ export declare function fetchChangesForInitialSync(downloadToDB: PouchDB.Database, remoteDbUrl: string, authHeader: string, decryptFunction: (doc: EntryDoc) => Promise<AnyEntry | EntryLeaf>, since?: number | string, onProgress?: (progress: FetchChangesForInitialSyncProgress) => void, onCheckpoint?: (sequence: DBSequence) => void | Promise<void>, customHeaders?: Record<string, string>, onStoredRevisions?: (revisions: readonly {
60
+ id: string;
61
+ rev: string;
62
+ }[]) => void | Promise<void>): Promise<void>;
59
63
  export {};