@vrtmrz/livesync-commonlib 0.2.3 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -8
- package/dist/API/DirectFileManipulatorV2.d.ts +8 -1
- package/dist/API/DirectFileManipulatorV2.js +52 -79
- package/dist/API/DirectFileWatchOwner.d.ts +24 -0
- package/dist/API/DirectFileWatchOwner.js +256 -0
- package/dist/API/processSetting.d.ts +1 -0
- package/dist/API/processSetting.js +3 -3
- package/dist/common/models/setting.const.defaults.js +1 -0
- package/dist/common/models/setting.const.qr.js +1 -0
- package/dist/common/models/setting.policy.d.ts +4 -0
- package/dist/common/models/setting.policy.js +1 -0
- package/dist/common/models/setting.type.d.ts +3 -1
- package/dist/common/models/shared.definition.configNames.js +1 -2
- package/dist/common/models/tweak.definition.d.ts +2 -1
- package/dist/common/pathNamespace.d.ts +4 -0
- package/dist/common/pathNamespace.js +10 -0
- package/dist/common/pathNormalization.d.ts +2 -0
- package/dist/common/pathNormalization.js +7 -2
- package/dist/common/settingConstants.d.ts +1 -0
- package/dist/common/settingConstants.js +4 -0
- package/dist/managers/ChunkFetcher.d.ts +3 -0
- package/dist/managers/ChunkFetcher.js +20 -13
- package/dist/managers/LiveSyncManagers.d.ts +2 -0
- package/dist/managers/LiveSyncManagers.js +1 -0
- package/dist/managers/chunkFetchSource.d.ts +14 -0
- package/dist/managers/chunkFetchSource.js +12 -0
- package/dist/platform/AmbiguousNormalisedPathError.d.ts +16 -0
- package/dist/platform/AmbiguousNormalisedPathError.js +21 -0
- package/dist/platform/StoragePathResolver.d.ts +15 -0
- package/dist/platform/StoragePathResolver.js +113 -0
- package/dist/platform/browser/UnicodeResolvingFileSystemAccessStorageAdapter.d.ts +74 -0
- package/dist/platform/browser/UnicodeResolvingFileSystemAccessStorageAdapter.js +169 -0
- package/dist/platform/browser/index.d.ts +1 -0
- package/dist/platform/browser/index.js +2 -0
- package/dist/platform/browser/storage.d.ts +23 -0
- package/dist/platform/browser/storage.js +138 -0
- package/dist/platform/node/UnicodeResolvingNodeStorageAdapter.d.ts +79 -0
- package/dist/platform/node/UnicodeResolvingNodeStorageAdapter.js +218 -0
- package/dist/platform/node/index.d.ts +1 -0
- package/dist/platform/node/index.js +2 -0
- package/dist/platform/node/storage.d.ts +30 -1
- package/dist/platform/node/storage.js +88 -0
- package/dist/platform/pathComparison.d.ts +53 -0
- package/dist/platform/pathComparison.js +39 -0
- package/dist/platform/pathTypes.d.ts +154 -0
- package/dist/platform/pathTypes.js +0 -0
- package/dist/platform/storagePathResolution.d.ts +13 -0
- package/dist/platform/storagePathResolution.js +9 -0
- package/dist/pouchdb/LiveSyncDBFunctions.js +5 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
- package/dist/pouchdb/LiveSyncLocalDB.js +13 -10
- package/dist/pouchdb/pathIdentityCompatibility.d.ts +8 -0
- package/dist/pouchdb/pathIdentityCompatibility.js +19 -1
- package/dist/replication/RemoteResource.d.ts +3 -0
- package/dist/replication/couchdb/LiveSyncReplicator.js +5 -3
- package/dist/replication/index.d.ts +2 -0
- package/dist/replication/index.js +2 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +1 -0
- package/dist/replication/journal/JournalSyncCore.js +35 -19
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +11 -3
- package/dist/replication/journal/LiveSyncJournalReplicator.js +123 -45
- package/dist/replication/trystero/P2PRoomSessionOwner.js +14 -13
- package/dist/replication/trystero/TrysteroReplicator.d.ts +9 -5
- package/dist/replication/trystero/TrysteroReplicator.js +98 -70
- package/dist/replication/trystero/TrysteroReplicator.pathIdentity.d.ts +11 -0
- package/dist/replication/trystero/TrysteroReplicator.pathIdentity.js +72 -0
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +2 -0
- package/dist/replication/trystero/useP2PReplicatorFeature.js +6 -2
- package/dist/serviceModules/Rebuilder.d.ts +1 -0
- package/dist/serviceModules/Rebuilder.js +35 -6
- package/dist/services/base/FileProcessingService.d.ts +2 -2
- package/dist/services/base/RemoteConnection.d.ts +2 -0
- package/dist/services/base/RemoteService.d.ts +1 -1
- package/dist/services/base/RemoteService.js +13 -5
- package/dist/services/implements/injectable/InjectableVaultService.d.ts +1 -1
- package/dist/services/lib/couchdbHttpCache.d.ts +5 -0
- package/dist/services/lib/couchdbHttpCache.js +36 -0
- package/dist/storage.d.ts +4 -0
- package/dist/storage.js +19 -0
- package/dist/string_and_binary/path.js +4 -7
- package/dist/worker/bgWorker.js +1 -1
- package/docs/adr/2026_11_shared_storage_path_resolution.md +73 -0
- package/docs/conflict-resolution.md +1 -1
- package/docs/design_docs/storage_path_resolution.md +305 -0
- package/docs/development.md +15 -1
- package/docs/platform-storage.md +61 -0
- package/docs/proven-in-use.md +30 -13
- package/docs/releasing.md +27 -0
- package/docs/service-feature-composition.md +7 -1
- package/docs/unicode-path-identity.md +104 -12
- package/package.json +10 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-11-03
|
|
3
|
+
commonlib-version: "0.3.0"
|
|
4
|
+
self-hosted-livesync-version: "1.0.44"
|
|
5
|
+
status: accepted-unreleased
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Architectural Decision Record: Shared Storage Path Resolution
|
|
9
|
+
|
|
10
|
+
## Conclusion
|
|
11
|
+
|
|
12
|
+
Commonlib owns strict Unicode path comparison and resolution behind a focused
|
|
13
|
+
`/storage` entry. Node and File System Access provide thin storage integrations;
|
|
14
|
+
hosts retain filesystem handles, original paths, caches, watchers, and operation
|
|
15
|
+
lifecycle. The [design](../design_docs/storage_path_resolution.md) defines the
|
|
16
|
+
accepted contract and remaining adoption work.
|
|
17
|
+
|
|
18
|
+
Accepted on 2026-11-03 against the versions recorded above. The implementation
|
|
19
|
+
is unreleased. Publication does not by itself enable NFC in WebApp or Bridge.
|
|
20
|
+
|
|
21
|
+
## Context
|
|
22
|
+
|
|
23
|
+
The Self-hosted LiveSync CLI already resolves NFC-equivalent names component by
|
|
24
|
+
component while preserving physical spelling. Repeating that logic in browser
|
|
25
|
+
and Deno hosts would duplicate ambiguity, missing-path, and rename rules.
|
|
26
|
+
Meanwhile, the legacy path union includes comparison keys, so removing a
|
|
27
|
+
namespace can appear to turn a lossy comparison value back into a file path.
|
|
28
|
+
|
|
29
|
+
## Decisions
|
|
30
|
+
|
|
31
|
+
- Separate original document paths, unprefixed file paths, NFC refinements,
|
|
32
|
+
resolved storage paths, and comparison keys. Preserve the old public types;
|
|
33
|
+
new namespace helpers exclude their legacy comparison-key member.
|
|
34
|
+
- Inject only strict directory enumeration into the resolver. Return `null`
|
|
35
|
+
for absence, and propagate permission, non-directory, and other I/O failures.
|
|
36
|
+
Resolve ambiguity before mutation, including when an exact spelling exists.
|
|
37
|
+
- Copy comparison settings for one operation or scan. Retain no cross-operation
|
|
38
|
+
directory cache or persistent path mapping. Scan already resolved entries
|
|
39
|
+
once per directory, and re-resolve later lookups after external changes.
|
|
40
|
+
- Preserve ordinary writes' physical spelling. An explicit rename may request
|
|
41
|
+
another spelling of the same leaf. Mutation and replacement semantics remain
|
|
42
|
+
with the storage owner; resolution is not an atomic reservation.
|
|
43
|
+
- Keep string-taking storage APIs and Legacy behaviour. New physical-I/O ports
|
|
44
|
+
take `StoragePath`. Namespace routing and cryptographic document-ID derivation
|
|
45
|
+
remain separate from filesystem resolution.
|
|
46
|
+
- Adopt the shared implementation in the CLI and the shared comparison and
|
|
47
|
+
namespace helpers at applicable Obsidian boundaries. WebApp and Bridge NFC
|
|
48
|
+
enablement remains separate work, including admission, events, and caches.
|
|
49
|
+
|
|
50
|
+
## Alternatives considered
|
|
51
|
+
|
|
52
|
+
- Duplicating the CLI resolver per host would duplicate the behavioural contract
|
|
53
|
+
and its failure handling.
|
|
54
|
+
- Renaming all physical files to NFC would alter user-owned storage and require
|
|
55
|
+
collision recovery beyond the accepted scope.
|
|
56
|
+
- A persistent alias map or shared directory cache would introduce invalidation,
|
|
57
|
+
restart, and lifecycle obligations which current directory reads avoid.
|
|
58
|
+
- Combining namespace, Unicode form, and case in a family of filename types
|
|
59
|
+
would conflate independent properties. Comparison keys instead name their
|
|
60
|
+
purpose and their storage or document domain.
|
|
61
|
+
|
|
62
|
+
## Consequences and verification
|
|
63
|
+
|
|
64
|
+
Hosts supply trustworthy complete listings and preserve distinct names before
|
|
65
|
+
constructing keyed collections. Filesystems or APIs which hide aliases limit
|
|
66
|
+
what the resolver can observe. Host concurrency and permission checks remain
|
|
67
|
+
necessary after resolution.
|
|
68
|
+
|
|
69
|
+
Contract tests cover spelling, ambiguity, strict failures, snapshots, scans,
|
|
70
|
+
and renames. Packed-consumer tests check assignability and platform bundling.
|
|
71
|
+
The downstream CLI retains its behavioural suite against the exact packed
|
|
72
|
+
candidate. Memory-backed File System Access tests establish adapter behaviour;
|
|
73
|
+
real-browser permissions and Deno integration require their own host evidence.
|
|
@@ -74,7 +74,7 @@ A host must not apply a dialogue result after either compared revision has cease
|
|
|
74
74
|
|
|
75
75
|
## Scheduling conflict checks
|
|
76
76
|
|
|
77
|
-
The
|
|
77
|
+
The `@vrtmrz/livesync-commonlib/conflicts` entry, published in Commonlib 0.2.3, exports
|
|
78
78
|
`createConflictCheckQueue`. The host supplies a `process(path, scope)` callback
|
|
79
79
|
and may observe pending plus active checks through `onProgress`. Concurrency
|
|
80
80
|
defaults to ten and includes each item's deferred cleanup. The host continues
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-11-03
|
|
3
|
+
commonlib-version: "0.3.0"
|
|
4
|
+
self-hosted-livesync-version: "1.0.44"
|
|
5
|
+
status: accepted-unreleased
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Shared storage path resolution
|
|
9
|
+
|
|
10
|
+
This design moves the CLI's existing Unicode-aware path resolution into
|
|
11
|
+
Commonlib so Node, File System Access, and Bridge can apply the same naming
|
|
12
|
+
rules. The shared resolver, path types, and Node and File System Access
|
|
13
|
+
integrations are implemented as unreleased additions. Installing the package
|
|
14
|
+
does not enable NFC in an existing host or migrate its database.
|
|
15
|
+
|
|
16
|
+
Accepted on 2026-11-03. The [decision record](../adr/2026_11_shared_storage_path_resolution.md)
|
|
17
|
+
records the ownership and compatibility choices. The [platform guide](../platform-storage.md#unicode-path-resolution)
|
|
18
|
+
provides consumer examples.
|
|
19
|
+
|
|
20
|
+
The baseline is Commonlib 0.3.0 and the CLI implementation reviewed in
|
|
21
|
+
[Self-hosted LiveSync #1309](https://github.com/vrtmrz/obsidian-livesync/pull/1309)
|
|
22
|
+
at `2716d68b3d21d493cdfdefa0857fad4c6a3c522a`. The existing
|
|
23
|
+
[Unicode identity contract](../unicode-path-identity.md) continues to govern
|
|
24
|
+
Metadata IDs, remote admission, defaults, and database migration.
|
|
25
|
+
|
|
26
|
+
## Ownership
|
|
27
|
+
|
|
28
|
+
Commonlib owns path comparison, component-by-component resolution, ambiguity
|
|
29
|
+
checks, and thin platform-storage integrations. Hosts own root selection,
|
|
30
|
+
permissions, filesystem handles, file objects, watchers, caches, and operation
|
|
31
|
+
lifecycle. A resolver performs directory reads but no filesystem mutation.
|
|
32
|
+
|
|
33
|
+
Keep the existing Node and File System Access storage APIs and their Legacy
|
|
34
|
+
behaviour. Compose the resolver only for NFC mode. Its comparison policy is
|
|
35
|
+
an immutable snapshot for one operation or scan, copied at construction;
|
|
36
|
+
a settings change requires a new composition. Explicit case-sensitive and
|
|
37
|
+
case-insensitive lookups use
|
|
38
|
+
the corresponding snapshot, without mutating a shared resolver.
|
|
39
|
+
|
|
40
|
+
## Path types
|
|
41
|
+
|
|
42
|
+
Separate namespace, Unicode form, comparison use, and storage resolution.
|
|
43
|
+
An actual filename can already be NFC, so these are not mutually exclusive
|
|
44
|
+
string categories.
|
|
45
|
+
|
|
46
|
+
| Type | Meaning | Producer |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `FilePath` | Original spelling without a Metadata namespace; no NFC or resolution guarantee. | Existing input and host boundaries. |
|
|
49
|
+
| `DocumentPath` | Original spelling with an optional recognised Metadata namespace; excludes comparison keys. | Document input and namespace helpers. |
|
|
50
|
+
| `NfcFilePath` | A `FilePath` normalised to NFC, without lower-casing. | The shared NFC normalisation function. |
|
|
51
|
+
| `StoragePath` | A root-relative storage target whose existing components use their resolved spelling. | Resolver or supported physical enumeration. |
|
|
52
|
+
| `PathComparisonKey<"storage">` | A comparison key for a path within one storage root. | The shared comparison helper. |
|
|
53
|
+
| `PathComparisonKey<"document">` | A comparison key which retains the document namespace. | The shared comparison helper. |
|
|
54
|
+
|
|
55
|
+
`StoragePath` is not proof that an entry exists, still exists, is a file,
|
|
56
|
+
or cannot change concurrently. A write target may contain missing components.
|
|
57
|
+
The root is the empty path; each operation retains its existing root rules.
|
|
58
|
+
|
|
59
|
+
Reuse the existing `FilePath` and OW `TaggedType` definitions. Add independent
|
|
60
|
+
symbols for refinements: nesting `TaggedType<FilePath, "StoragePath">` would
|
|
61
|
+
intersect incompatible values of OW's existing tag property.
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
declare const unicodeForm: unique symbol;
|
|
65
|
+
declare const storagePath: unique symbol;
|
|
66
|
+
|
|
67
|
+
type DocumentPath = Exclude<FilePathWithPrefix, FilePathWithPrefixLC>;
|
|
68
|
+
|
|
69
|
+
type NfcFilePath = FilePath & {
|
|
70
|
+
readonly [unicodeForm]: "NFC";
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type StoragePath = FilePath & {
|
|
74
|
+
readonly [storagePath]: true;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
type PathComparisonKey<Domain extends "storage" | "document"> =
|
|
78
|
+
TaggedType<string, `PathComparisonKey:${Domain}`>;
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Use `NfcFilePath` within the normalisation boundary initially. Publish the
|
|
82
|
+
path and key types needed by consumers of the new focused API. Preserve the
|
|
83
|
+
existing definitions of `FilePathWithPrefix` and `FilePathWithPrefixLC` at
|
|
84
|
+
compatibility entry points. Do not alias either old type to a new key type:
|
|
85
|
+
that would change existing assignability.
|
|
86
|
+
|
|
87
|
+
The old `FilePathWithPrefix` union includes both `FilePath` and
|
|
88
|
+
`FilePathWithPrefixLC`. The new `DocumentPath` intentionally removes the latter.
|
|
89
|
+
The old scanner's `convertCase()` returns its `LC` type even when case-sensitive
|
|
90
|
+
handling retains letter case. New code therefore names its output by its
|
|
91
|
+
comparison purpose, not an assumed lower-case form.
|
|
92
|
+
|
|
93
|
+
Brand values at the boundary which establishes their meaning. `parseFilePath`
|
|
94
|
+
validates raw storage input using the existing path vocabulary without changing
|
|
95
|
+
its spelling. `parseDocumentPath` accepts original document-path strings;
|
|
96
|
+
namespace routing precedes storage validation. Neither input helper recovers
|
|
97
|
+
original spelling from a comparison key. NFC conversion and resolution establish their own refinements.
|
|
98
|
+
Keep casts inside those owning implementations; consumer code receives the
|
|
99
|
+
typed result. The existing string-taking APIs remain compatibility escape
|
|
100
|
+
routes, so this migration does not claim to make all old call sites type-safe.
|
|
101
|
+
Within the new integrations, physical-I/O ports accept `StoragePath`; NFC
|
|
102
|
+
normalisation or key generation alone cannot supply that argument.
|
|
103
|
+
|
|
104
|
+
## Namespaces and comparison
|
|
105
|
+
|
|
106
|
+
Namespace helpers accept `DocumentPath`, return a separate namespace and
|
|
107
|
+
`FilePath`, and recognise the existing `i:`, `ix:`, and `ps:` rules. Split one
|
|
108
|
+
namespace only; further colons belong to the body. The caller handles the
|
|
109
|
+
namespace's meaning before submitting a filesystem path to the resolver.
|
|
110
|
+
Chunk prefixes and obfuscated IDs remain `DocumentID` concerns.
|
|
111
|
+
|
|
112
|
+
Document comparison preserves the namespace; storage comparison does not
|
|
113
|
+
receive one. A normal document and an internal document with the same body
|
|
114
|
+
remain distinct document identities. Neither comparison-key domain is accepted
|
|
115
|
+
by the resolver or by the new namespace helpers.
|
|
116
|
+
|
|
117
|
+
The comparison policy uses the existing `normalisePathForIdentity` operation,
|
|
118
|
+
then the existing `toLowerCase()` rule only when configured. NFC conversion
|
|
119
|
+
alone produces `NfcFilePath`; the complete comparison pipeline produces a key.
|
|
120
|
+
It does not infer the host filesystem's case rules or introduce a new Unicode
|
|
121
|
+
case-folding rule. Preserve original paths alongside keys; a key is never
|
|
122
|
+
converted back into an original path.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
type PathComparisonSettings = Readonly<{
|
|
126
|
+
pathNormalizationVersion: 0 | 1;
|
|
127
|
+
handleFilenameCaseSensitive: boolean;
|
|
128
|
+
}>;
|
|
129
|
+
|
|
130
|
+
type DocumentPathParts = Readonly<{
|
|
131
|
+
prefix: "" | "i:" | "ix:" | "ps:";
|
|
132
|
+
path: FilePath;
|
|
133
|
+
}>;
|
|
134
|
+
|
|
135
|
+
declare function splitDocumentPath(path: DocumentPath): DocumentPathParts;
|
|
136
|
+
declare function joinDocumentPath(parts: DocumentPathParts): DocumentPath;
|
|
137
|
+
|
|
138
|
+
declare function getStoragePathComparisonKey(
|
|
139
|
+
path: FilePath, settings: PathComparisonSettings
|
|
140
|
+
): PathComparisonKey<"storage">;
|
|
141
|
+
|
|
142
|
+
declare function getDocumentPathComparisonKey(
|
|
143
|
+
path: DocumentPath, settings: PathComparisonSettings
|
|
144
|
+
): PathComparisonKey<"document">;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Namespace routing and cryptographic document-ID derivation remain outside the
|
|
148
|
+
resolver. The new comparison helpers must retain the existing key bytes for
|
|
149
|
+
each supported policy; extracting them does not rewrite IDs or stored paths.
|
|
150
|
+
|
|
151
|
+
## Directory and resolver contract
|
|
152
|
+
|
|
153
|
+
The only injected I/O operation enumerates immediate children using their
|
|
154
|
+
actual names and kinds. Paths are relative to the selected root and use `/`.
|
|
155
|
+
`null` means the directory is absent. Permission failures, a non-directory
|
|
156
|
+
parent, and other I/O failures reject; they are not empty listings.
|
|
157
|
+
|
|
158
|
+
The existing storage `list()` methods can hide some failures as empty results.
|
|
159
|
+
Provide a strict enumeration path for the resolver while preserving the old
|
|
160
|
+
methods' published behaviour. Reuse each platform's containment and symbolic-link
|
|
161
|
+
checks. Keep native handles and platform stat types behind this boundary.
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
type DirectoryEntry = Readonly<{
|
|
165
|
+
name: string;
|
|
166
|
+
kind: "file" | "directory";
|
|
167
|
+
}>;
|
|
168
|
+
|
|
169
|
+
type ReadDirectory = (
|
|
170
|
+
physicalDirectory: StoragePath
|
|
171
|
+
) => Promise<readonly DirectoryEntry[] | null>;
|
|
172
|
+
|
|
173
|
+
type StorageEntry = Readonly<{
|
|
174
|
+
path: StoragePath;
|
|
175
|
+
kind: "file" | "directory";
|
|
176
|
+
}>;
|
|
177
|
+
|
|
178
|
+
type ResolvedRename = Readonly<{
|
|
179
|
+
source: StoragePath;
|
|
180
|
+
target: StoragePath;
|
|
181
|
+
}>;
|
|
182
|
+
|
|
183
|
+
interface StoragePathResolver {
|
|
184
|
+
resolveExisting(path: FilePath): Promise<StoragePath | null>;
|
|
185
|
+
resolveForWrite(path: FilePath): Promise<StoragePath>;
|
|
186
|
+
resolveRename(source: FilePath, target: FilePath): Promise<ResolvedRename | null>;
|
|
187
|
+
listResolvedDirectory(path: StoragePath): Promise<readonly StorageEntry[] | null>;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
declare function createStoragePathResolver(options: Readonly<{
|
|
191
|
+
readDirectory: ReadDirectory;
|
|
192
|
+
policy: Readonly<{
|
|
193
|
+
pathNormalizationVersion: 1;
|
|
194
|
+
handleFilenameCaseSensitive: boolean;
|
|
195
|
+
}>;
|
|
196
|
+
}>): StoragePathResolver;
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`DirectoryEntry.name` is an immediate child name, never a path. Reject malformed
|
|
200
|
+
entries at the enumeration boundary. Add a separate filename brand only when a
|
|
201
|
+
consumer boundary needs it; do not publish a family of filename variants now.
|
|
202
|
+
|
|
203
|
+
The resolver uses complete sibling enumeration before deciding uniqueness;
|
|
204
|
+
a literal match does not override a second equivalent name. File/directory
|
|
205
|
+
collisions count as ambiguity. A unique file cannot be traversed as a parent.
|
|
206
|
+
An individual lookup checks candidates for each requested component; a full
|
|
207
|
+
scan checks every sibling group before returning that directory's entries.
|
|
208
|
+
|
|
209
|
+
| Operation | Result |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `resolveExisting` | Resolve each existing component to its listed spelling; return `null` on absence. |
|
|
212
|
+
| `resolveForWrite` | Preserve listed names for existing components and the requested spelling for the missing suffix. |
|
|
213
|
+
| `resolveRename` | Resolve both ends. If the target resolves to the source, retain the resolved parent and the explicitly requested leaf spelling. Return `null` when the source is absent. |
|
|
214
|
+
| `listResolvedDirectory` | Read an already resolved directory, check all sibling identities, and return actual child paths for scanning and direct access. |
|
|
215
|
+
|
|
216
|
+
All operations reject ambiguity using the shared `AmbiguousNormalisedPathError`.
|
|
217
|
+
Include the requested path and conflicting actual names for diagnosis. Preserve
|
|
218
|
+
I/O failures separately from absence. Host catch blocks must propagate these
|
|
219
|
+
failures instead of converting them to a missing file or a successful partial
|
|
220
|
+
scan.
|
|
221
|
+
|
|
222
|
+
Normal writes preserve existing Unicode spelling. Explicit renames may change
|
|
223
|
+
it. Existing-destination replacement policy and the actual rename, write,
|
|
224
|
+
rollback, or deletion remain the storage owner's responsibility. Resolution is
|
|
225
|
+
not an atomic reservation; existing host operation ordering and platform
|
|
226
|
+
protections remain necessary. The guarantee is limited to names exposed by the
|
|
227
|
+
host's supported listing.
|
|
228
|
+
|
|
229
|
+
## Composition and adoption
|
|
230
|
+
|
|
231
|
+
The `/storage` entry contains the host-neutral types, comparison and
|
|
232
|
+
namespace helpers, and resolver. It imports no Node, DOM, database, or Service
|
|
233
|
+
Hub runtime. `/node` and `/browser` expose their platform integrations through
|
|
234
|
+
the existing bundling boundaries.
|
|
235
|
+
|
|
236
|
+
Commonlib owns `UnicodeResolvingNodeStorageAdapter` and delegates
|
|
237
|
+
its resolution to the common implementation. The current CLI candidate adopts
|
|
238
|
+
the focused API while retaining Legacy delegation and thin compatibility
|
|
239
|
+
methods. File System Access uses the same resolver, including host paths which
|
|
240
|
+
operate directly on handles. Bridge supplies a Deno directory reader and uses
|
|
241
|
+
the same contract in an unreleased integration candidate.
|
|
242
|
+
|
|
243
|
+
Keep the resolver free of persistent mappings, cross-operation caches, watchers,
|
|
244
|
+
retry owners, and disposal requirements. A full scan descends through the
|
|
245
|
+
resolved entries and reads each directory once. A later lookup re-reads the
|
|
246
|
+
relevant directories, including after a file-object cache hit. OW Scope or
|
|
247
|
+
context can own host operation resources where needed; the resolver itself
|
|
248
|
+
requires no resource scope.
|
|
249
|
+
|
|
250
|
+
Hosts retain original physical paths alongside any comparison keys. Detect
|
|
251
|
+
aliases before collapsing them into a keyed collection. Cache invalidation,
|
|
252
|
+
queue ownership, and watch-event reconciliation must follow the same comparison
|
|
253
|
+
policy without losing distinct names. A deletion event may name an entry which
|
|
254
|
+
is already absent; preserve that event path rather than trying to recover its
|
|
255
|
+
spelling from a live directory lookup. Check rename events as an ordered change
|
|
256
|
+
of state, including hosts which report them as deletion and creation.
|
|
257
|
+
|
|
258
|
+
## Current adoption and publication status
|
|
259
|
+
|
|
260
|
+
The shared `/storage` contract, Node and File System Access adapters, and
|
|
261
|
+
explicit `DirectFileManipulator` path-version option are unreleased additions
|
|
262
|
+
after the published Commonlib `0.3.0` artefact. They require a new immutable
|
|
263
|
+
package version; a source checkout with version string `0.3.0` is not the
|
|
264
|
+
registry artefact.
|
|
265
|
+
|
|
266
|
+
The CLI candidate uses the shared Node resolver, and the current Obsidian
|
|
267
|
+
candidate uses shared namespace and comparison helpers while retaining Vault
|
|
268
|
+
APIs and its alias guard. The matching Journal and P2P host adoption remains
|
|
269
|
+
unreleased in Self-hosted LiveSync [#1309](https://github.com/vrtmrz/obsidian-livesync/pull/1309).
|
|
270
|
+
The browser resolver is a general primitive, not WebApp NFC support: WebApp
|
|
271
|
+
remains Legacy-only and rejects an explicit version `1` at startup. WebPeer
|
|
272
|
+
has no Vault filesystem and is not an NFC filesystem host.
|
|
273
|
+
|
|
274
|
+
Bridge's Deno integration is also an unreleased candidate. NFC mode is an
|
|
275
|
+
explicit per-peer opt-in, and every peer in a group must use the same path
|
|
276
|
+
version. Its ordinary case-mismatch check remains enabled; the Bridge-wide
|
|
277
|
+
`disableCheckingConfigMismatch: true` override disables that case check only
|
|
278
|
+
and does not bypass path-version admission. Preserve the existing coordinated
|
|
279
|
+
Rebuild/Fetch migration and remote mismatch protection. This design adds no
|
|
280
|
+
automatic migration or translation between identity modes.
|
|
281
|
+
|
|
282
|
+
## Verification
|
|
283
|
+
|
|
284
|
+
Check the public declarations against the actual published path types and OW
|
|
285
|
+
tag implementation. Positive cases cover source paths, namespace round trips,
|
|
286
|
+
and independent NFC/storage refinements. Negative cases cover raw strings,
|
|
287
|
+
prefix-bearing paths at the resolver, comparison keys at resolution and namespace
|
|
288
|
+
boundaries, and mixing the two key domains. Retain an assertion that the legacy
|
|
289
|
+
`LC` type is still assignable to the legacy union, but not to `DocumentPath`.
|
|
290
|
+
|
|
291
|
+
Implementation must preserve the existing CLI behavioural cases and apply one
|
|
292
|
+
resolver contract to Node and File System Access, with Bridge-specific checks:
|
|
293
|
+
|
|
294
|
+
- NFD files and mixed-spelling parents remain addressable without implicit renames.
|
|
295
|
+
- New components retain the requested spelling under the actual parent.
|
|
296
|
+
- File, directory, and case collisions reject before mutation, including exact-match requests.
|
|
297
|
+
- Permission and listing failures do not become absence, creation, deletion, or partial-scan success.
|
|
298
|
+
- Explicit spelling renames preserve contents and the host's replacement and recovery semantics.
|
|
299
|
+
- External additions, deletions, renames, and collisions are observed after earlier cache hits.
|
|
300
|
+
- Full scans inspect each directory once; Legacy behaviour and namespace separation remain unchanged.
|
|
301
|
+
- Watcher deletion/creation sequences, restart state, and cache keys preserve the intended identity.
|
|
302
|
+
|
|
303
|
+
Packed-package checks must verify the focused entry and platform bundling
|
|
304
|
+
boundaries. Real browser and Deno checks establish their host behaviour;
|
|
305
|
+
Commonlib's memory and Node tests alone do not establish those guarantees.
|
package/docs/development.md
CHANGED
|
@@ -6,7 +6,7 @@ This document is for Commonlib developers. Package consumers should begin with t
|
|
|
6
6
|
|
|
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
|
-
- `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;
|
|
9
|
+
- `src/index.ts`, `src/context.ts`, `src/storage.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
10
|
- `src/conflicts.ts` defines the host-owned conflict-check queue contract; `src/concurrency/keyedPendingWork.ts` remains package-private;
|
|
11
11
|
- `src/replication/index.ts` defines the focused provider-capability and interaction contract for hosts which compose replication;
|
|
12
12
|
- `src/diagnostics/index.ts` defines read-only Usage and integrity inspection, report projection, and formatting; hosts own the run dialogue and output destination;
|
|
@@ -19,6 +19,16 @@ The source tree is larger than the supported package surface. Consumers may impo
|
|
|
19
19
|
|
|
20
20
|
Files elsewhere under `src` are implementation details unless a focused entry re-exports them or the compatibility inventory names them. Do not use the source layout, TypeScript path aliases, or the presence of generated declarations as evidence of a supported consumer import.
|
|
21
21
|
|
|
22
|
+
## Storage path boundary
|
|
23
|
+
|
|
24
|
+
The unreleased `/storage` entry owns namespace helpers, comparison keys, and
|
|
25
|
+
strict component resolution. The [design](design_docs/storage_path_resolution.md)
|
|
26
|
+
and [decision record](adr/2026_11_shared_storage_path_resolution.md) separate
|
|
27
|
+
that contract from host handles, caches, watchers, and migration. See the
|
|
28
|
+
[platform guide](platform-storage.md#unicode-path-resolution) for composition.
|
|
29
|
+
The [Unicode identity contract](unicode-path-identity.md) retains its existing
|
|
30
|
+
Metadata, remote-admission, and database-migration rules.
|
|
31
|
+
|
|
22
32
|
## Module filenames
|
|
23
33
|
|
|
24
34
|
For new or renamed TypeScript and JavaScript source modules, start the filename
|
|
@@ -111,6 +121,10 @@ The revision-tree safety rules are documented in [Conflict resolution and file p
|
|
|
111
121
|
|
|
112
122
|
`LiveSyncReplicator.unit.spec.ts` owns the CouchDB remote-chunk batch contract: a mixed `_all_docs` result must return each available document even when another keyed row reports an error. `ChunkFetcher.unit.spec.ts` owns the consumer behaviour: it writes valid returned chunks and emits `missingChunkRemote` only for requested IDs which are absent from the returned set. Extend both sides when changing remote chunk acquisition or missing-chunk delivery.
|
|
113
123
|
|
|
124
|
+
Direct CouchDB hosts supply a database-bound `ChunkFetchSource` through the optional `LiveSyncLocalDB` runtime factory. `LiveSyncManagers` passes it to the shared `ChunkFetcher`; application hosts omit it and retain active-replicator retrieval and local persistence. The direct source reads the prepared database and accepts results without writing them back. Recreate it with each database instance so reopening cannot retain a closed connection. `ChunkFetcher.source.unit.spec.ts` covers direct retrieval, delayed availability without change notifications, bounded failure, and teardown. `DirectFileManipulatorV2.integration.spec.ts` covers encrypted arrival before and during an outstanding read, with assertions that the reader does not write Chunks back.
|
|
125
|
+
|
|
126
|
+
`DirectFileWatchOwner` owns the direct watch lifecycle. Its per-client state retains unfinished reads, same-document delivery ownership, and pending checkpoint persistence across stop and restart. Each watch session separately owns its pending documents, connection generation, and timers. Named operations receive explicit contexts through Octagonal Wheels `bindContext`; per-file and per-delivery scopes release their bookkeeping after the operation settles. Feed cancellation and timer cancellation remain synchronous in `endWatch()`, independently of unfinished reads or host callbacks. The owner tests cover retries, safe checkpoint positions, replacement sessions, and failure handling; `DirectFileManipulatorV2.unit.spec.ts` retains the public state, database binding, and close-order checks.
|
|
127
|
+
|
|
114
128
|
`RemoteService.connect()` returns an owned CouchDB connection as the existing flat `{ db, info }` result with an idempotent `close()` method. `close()` first aborts outstanding abort-capable requests in that connection, then closes the PouchDB handle. The owner must transfer or close the complete connection; passing only `db` loses the cancellation boundary. A skipped information request retains the established placeholder in `info` for compatibility. `OwnedCouchDBConnection`, its opening options, and the checked `CouchDBReplicationConnection` are Commonlib-owned types exported by the existing CouchDB replicator module. The legacy `_ensureConnection()` helper continues to return a raw PouchDB handle for source compatibility; new Commonlib code must retain and close the complete owned connection instead.
|
|
115
129
|
|
|
116
130
|
The one-shot CouchDB connectivity preflight is the first bounded consumer. It applies an internal 60-second safety fuse to the complete preflight on the web-compatible fetch path, disables fallback into the non-abortable native request adapter, and clears the fuse before transferring the connection to replication. The fuse is a last-resort wall-clock bound on an owner which would otherwise remain pending indefinitely; it is not the expected completion time, a per-request inactivity timeout, a user setting, or the `useTimeouts` changes-feed setting. The explicitly selected native Request API retains its previous unbounded behaviour because its current host adapter cannot honour `AbortSignal`. Extend `RemoteService.unit.spec.ts` and the one-shot ownership table in `LiveSyncReplicator.unit.spec.ts` when changing this boundary. Do not use `Promise.race()` to report a mutating native request as cancelled while it can still complete remotely.
|
package/docs/platform-storage.md
CHANGED
|
@@ -61,6 +61,67 @@ Writes create missing parent directories. Listing returns direct child paths rel
|
|
|
61
61
|
|
|
62
62
|
The File System Access API does not expose portable creation times or permit setting file times. Consumers must not rely on identical timestamp fidelity across the two adapters.
|
|
63
63
|
|
|
64
|
+
## Unicode path resolution
|
|
65
|
+
|
|
66
|
+
The following APIs are unreleased additions after Commonlib 0.3.0. Node and
|
|
67
|
+
File System Access hosts can use the same NFC comparison and component
|
|
68
|
+
resolution while retaining the names listed by their filesystem. Hosts still
|
|
69
|
+
own the selected identity mode and its database migration.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { UnicodeResolvingNodeStorageAdapter } from '@vrtmrz/livesync-commonlib/node';
|
|
73
|
+
import { parseFilePath } from '@vrtmrz/livesync-commonlib/storage';
|
|
74
|
+
|
|
75
|
+
const storage = new UnicodeResolvingNodeStorageAdapter(
|
|
76
|
+
'/srv/livesync',
|
|
77
|
+
() => settings.pathNormalizationVersion,
|
|
78
|
+
() => !settings.handleFilenameCaseSensitive,
|
|
79
|
+
);
|
|
80
|
+
const existing = await storage.resolveLogicalPath(parseFilePath('Café.md'), undefined, false);
|
|
81
|
+
if (existing !== null) {
|
|
82
|
+
const content = await storage.readResolved(existing);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The browser equivalent is `UnicodeResolvingFileSystemAccessStorageAdapter`
|
|
87
|
+
from `/browser`, constructed with an authorised root handle and the same
|
|
88
|
+
settings callbacks. Version `0` delegates to the existing storage behaviour;
|
|
89
|
+
version `1` resolves every component against complete sibling listings. Normal
|
|
90
|
+
writes preserve existing spelling. Node's explicit `rename` can change a leaf's
|
|
91
|
+
Unicode or letter-case spelling; the browser adapter retains its existing
|
|
92
|
+
operation set and does not add a rename primitive.
|
|
93
|
+
|
|
94
|
+
`AmbiguousNormalisedPathError` from `/storage` reports multiple equivalent
|
|
95
|
+
names, even when one is a literal match. Preserve that error and directory or
|
|
96
|
+
permission failures. Only absence produces `null`; a failure must not be used
|
|
97
|
+
as permission to create a file or as a successful partial scan.
|
|
98
|
+
|
|
99
|
+
For another host, compose `createStoragePathResolver({ readDirectory, policy })`
|
|
100
|
+
from `/storage`. `readDirectory` accepts a `StoragePath` and returns immediate
|
|
101
|
+
child names and kinds, or `null` for absence. Propagate other failures. The
|
|
102
|
+
resolver copies one NFC policy and retains no directory cache. For a full scan,
|
|
103
|
+
start at `resolveForWrite(parseFilePath(''))`, then recurse through
|
|
104
|
+
`listResolvedDirectory` entries using the same resolver. Each directory is read
|
|
105
|
+
once. Re-resolve later operations so external changes are visible.
|
|
106
|
+
|
|
107
|
+
`FilePath` retains original, unprefixed spelling. `StoragePath` records that
|
|
108
|
+
existing components have been resolved; it does not guarantee existence or
|
|
109
|
+
reserve a target. `parseDocumentPath` marks an original document-path input,
|
|
110
|
+
and `splitDocumentPath` separates one recognised namespace before host routing.
|
|
111
|
+
Use `joinDocumentPath` to restore it. `getStoragePathComparisonKey` and
|
|
112
|
+
`getDocumentPathComparisonKey` return distinct comparison-only types; the latter
|
|
113
|
+
preserves the namespace. Keep the original path beside each key. The old
|
|
114
|
+
`FilePathWithPrefix` and `FilePathWithPrefixLC` remain available for compatibility.
|
|
115
|
+
|
|
116
|
+
The [design](design_docs/storage_path_resolution.md) defines the detailed
|
|
117
|
+
contract. These primitives do not enable NFC automatically in a host
|
|
118
|
+
application. WebApp remains Legacy-only and rejects explicit version `1` at
|
|
119
|
+
startup. The Bridge Deno integration is an unreleased candidate; version `1`
|
|
120
|
+
is an explicit peer opt-in, and every peer in a group must use the same path
|
|
121
|
+
version. Its ordinary case-mismatch check remains separate from path-version
|
|
122
|
+
admission. See [Unicode path identity](unicode-path-identity.md#supported-hosts)
|
|
123
|
+
for the current publication and host-adoption boundaries.
|
|
124
|
+
|
|
64
125
|
## Bundling boundary
|
|
65
126
|
|
|
66
127
|
Browser consumers import only `@vrtmrz/livesync-commonlib/browser`. Packed-package tests build that entry with browser conditions and verify that the Node implementation is absent. Node consumers import `@vrtmrz/livesync-commonlib/node`; the root and context entries do not load Node built-ins.
|
package/docs/proven-in-use.md
CHANGED
|
@@ -6,9 +6,23 @@ 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
|
-
##
|
|
9
|
+
## Published shared-runtime integration
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Commonlib [0.2.3](https://github.com/vrtmrz/livesync-commonlib/releases/tag/0.2.3)
|
|
12
|
+
is published on npm through `latest` and `next`, with an exact dependency on
|
|
13
|
+
Octagonal Wheels 0.1.56. Self-hosted LiveSync
|
|
14
|
+
[1.0.44](https://github.com/vrtmrz/obsidian-livesync/releases/tag/1.0.44)
|
|
15
|
+
adopts the published Commonlib package. Its
|
|
16
|
+
[integration record](https://github.com/vrtmrz/obsidian-livesync/pull/1307)
|
|
17
|
+
separates the final source checks from the focused browser, CLI, and real-Obsidian
|
|
18
|
+
results for unchanged consumer boundaries. The earlier candidate measurements
|
|
19
|
+
below remain historical evidence at their recorded versions.
|
|
20
|
+
|
|
21
|
+
<a id="blob-construction-and-comparison-candidate-integration"></a>
|
|
22
|
+
|
|
23
|
+
## Blob construction and comparison: published integration
|
|
24
|
+
|
|
25
|
+
Commonlib 0.2.3 delegates Blob construction to
|
|
12
26
|
`octagonal-wheels/binary/blob`. Commonlib retains the old creation imports and
|
|
13
27
|
wraps `toBlob` as `createBlob`, while document classification, binary decoding,
|
|
14
28
|
and content-comparison policy remain in Commonlib. The legacy
|
|
@@ -31,13 +45,12 @@ absence on the native path. The
|
|
|
31
45
|
the existing Commonlib imports after packaging.
|
|
32
46
|
|
|
33
47
|
Earlier candidate checks explicitly installed the selected unpublished Octagonal
|
|
34
|
-
Wheels tarball in the disposable consumer.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
compatibility contracts;
|
|
40
|
-
remain pending.
|
|
48
|
+
Wheels tarball in the disposable consumer. Commonlib 0.2.3 pins the published
|
|
49
|
+
Octagonal Wheels 0.1.56 dependency and uses the ordinary package gate without a
|
|
50
|
+
candidate override. The subsequent Self-hosted LiveSync integration uses the
|
|
51
|
+
exact published Commonlib package. Its recorded application builds and focused
|
|
52
|
+
real-Obsidian conflict and catalogue checks complement the Commonlib-owned
|
|
53
|
+
compatibility contracts; they do not establish physical mobile-device behaviour.
|
|
41
54
|
|
|
42
55
|
## Obsidian plug-in
|
|
43
56
|
|
|
@@ -254,7 +267,9 @@ Use the named consumer paths as integration examples, while retaining the owners
|
|
|
254
267
|
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
268
|
|
|
256
269
|
|
|
257
|
-
|
|
270
|
+
<a id="shared-value-helpers-candidate-integration"></a>
|
|
271
|
+
|
|
272
|
+
## Shared value helpers: published integration
|
|
258
273
|
|
|
259
274
|
The [runtime helper tests](../src/common/utils.runtime-tools.unit.spec.ts)
|
|
260
275
|
characterise duration thresholds, delimiter empty parts, and JSON conversion
|
|
@@ -264,16 +279,18 @@ to detach settings before an operation. The shared package owns the value
|
|
|
264
279
|
operations. The packed [type fixture](../_tools/fixtures/runtime-tools-types.ts) and
|
|
265
280
|
[runtime fixture](../_tools/fixtures/runtime-tools-runtime.mjs) exercise those
|
|
266
281
|
public duration and delimiter compatibility entries. The original checks used an
|
|
267
|
-
unpublished dependency candidate;
|
|
282
|
+
unpublished dependency candidate; Commonlib 0.2.3 pins the published
|
|
268
283
|
Octagonal Wheels 0.1.56 release. Source tests cover the internal copy adapter.
|
|
269
284
|
LiveSync continues to use the existing compatibility entry points, including
|
|
270
285
|
duration labels in ConfigSync and tagged lists in settings. Source and packed
|
|
271
286
|
checks do not establish real-Obsidian rendering or device behaviour.
|
|
272
287
|
|
|
273
288
|
|
|
274
|
-
|
|
289
|
+
<a id="shared-multi-key-locking-candidate-integration"></a>
|
|
290
|
+
|
|
291
|
+
## Shared multi-key locking: published integration
|
|
275
292
|
|
|
276
|
-
|
|
293
|
+
Commonlib 0.2.3 imports `withLocks` directly from
|
|
277
294
|
`octagonal-wheels/concurrency/lock` for offline Metadata repair and file-event
|
|
278
295
|
processing. Commonlib retains the `processFileEvent-` namespace, unique sorted
|
|
279
296
|
document IDs, and conflict hooks deferred until every lock has been released.
|
package/docs/releasing.md
CHANGED
|
@@ -34,12 +34,39 @@ Inspect the lockfile diff before committing it. A dependency-only maintenance ch
|
|
|
34
34
|
|
|
35
35
|
When a named host-specific risk is not established by the Commonlib owner checks, run the relevant downstream workflow against an exact Self-hosted LiveSync ref which consumes the package. It installs the tarball produced from the selected Commonlib commit and runs LiveSync type checks, unit tests, plug-in and application builds, and CLI E2E. Real Obsidian E2E remains local-only and is appropriate when the named risk concerns actual plug-in composition, storage, UI, or platform behaviour. These checks are conditional on the risk; LiveSync adoption is not a general release prerequisite.
|
|
36
36
|
|
|
37
|
+
## Coordinating package and application releases
|
|
38
|
+
|
|
39
|
+
A downstream release which imports a new package entry point or depends on new
|
|
40
|
+
behaviour must use a published immutable Commonlib version. Prepare and test
|
|
41
|
+
consumer branches against an exact local pack while the package is unreleased,
|
|
42
|
+
but retain those results as local-candidate evidence.
|
|
43
|
+
|
|
44
|
+
1. Complete the Commonlib owner checks and review the release source. For a
|
|
45
|
+
stable package, the exact reviewed release commit must be on `main` before
|
|
46
|
+
staging, as required by the trusted workflow below. An optional prerelease
|
|
47
|
+
can select a reviewed draft branch instead.
|
|
48
|
+
2. Stage the exact package to `next`, validate the published registry artefact,
|
|
49
|
+
and complete any specifically required downstream checks before promoting
|
|
50
|
+
that same version to `latest`. These remain separate maintainer operations.
|
|
51
|
+
3. Update each releasing consumer to the exact validated version and registry
|
|
52
|
+
integrity. Self-hosted LiveSync records one root dependency for its plug-in,
|
|
53
|
+
CLI, WebApp, and WebPeer; Bridge records its own Deno import map and lockfile.
|
|
54
|
+
Verify clean installation and the affected consumer boundaries before their
|
|
55
|
+
release workflows run.
|
|
56
|
+
|
|
57
|
+
Commonlib publication does not require every consumer branch to be merged.
|
|
58
|
+
Conversely, publishing Commonlib does not release an application or migrate an
|
|
59
|
+
existing database. A source checkout with the same version label as a registry
|
|
60
|
+
package is not a substitute for that immutable registry artefact.
|
|
61
|
+
|
|
37
62
|
## Version compatibility policy
|
|
38
63
|
|
|
39
64
|
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
65
|
|
|
41
66
|
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
67
|
|
|
68
|
+
Version 0.3.0 introduces a source compatibility change: the custom client passed to `LiveSyncJournalReplicator.ensureBucketIsCompatible` must provide `hasRemoteJournals(): Promise<boolean>`. The operation distinguishes an empty bucket from Journal files left without an identifying milestone. Keeping it required makes missing client capability a type error. The standard `JournalSyncCore` client already supplies it. This change uses the next minor version under the policy above.
|
|
69
|
+
|
|
43
70
|
## Preparing a release
|
|
44
71
|
|
|
45
72
|
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.
|
|
@@ -2,11 +2,17 @@
|
|
|
2
2
|
date: 2026-10-25
|
|
3
3
|
commonlib-version: "0.2.0"
|
|
4
4
|
self-hosted-livesync-version: "1.0.40"
|
|
5
|
-
status:
|
|
5
|
+
status: published
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Service feature composition
|
|
9
9
|
|
|
10
|
+
Publication update, 2nd November, 2026: the Service Hub source selection, narrowed
|
|
11
|
+
host dependencies, and signature-preserving composition helpers are published
|
|
12
|
+
in Commonlib [0.2.3](https://github.com/vrtmrz/livesync-commonlib/releases/tag/0.2.3)
|
|
13
|
+
and used by Self-hosted LiveSync 1.0.44. The front matter retains the original
|
|
14
|
+
review date and version baseline.
|
|
15
|
+
|
|
10
16
|
This guide is for Commonlib and maintained-host developers who are adding behaviour to the Service Hub composition. It explains when to extend a Service, create a serviceFeature, share a ServiceModule, or introduce a class which owns a resource.
|
|
11
17
|
|
|
12
18
|
The choice is primarily about dependency direction and ownership. Mutable state alone does not require a class or a ServiceModule.
|