@vrtmrz/livesync-commonlib 0.1.0-rc.7 → 0.1.0-rc.9

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.
@@ -2,6 +2,7 @@ import type { ObsidianLiveSyncSettings } from "./setting.type.js";
2
2
  export declare const SettingsMigrationReviewCodes: {
3
3
  readonly LegacyUpdatePending: "legacy-update-review-pending";
4
4
  readonly FutureSchema: "settings-schema-newer-than-runtime";
5
+ /** @deprecated Missing legacy values are now normalised to false and do not emit this reason. */
5
6
  readonly FilenameCaseSensitivityUnresolved: "filename-case-sensitivity-unresolved";
6
7
  };
7
8
  export type SettingsMigrationReviewCode = (typeof SettingsMigrationReviewCodes)[keyof typeof SettingsMigrationReviewCodes];
@@ -7,6 +7,7 @@ import {
7
7
  const SettingsMigrationReviewCodes = {
8
8
  LegacyUpdatePending: "legacy-update-review-pending",
9
9
  FutureSchema: "settings-schema-newer-than-runtime",
10
+ /** @deprecated Missing legacy values are now normalised to false and do not emit this reason. */
10
11
  FilenameCaseSensitivityUnresolved: "filename-case-sensitivity-unresolved"
11
12
  };
12
13
  const SETTINGS_MIGRATIONS = [
@@ -43,6 +44,10 @@ function storedSettingVersion(settings) {
43
44
  function isBlankSettings(settings) {
44
45
  return settings === void 0 || Object.keys(settings).length === 0;
45
46
  }
47
+ function inferLegacyConfiguredState(settings) {
48
+ const completed = { ...SETTINGS_SCHEMA_DEFAULTS, ...settings };
49
+ return JSON.stringify(completed) !== JSON.stringify(SETTINGS_SCHEMA_DEFAULTS);
50
+ }
46
51
  function prepareSettingsForLoad(storedSettings) {
47
52
  if (isBlankSettings(storedSettings)) {
48
53
  return {
@@ -93,20 +98,19 @@ function prepareSettingsForLoad(storedSettings) {
93
98
  `No setting migration reaches schema ${CURRENT_SETTING_VERSION} from schema ${sourceVersion}.`
94
99
  );
95
100
  }
96
- if (typeof migrated.handleFilenameCaseSensitive !== "boolean") {
97
- reviewReasons.push({
98
- code: SettingsMigrationReviewCodes.FilenameCaseSensitivityUnresolved,
99
- fromVersion: sourceVersion,
100
- toVersion: targetVersion
101
- });
101
+ const normalisedConfiguredState = migrated.isConfigured === void 0;
102
+ if (normalisedConfiguredState) {
103
+ migrated = { ...migrated, isConfigured: inferLegacyConfiguredState(source) };
102
104
  }
105
+ const normalisedFilenameCase = typeof migrated.handleFilenameCaseSensitive !== "boolean";
106
+ if (normalisedFilenameCase) migrated = { ...migrated, handleFilenameCaseSensitive: false };
103
107
  return {
104
108
  settings: cloneSettings(migrated, SETTINGS_SCHEMA_DEFAULTS),
105
109
  sourceVersion,
106
110
  targetVersion,
107
111
  isNewVault: false,
108
112
  isFromFutureSchema: false,
109
- changed: sourceVersion !== targetVersion,
113
+ changed: sourceVersion !== targetVersion || normalisedConfiguredState || normalisedFilenameCase,
110
114
  requiresSyncReview: reviewReasons.length > 0,
111
115
  reviewReasons
112
116
  };
@@ -2,7 +2,12 @@ import type { UXDataWriteOptions, UXStat } from "../../common/types.js";
2
2
  import type { IStorageAdapter } from "../../serviceModules/adapters/IStorageAdapter.js";
3
3
  export type NodeStorageStat = UXStat;
4
4
  export interface CreateNodeStorageOptions {
5
- /** Absolute or process-relative directory which bounds every adapter operation. */
5
+ /**
6
+ * Host-selected root directory. Adapter paths are resolved relative to this root.
7
+ *
8
+ * The host owns this trust boundary. The adapter is not a filesystem sandbox against
9
+ * an untrusted local process which can replace path components concurrently.
10
+ */
6
11
  rootPath: string;
7
12
  }
8
13
  /** Rooted storage adapter backed by Node's file-system APIs. */
@@ -39,7 +39,9 @@ await storage.write('notes/example.md', '# Example');
39
39
 
40
40
  `rootPath` may be absolute or process-relative. The host should normally resolve and validate configuration before constructing the adapter.
41
41
 
42
- The Node adapter does not follow symbolic links in adapter paths. A path containing a symbolic link is rejected, including when the link points to another location below `rootPath`, so every operation retains one unambiguous rooted-storage boundary. Its concrete `rename(sourcePath, targetPath)` operation preserves this boundary while providing the atomic rename expected by Node hosts.
42
+ The Node adapter rejects symbolic-link components which it observes in an adapter path before an operation. File opens also use `O_NOFOLLOW` for the final component where the platform exposes that flag. These checks protect against invalid paths and symbolic links which already exist when an operation begins. Its concrete `rename(sourcePath, targetPath)` operation validates both paths before providing the atomic rename expected by Node hosts.
43
+
44
+ The host-selected `rootPath` is a trust boundary, not an operating-system filesystem sandbox. The adapter assumes that an untrusted local process cannot replace that root or its descendants concurrently with an operation. A host which grants another local actor write access to the tree must not rely on the adapter as a privilege boundary.
43
45
 
44
46
  ## Shared path contract
45
47
 
@@ -20,8 +20,25 @@ const initialSettings = createNewVaultSettings();
20
20
 
21
21
  The recommended base currently selects a 50 MB maximum synchronised file size, Rabin–Karp chunk splitting, Plug-in Sync V2, case-insensitive file-name handling, and E2EE V2. Chunk revisions are always derived from their content; the obsolete `doNotUseFixedRevisionForChunks` stored key is retained only for compatibility and is not a recommendation or tweak. The recommended base does not enable synchronisation or encryption on behalf of the user.
22
22
 
23
+ Compared with the conservative stored-setting fallbacks, the current new-Vault object has exactly two semantic differences:
24
+
25
+ | Setting | Existing stored setting is absent | New Vault |
26
+ | --- | --- | --- |
27
+ | `usePluginSyncV2` | `false` | `true` |
28
+ | `handleFilenameCaseSensitive` | normalised from the legacy absent value to `false` | `false` |
29
+
30
+ The other headline recommendations are already the conservative fallbacks: `syncMaxSizeInMB` is 50, `chunkSplitterVersion` is `v3-rabin-karp`, and `E2EEAlgorithm` is `v2`. Data Compression, Eden, V1 dynamic iteration, the legacy IndexedDB adapter, Hidden File Sync, and automatic synchronisation remain disabled when their stored values are absent. Explicit stored values take precedence.
31
+
23
32
  Apply a remote-specific preferred object only while configuring that remote. Do not merge it into an existing stored configuration merely because the remote type can be inferred. The self-hosted CouchDB profile uses a chunk-size coefficient of 60, matching the Doctor check for Rabin–Karp databases; Cloudant retains its service-specific value of 0.
24
33
 
34
+ | Explicitly selected remote | `customChunkSize` | `concurrencyOfReadChunksOnline` | `minimumIntervalOfReadChunksOnline` |
35
+ | --- | ---: | ---: | ---: |
36
+ | Cloudant | 0 | 100 | 333 |
37
+ | Self-hosted CouchDB | 60 | 30 | 25 |
38
+ | Object Storage | 10 | 30 | 25 |
39
+
40
+ These are setup-time recommendations, not upgrade migrations. Selecting or detecting a remote does not apply them to an existing configuration unless the person is actively configuring that remote.
41
+
25
42
  ## Loading existing settings
26
43
 
27
44
  `prepareSettingsForLoad` detects a blank store, completes stored settings with conservative schema fallbacks, runs ordered schema migrations, and reports the result separately from the settings value:
@@ -43,7 +60,13 @@ if (prepared.requiresSyncReview) {
43
60
 
44
61
  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.
45
62
 
46
- An existing settings store without an explicit `handleFilenameCaseSensitive` value reports `filename-case-sensitivity-unresolved` and leaves the value unset. The host must keep synchronisation paused until the user makes an explicit compatibility decision. An explicit `true` or `false` value is preserved. New Vaults select case-insensitive handling (`false`).
63
+ 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`).
64
+
65
+ `isConfigured` has a separate compatibility rule from new-Vault initialisation. For a blank store, the standard service keeps the runtime unconfigured. For a non-empty legacy document without the flag, Commonlib repeats the pre-1.0 inference: a document which is equivalent to the conservative defaults remains `false`, while a stored non-default value is evidence for `true`. The inferred boolean is saved. An explicit boolean is preserved. This rule does not make a non-empty document eligible for new-Vault recommendations.
66
+
67
+ Two other values deliberately remain unset until their owning lifecycle supplies evidence: `autoAcceptCompatibleTweak` represents that no auto-accept decision has been made, and `tweakModified` represents that no tweak-change timestamp has been recorded. They are not implicit boolean defaults and are not normalised by the settings migration.
68
+
69
+ `SettingsMigrationReviewCodes.FilenameCaseSensitivityUnresolved` remains as a deprecated compatibility export for release-candidate consumers, but `prepareSettingsForLoad` no longer emits it.
47
70
 
48
71
  The standard Commonlib `SettingService` performs this preparation automatically. Hosts which use that service can inspect `getSettingsMigrationState()` after `loadSettings()` instead of calling the function themselves.
49
72
 
@@ -57,6 +80,6 @@ The settings schema version is independent of the package version and of any rem
57
80
 
58
81
  ## Tests owned by Commonlib
59
82
 
60
- The settings lifecycle unit tests cover blank stores, representative legacy choices, migration idempotence, unresolved file-name case policy, legacy review state, future-schema downgrade protection, and the distinction between new-Vault recommendations and stored-setting fallbacks. The packed-package test imports the focused `/settings` entry from a clean consumer and checks its declarations and runtime exports.
83
+ The settings lifecycle unit tests cover blank stores, representative legacy choices, migration idempotence, legacy file-name case normalisation, legacy review state, future-schema downgrade protection, and the distinction between new-Vault recommendations and stored-setting fallbacks. The packed-package test imports the focused `/settings` entry from a clean consumer and checks its declarations and runtime exports.
61
84
 
62
85
  Hosts remain responsible for testing when setup is classified as new, how local review acknowledgement is stored, how replication is gated, and how their settings interface presents the result.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.1.0-rc.7",
3
+ "version": "0.1.0-rc.9",
4
4
  "description": "Shared data, storage, and replication primitives for Self-hosted LiveSync clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",