@vrtmrz/livesync-commonlib 0.3.1 → 0.4.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.
- package/README.md +29 -2
- package/dist/managers/EntryManager/EntryManagerImpls.js +1 -1
- package/dist/p2p/P2PService.d.ts +10 -1
- package/dist/p2p/P2PService.js +25 -2
- package/dist/p2p/index.d.ts +4 -1
- package/dist/p2p/index.js +2 -0
- package/dist/p2p/p2pDiagnostics.d.ts +35 -0
- package/dist/p2p/p2pDiagnostics.js +76 -0
- package/dist/pouchdb/ReplicatorShim.batch.d.ts +30 -0
- package/dist/pouchdb/ReplicatorShim.batch.js +62 -0
- package/dist/pouchdb/ReplicatorShim.d.ts +14 -1
- package/dist/pouchdb/ReplicatorShim.js +64 -80
- package/dist/replication/SyncParamsHandler.d.ts +1 -0
- package/dist/replication/SyncParamsHandler.js +5 -1
- package/dist/replication/journal/JournalReceiveFailure.d.ts +7 -0
- package/dist/replication/journal/JournalReceiveFailure.js +16 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +22 -3
- package/dist/replication/journal/JournalSyncCore.js +288 -166
- package/dist/replication/journal/JournalSyncTypes.d.ts +13 -0
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +6 -1
- package/dist/replication/journal/LiveSyncJournalReplicator.js +31 -44
- package/dist/replication/journal/journalCompatibility.d.ts +9 -0
- package/dist/replication/journal/journalCompatibility.js +58 -0
- package/dist/replication/journal/journalReceive.d.ts +23 -0
- package/dist/replication/journal/journalReceive.js +44 -0
- package/dist/replication/journal/journalRecords.d.ts +10 -0
- package/dist/replication/journal/journalRecords.js +96 -0
- package/dist/replication/trystero/P2PLogCollector.d.ts +12 -0
- package/dist/replication/trystero/P2PLogCollector.js +107 -72
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +4 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.js +39 -2
- package/dist/replication/trystero/TrysteroReplicator.d.ts +21 -24
- package/dist/replication/trystero/TrysteroReplicator.js +191 -69
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +11 -0
- package/dist/replication/trystero/p2pReplicationDiagnostics.d.ts +9 -0
- package/dist/replication/trystero/p2pReplicationDiagnostics.js +51 -0
- package/dist/replication/trystero/p2pReplicationProgress.d.ts +15 -0
- package/dist/replication/trystero/p2pReplicationProgress.js +37 -0
- package/dist/replication/trystero/types.d.ts +3 -0
- package/dist/services/base/APIService.d.ts +10 -0
- package/dist/services/base/APIService.js +13 -0
- package/dist/services/base/IService.d.ts +2 -0
- package/docs/adr/2026_11_shared_storage_path_resolution.md +4 -2
- package/docs/design_docs/storage_path_resolution.md +8 -7
- package/docs/development.md +2 -2
- package/docs/journal-receive-recovery.md +69 -0
- package/docs/p2p-transport-lifecycle.md +117 -10
- package/docs/platform-storage.md +2 -2
- package/docs/releasing.md +6 -0
- package/docs/unicode-path-identity.md +9 -8
- package/package.json +2 -2
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type P2PDiagnosticInput } from "../../p2p/p2pDiagnostics.js";
|
|
2
|
+
import type { P2PReplicationResult } from "./TrysteroReplicator.js";
|
|
3
|
+
/** Describe an observed response without coercing an unavailable response to refusal. */
|
|
4
|
+
export declare function authenticationResultDiagnostic(peerId: string, response: unknown): P2PDiagnosticInput;
|
|
5
|
+
export declare function authenticationFailureDiagnostic(peerId: string, error: unknown, signal?: AbortSignal): P2PDiagnosticInput;
|
|
6
|
+
/** Classify the result without changing the operation's result object or error. */
|
|
7
|
+
export declare function pullResultDiagnostic(peerId: string, status: P2PReplicationResult["status"]): P2PDiagnosticInput;
|
|
8
|
+
export declare function pullFailureDiagnostic(peerId: string, cancelled: boolean): P2PDiagnosticInput;
|
|
9
|
+
export declare function notificationFailureDiagnostic(peerId: string, error: unknown): P2PDiagnosticInput;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { classifyP2PRpcFailure } from "../../p2p/p2pDiagnostics.js";
|
|
2
|
+
const authenticationFailureCodes = {
|
|
3
|
+
"rpc-timeout": "auth-timeout",
|
|
4
|
+
"rpc-cancelled": "auth-cancelled",
|
|
5
|
+
"rpc-error": "auth-error"
|
|
6
|
+
};
|
|
7
|
+
const notificationFailureCodes = {
|
|
8
|
+
"rpc-timeout": "change-notification-timeout",
|
|
9
|
+
"rpc-cancelled": "change-notification-cancelled",
|
|
10
|
+
"rpc-error": "change-notification-error"
|
|
11
|
+
};
|
|
12
|
+
function authenticationResultDiagnostic(peerId, response) {
|
|
13
|
+
if (response === true) {
|
|
14
|
+
return { category: "peer", code: "auth-accepted", peerId };
|
|
15
|
+
}
|
|
16
|
+
if (response === false) {
|
|
17
|
+
return { category: "peer", code: "auth-refused", reason: "authentication-refused", peerId };
|
|
18
|
+
}
|
|
19
|
+
return { category: "peer", code: "auth-error", reason: "authentication-unobserved", peerId };
|
|
20
|
+
}
|
|
21
|
+
function authenticationFailureDiagnostic(peerId, error, signal) {
|
|
22
|
+
const reason = classifyP2PRpcFailure(error, signal);
|
|
23
|
+
return { category: "peer", code: authenticationFailureCodes[reason], reason, peerId };
|
|
24
|
+
}
|
|
25
|
+
function pullResultDiagnostic(peerId, status) {
|
|
26
|
+
switch (status) {
|
|
27
|
+
case "completed":
|
|
28
|
+
return { category: "sync", code: "pull-completed", peerId };
|
|
29
|
+
case "cancelled":
|
|
30
|
+
return { category: "sync", code: "pull-cancelled", reason: "operation-cancelled", peerId };
|
|
31
|
+
default:
|
|
32
|
+
return { category: "sync", code: "pull-failed", reason: "replication-error", peerId };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function pullFailureDiagnostic(peerId, cancelled) {
|
|
36
|
+
if (cancelled) {
|
|
37
|
+
return pullResultDiagnostic(peerId, "cancelled");
|
|
38
|
+
}
|
|
39
|
+
return pullResultDiagnostic(peerId, "failed");
|
|
40
|
+
}
|
|
41
|
+
function notificationFailureDiagnostic(peerId, error) {
|
|
42
|
+
const reason = classifyP2PRpcFailure(error);
|
|
43
|
+
return { category: "sync", code: notificationFailureCodes[reason], reason, peerId };
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
authenticationFailureDiagnostic,
|
|
47
|
+
authenticationResultDiagnostic,
|
|
48
|
+
notificationFailureDiagnostic,
|
|
49
|
+
pullFailureDiagnostic,
|
|
50
|
+
pullResultDiagnostic
|
|
51
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ProgressInfo } from "../../pouchdb/ReplicatorShim.js";
|
|
2
|
+
import type { P2PReplicationDirectionProgress } from "./TrysteroReplicator.js";
|
|
3
|
+
/** Transient attempt correlation; no replication checkpoint is stored here. */
|
|
4
|
+
export type ProgressAcknowledgement = {
|
|
5
|
+
attempt: number;
|
|
6
|
+
finalProgress?: ProgressInfo;
|
|
7
|
+
};
|
|
8
|
+
export type AcknowledgedProgressAttempt = {
|
|
9
|
+
attempt: number;
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
checkedChanges: number;
|
|
12
|
+
};
|
|
13
|
+
export declare function projectDirectionProgress(info?: ProgressInfo | null): P2PReplicationDirectionProgress;
|
|
14
|
+
/** Return the next attempt snapshot, or reject stale, reordered, or already-ended progress. */
|
|
15
|
+
export declare function projectAcknowledgedAttempt(previous: AcknowledgedProgressAttempt | undefined, info: ProgressInfo | null | undefined, acknowledgement: ProgressAcknowledgement): AcknowledgedProgressAttempt | undefined;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function projectDirectionProgress(info) {
|
|
2
|
+
return {
|
|
3
|
+
max: info?.maxSeqInBatch ?? 0,
|
|
4
|
+
current: info?.lastSeq ?? 0,
|
|
5
|
+
isActive: info != null,
|
|
6
|
+
...info?.statistics ? { statistics: { ...info.statistics } } : {}
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
function projectAcknowledgedAttempt(previous, info, acknowledgement) {
|
|
10
|
+
if (!previous) {
|
|
11
|
+
return {
|
|
12
|
+
attempt: acknowledgement.attempt,
|
|
13
|
+
isActive: info != null,
|
|
14
|
+
checkedChanges: info?.statistics?.checkedChanges ?? 0
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
if (acknowledgement.attempt < previous.attempt) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
if (acknowledgement.attempt === previous.attempt) {
|
|
21
|
+
if (!previous.isActive) {
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
if ((info?.statistics?.checkedChanges ?? previous.checkedChanges) < previous.checkedChanges) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
attempt: acknowledgement.attempt,
|
|
30
|
+
isActive: info != null,
|
|
31
|
+
checkedChanges: info?.statistics?.checkedChanges ?? previous.checkedChanges
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
projectAcknowledgedAttempt,
|
|
36
|
+
projectDirectionProgress
|
|
37
|
+
};
|
|
@@ -5,6 +5,7 @@ import type { Confirm } from "../../interfaces/Confirm.js";
|
|
|
5
5
|
import type { AsyncActivityRunner } from "../../interfaces/AsyncActivityRunner.js";
|
|
6
6
|
import type { LiveSyncEventHub } from "../../hub/hub.js";
|
|
7
7
|
import type { MessageTranslator } from "../../services/base/MessageTranslator.js";
|
|
8
|
+
import type { P2PDiagnosticReporter } from "../../p2p/p2pDiagnostics.js";
|
|
8
9
|
import type { P2PAutomationCoordinator } from "./P2PAutomationCoordinator.js";
|
|
9
10
|
export declare const DIRECTION_REQUEST = "request";
|
|
10
11
|
export type DIRECTION_REQUEST = typeof DIRECTION_REQUEST;
|
|
@@ -78,6 +79,8 @@ export interface ReplicatorHost {
|
|
|
78
79
|
confirm: Confirm;
|
|
79
80
|
}
|
|
80
81
|
export interface ReplicatorHostEnv extends ReplicatorHost {
|
|
82
|
+
/** Observation port fenced by the owning room session; it cannot control transport work. */
|
|
83
|
+
reportDiagnostic?: P2PDiagnosticReporter;
|
|
81
84
|
/** Event channel owned by the containing Commonlib service composition. */
|
|
82
85
|
events: LiveSyncEventHub;
|
|
83
86
|
/** Message translation owned by the containing Commonlib service composition. */
|
|
@@ -72,6 +72,16 @@ export declare abstract class APIService<T extends ServiceContext = ServiceConte
|
|
|
72
72
|
* @param handler The handler function for the protocol.
|
|
73
73
|
*/
|
|
74
74
|
abstract registerProtocolHandler(action: string, handler: (params: Record<string, string>) => unknown): void;
|
|
75
|
+
/**
|
|
76
|
+
* Register synchronous cleanup for this host's normal terminal unload.
|
|
77
|
+
* The default uses the context-owned platform-unloaded event once per registration,
|
|
78
|
+
* after lifecycle handlers and local database closure. An unload interrupted before
|
|
79
|
+
* that event does not execute these callbacks.
|
|
80
|
+
* Native hosts may override this to retain their own unload timing. Callbacks are
|
|
81
|
+
* not awaited; resource retirement which must finish before database closure
|
|
82
|
+
* belongs to AppLifecycleService.onUnload instead.
|
|
83
|
+
*/
|
|
84
|
+
registerCleanup(callback: () => void): void;
|
|
75
85
|
/**
|
|
76
86
|
* Get the basic UI component for showing a confirmation dialog to the user.
|
|
77
87
|
*/
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { EVENT_PLATFORM_UNLOADED } from "../../events/coreEvents.js";
|
|
1
2
|
import { ServiceBase } from "./ServiceBase.js";
|
|
2
3
|
import { reactiveSource } from "octagonal-wheels/dataobject/reactive";
|
|
3
4
|
import { _fetch, compatGlobal } from "../../common/coreEnvFunctions.js";
|
|
@@ -9,6 +10,18 @@ class APIService extends ServiceBase {
|
|
|
9
10
|
showWindowOnRight(type) {
|
|
10
11
|
return this.showWindow(type);
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Register synchronous cleanup for this host's normal terminal unload.
|
|
15
|
+
* The default uses the context-owned platform-unloaded event once per registration,
|
|
16
|
+
* after lifecycle handlers and local database closure. An unload interrupted before
|
|
17
|
+
* that event does not execute these callbacks.
|
|
18
|
+
* Native hosts may override this to retain their own unload timing. Callbacks are
|
|
19
|
+
* not awaited; resource retirement which must finish before database closure
|
|
20
|
+
* belongs to AppLifecycleService.onUnload instead.
|
|
21
|
+
*/
|
|
22
|
+
registerCleanup(callback) {
|
|
23
|
+
this.context.events.onceEvent(EVENT_PLATFORM_UNLOADED, callback);
|
|
24
|
+
}
|
|
12
25
|
requestCount = reactiveSource(0);
|
|
13
26
|
responseCount = reactiveSource(0);
|
|
14
27
|
get isOnline() {
|
|
@@ -49,6 +49,8 @@ export interface IAPIService {
|
|
|
49
49
|
registerWindow<T>(type: string, factory: (leaf: T) => unknown): void;
|
|
50
50
|
addRibbonIcon(icon: string, title: string, callback: (evt: MouseEvent) => unknown): HTMLElement;
|
|
51
51
|
registerProtocolHandler(action: string, handler: (params: Record<string, string>) => unknown): void;
|
|
52
|
+
/** Registers synchronous cleanup at host terminal unload; callbacks are not awaited. */
|
|
53
|
+
registerCleanup(callback: () => void): void;
|
|
52
54
|
confirm: Confirm;
|
|
53
55
|
responseCount: ReactiveSource<number>;
|
|
54
56
|
requestCount: ReactiveSource<number>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
date: 2026-11-03
|
|
3
3
|
commonlib-version: "0.3.0"
|
|
4
4
|
self-hosted-livesync-version: "1.0.44"
|
|
5
|
-
status:
|
|
5
|
+
status: published
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Architectural Decision Record: Shared Storage Path Resolution
|
|
@@ -16,7 +16,9 @@ lifecycle. The [design](../design_docs/storage_path_resolution.md) defines the
|
|
|
16
16
|
accepted contract and remaining adoption work.
|
|
17
17
|
|
|
18
18
|
Accepted on 2026-11-03 against the versions recorded above. The implementation
|
|
19
|
-
is
|
|
19
|
+
is published in [Commonlib 0.3.1](https://www.npmjs.com/package/@vrtmrz/livesync-commonlib/v/0.3.1).
|
|
20
|
+
The original date and reviewed versions retain the accepted baseline.
|
|
21
|
+
Publication does not by itself enable NFC in WebApp or Bridge.
|
|
20
22
|
|
|
21
23
|
## Context
|
|
22
24
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
date: 2026-11-03
|
|
3
3
|
commonlib-version: "0.3.0"
|
|
4
4
|
self-hosted-livesync-version: "1.0.44"
|
|
5
|
-
status:
|
|
5
|
+
status: published
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Shared storage path resolution
|
|
@@ -10,8 +10,8 @@ status: accepted-unreleased
|
|
|
10
10
|
This design moves the CLI's existing Unicode-aware path resolution into
|
|
11
11
|
Commonlib so Node, File System Access, and Bridge can apply the same naming
|
|
12
12
|
rules. The shared resolver, path types, and Node and File System Access
|
|
13
|
-
integrations are
|
|
14
|
-
|
|
13
|
+
integrations are published in Commonlib 0.3.1. Installing the package does
|
|
14
|
+
not enable NFC in an existing host or migrate its database.
|
|
15
15
|
|
|
16
16
|
Accepted on 2026-11-03. The [decision record](../adr/2026_11_shared_storage_path_resolution.md)
|
|
17
17
|
records the ownership and compatibility choices. The [platform guide](../platform-storage.md#unicode-path-resolution)
|
|
@@ -258,10 +258,11 @@ of state, including hosts which report them as deletion and creation.
|
|
|
258
258
|
## Current adoption and publication status
|
|
259
259
|
|
|
260
260
|
The shared `/storage` contract, Node and File System Access adapters, and
|
|
261
|
-
explicit `DirectFileManipulator` path-version option are
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
registry artefact.
|
|
261
|
+
explicit `DirectFileManipulator` path-version option are published in
|
|
262
|
+
[Commonlib 0.3.1](https://www.npmjs.com/package/@vrtmrz/livesync-commonlib/v/0.3.1).
|
|
263
|
+
The original decision date and reviewed versions above retain the accepted
|
|
264
|
+
baseline. These additions are absent from the registry artefact `0.3.0`;
|
|
265
|
+
a source checkout carrying that version label is not the published artefact.
|
|
265
266
|
|
|
266
267
|
The CLI candidate uses the shared Node resolver, and the current Obsidian
|
|
267
268
|
candidate uses shared namespace and comparison helpers while retaining Vault
|
package/docs/development.md
CHANGED
|
@@ -21,8 +21,8 @@ Files elsewhere under `src` are implementation details unless a focused entry re
|
|
|
21
21
|
|
|
22
22
|
## Storage path boundary
|
|
23
23
|
|
|
24
|
-
The
|
|
25
|
-
strict component resolution. The [design](design_docs/storage_path_resolution.md)
|
|
24
|
+
The `/storage` entry, published in Commonlib 0.3.1, owns namespace helpers,
|
|
25
|
+
comparison keys, and strict component resolution. The [design](design_docs/storage_path_resolution.md)
|
|
26
26
|
and [decision record](adr/2026_11_shared_storage_path_resolution.md) separate
|
|
27
27
|
that contract from host handles, caches, watchers, and migration. See the
|
|
28
28
|
[platform guide](platform-storage.md#unicode-path-resolution) for composition.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
---
|
|
2
|
+
date: 2026-11-04
|
|
3
|
+
commonlib-version: "0.3.1"
|
|
4
|
+
self-hosted-livesync-version: "1.0.44"
|
|
5
|
+
status: published
|
|
6
|
+
published-in: "0.3.2"
|
|
7
|
+
publication-date: 2026-11-05
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Journal receive failures and recovery
|
|
11
|
+
|
|
12
|
+
This document describes receive behaviour published in
|
|
13
|
+
[Commonlib 0.3.2](https://github.com/vrtmrz/livesync-commonlib/releases/tag/0.3.2).
|
|
14
|
+
Hosts choose when to offer the manual skip operation and remain responsible
|
|
15
|
+
for Vault reflection and conflict handling.
|
|
16
|
+
|
|
17
|
+
## Stop and retry by default
|
|
18
|
+
|
|
19
|
+
A per-Journal decode, record-processing, or write failure stops receipt. The
|
|
20
|
+
failed Journal and all later Journals remain unacknowledged. The next normal
|
|
21
|
+
receive attempt retries the failed Journal before proceeding to later Journals.
|
|
22
|
+
Partial progress does not change this rule.
|
|
23
|
+
|
|
24
|
+
Chunks are written before Metadata, so a Chunk failure prevents that Journal's
|
|
25
|
+
Metadata stage. Metadata retains its received revision ancestry through
|
|
26
|
+
`new_edits: false`, whose empty success result is valid. Successfully written
|
|
27
|
+
records remain in the database when another record fails. There is no rollback.
|
|
28
|
+
|
|
29
|
+
A retry avoids rewriting existing records and delivers accepted Metadata to
|
|
30
|
+
the host again, including revisions saved before an earlier partial failure.
|
|
31
|
+
The host must tolerate repeated delivery. Chunk-arrival events describe only
|
|
32
|
+
successful writes. The `knownIDs` set suppresses unnecessary
|
|
33
|
+
outbound transfer; it does not prove that a receive has completed locally.
|
|
34
|
+
|
|
35
|
+
Warnings identify the failed Journal key and the stage where receipt stopped,
|
|
36
|
+
without logging document contents. A stop request lets current I/O settle and
|
|
37
|
+
closes the receive operation; it is not a Journal failure.
|
|
38
|
+
|
|
39
|
+
## Skip the stopped Journal and resume
|
|
40
|
+
|
|
41
|
+
When receipt stops on a Journal failure, a host may offer the explicit
|
|
42
|
+
'Skip stopped Journal and resume' operation. It accepts the exact stopped
|
|
43
|
+
failure snapshot, marks only that Journal received, and resumes receipt of the
|
|
44
|
+
remaining Journals. If another Journal fails, receipt stops at that Journal
|
|
45
|
+
and requires a separate decision.
|
|
46
|
+
|
|
47
|
+
Skipping does not roll back partial writes. It can leave content or revision
|
|
48
|
+
history missing, and receipt of later Journals does not prove that every file
|
|
49
|
+
can be reconstructed or reflected to storage. Each skip acknowledges the
|
|
50
|
+
failed Journal despite its warning.
|
|
51
|
+
|
|
52
|
+
The stopped failure is runtime state, not a persistent skip mode or failure
|
|
53
|
+
queue. After a restart, normal receipt must encounter the failure again before
|
|
54
|
+
the host can offer a skip for it.
|
|
55
|
+
|
|
56
|
+
## Replay and host boundaries
|
|
57
|
+
|
|
58
|
+
The existing 'Reset journal received history' operation clears `receivedFiles`
|
|
59
|
+
and `knownIDs`, while retaining the local database and `sentFiles`. After the
|
|
60
|
+
failure has been corrected, resetting this history lets receipt replay remote
|
|
61
|
+
Journals other than those sent by this device. Replay requires the affected
|
|
62
|
+
remote objects to remain available and readable; resetting history cannot
|
|
63
|
+
recreate missing remote bytes.
|
|
64
|
+
|
|
65
|
+
A host operation which replaces the local database, such as CLI Fetch, has
|
|
66
|
+
separate reconstruction and completion rules. Repeating Fetch is not the
|
|
67
|
+
same-database retry described above. Journal receipt also remains separate
|
|
68
|
+
from Vault reflection and conflict handling; hosts must preserve their own
|
|
69
|
+
failure and content-protection checks.
|
|
@@ -55,19 +55,126 @@ Ordinary consumers use the focused views returned by the service feature. They d
|
|
|
55
55
|
|
|
56
56
|
The stable view objects close over the same private context, but do not expose that context or one another. They are capability boundaries for consumers, not separately allocated owners. A consumer receives only the views it needs.
|
|
57
57
|
|
|
58
|
-
| View | Implemented responsibility
|
|
59
|
-
| ----------------------------- |
|
|
60
|
-
| `P2PTransportLifecycle` | observe the current connection and accept explicit connect or disconnect intent
|
|
61
|
-
| `P2PConnectionProbeAdmission` | arbitrate a complete Setup signalling check against the current relay binding
|
|
62
|
-
| `P2PPeerDirectory` | supply a current peer advertisement snapshot
|
|
63
|
-
| `P2PPeerAdmission` | apply or revoke temporary and persisted acceptance decisions
|
|
64
|
-
| `P2PTargetedTransfer` | pull, request push, synchronise one peer, or execute the persisted configured-target set without peer-selection dialogue
|
|
65
|
-
| `P2PChangeRelay` | watch or unwatch a peer and enable or disable local-change broadcast
|
|
66
|
-
| `P2PConfigurationExchange` | retrieve configuration from one explicitly selected peer
|
|
67
|
-
| `P2PDiagnostics` | request status and project RTC metrics without exposing
|
|
58
|
+
| View | Implemented responsibility |
|
|
59
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
60
|
+
| `P2PTransportLifecycle` | observe the current connection and accept explicit connect or disconnect intent |
|
|
61
|
+
| `P2PConnectionProbeAdmission` | arbitrate a complete Setup signalling check against the current relay binding |
|
|
62
|
+
| `P2PPeerDirectory` | supply a current peer advertisement snapshot |
|
|
63
|
+
| `P2PPeerAdmission` | apply or revoke temporary and persisted acceptance decisions |
|
|
64
|
+
| `P2PTargetedTransfer` | pull, request push, synchronise one peer, or execute the persisted configured-target set without peer-selection dialogue |
|
|
65
|
+
| `P2PChangeRelay` | watch or unwatch a peer and enable or disable local-change broadcast |
|
|
66
|
+
| `P2PConfigurationExchange` | retrieve configuration from one explicitly selected peer |
|
|
67
|
+
| `P2PDiagnostics` | observe diagnostic events and replication progress, request status, and project RTC metrics without exposing transport handles |
|
|
68
68
|
|
|
69
69
|
The optional host UI factory is an explicit compatibility boundary. Commonlib supplies both the deprecated compatibility Replicator and the stable views. Maintained modal transport and status controls use the views; legacy interactive execution and rebuild-specific setup markers may still use the compatibility Replicator. Receiving that façade does not transfer ownership of the room. `UseP2PReplicatorResult.replicator` remains deprecated for the same bounded migration work.
|
|
70
70
|
|
|
71
|
+
## Per-peer replication progress
|
|
72
|
+
|
|
73
|
+
`P2PDiagnostics.getReplicationProgress?()` supplies a read-only snapshot sorted by
|
|
74
|
+
peer ID. `onReplicationProgress?(listener)` observes subsequent snapshots and
|
|
75
|
+
returns a subscription disposer. Hosts should check that these optional methods
|
|
76
|
+
exist when composing an older implementation. The service owns a `P2PLogCollector`
|
|
77
|
+
for these views, and releases its event registrations on platform unload. Peer
|
|
78
|
+
departure removes that peer, and room disconnect or replacement clears the snapshot.
|
|
79
|
+
Repeated advertisements update the peer name and retain its structured progress;
|
|
80
|
+
the legacy map and text keep their existing advertisement reset behaviour.
|
|
81
|
+
|
|
82
|
+
Each direction retains the established `current` and `max` numeric source sequence
|
|
83
|
+
values and `isActive` flag. `P2PLogCollector.p2pReplicationResult` and
|
|
84
|
+
`p2pReplicationLine` keep those sequence units and the existing text format.
|
|
85
|
+
Sequence subtraction does not measure transferred documents.
|
|
86
|
+
|
|
87
|
+
A direction may additionally supply `statistics: { documents, checkedChanges }`:
|
|
88
|
+
|
|
89
|
+
- `documents` counts successfully transferred database document revisions, including
|
|
90
|
+
Metadata and Chunks. It excludes already-held revisions and refresh reads. Two
|
|
91
|
+
missing conflict revisions of one document count as two transfers.
|
|
92
|
+
- `checkedChanges` counts changes-feed entries processed for revision comparison,
|
|
93
|
+
including entries which require no transfer and repeated entries checked again.
|
|
94
|
+
|
|
95
|
+
Both counters start at zero for each finite pull attempt, accumulate across its
|
|
96
|
+
batches, and retain their final values when the direction becomes inactive. The
|
|
97
|
+
sending direction observes the receiver's acknowledgements. An absent
|
|
98
|
+
`statistics` field means the peer has not supplied these counts; it differs from
|
|
99
|
+
an observed zero. A completed all-known batch updates statistics without invoking
|
|
100
|
+
the legacy document-processing callback. That callback still receives the
|
|
101
|
+
refreshed documents only when the batch has missing revisions.
|
|
102
|
+
|
|
103
|
+
The shim's optional `onStatistics` observer also reports accepted writes when a
|
|
104
|
+
later write, refresh, or checkpoint step fails. These counts do not establish a
|
|
105
|
+
committed checkpoint or a completed replication attempt. The observer's sequence
|
|
106
|
+
retains the latest successfully processed batch boundary when a later batch fails
|
|
107
|
+
or is cancelled before writing; the document callback keeps its original batch
|
|
108
|
+
sequence values. Observer failures do not change document processing or checkpoint
|
|
109
|
+
safety.
|
|
110
|
+
|
|
111
|
+
Progress uses the existing `onProgressAcknowledged` RPC. An optional third argument
|
|
112
|
+
carries a transient attempt number, allocated monotonically across room sessions
|
|
113
|
+
within the receiver's module lifetime. Trystero's default peer ID is also generated
|
|
114
|
+
for that module lifetime. The sending observer ignores older attempts, reordered
|
|
115
|
+
batch counts, and updates received after the same attempt's terminal marker. The
|
|
116
|
+
terminal acknowledgement keeps the legacy absent progress argument and adds the
|
|
117
|
+
final progress in its optional metadata, so a terminal marker which overtakes a
|
|
118
|
+
batch still supplies final sequences and counts. Older peers can ignore the extra
|
|
119
|
+
argument and continue using sequence progress. No handshake, retry, heartbeat, or
|
|
120
|
+
persistent replication state is added.
|
|
121
|
+
|
|
122
|
+
## Latest diagnostic observations
|
|
123
|
+
|
|
124
|
+
The optional `P2PDiagnostics.getDiagnosticSnapshot()` and
|
|
125
|
+
`onDiagnosticEvent(listener)` methods expose the latest `relay`, `peer`, and
|
|
126
|
+
`sync` observations. A missing category is unobserved, rather than a successful
|
|
127
|
+
result or a measured zero. Each immutable event contains a source timestamp in
|
|
128
|
+
milliseconds since the Unix epoch, a fixed code, an optional fixed reason, and an
|
|
129
|
+
optional peer ID. Events do not include device names, settings, room IDs, keys,
|
|
130
|
+
SDP, IP addresses, or arbitrary error text.
|
|
131
|
+
|
|
132
|
+
The stable service owns only one latest event per category. Peer departure and
|
|
133
|
+
room closure retain the latest peer and sync evidence after the peer row has
|
|
134
|
+
disappeared. Room replacement fences the previous session's reporting port before
|
|
135
|
+
retirement, so a late authentication result, notification, or transport callback
|
|
136
|
+
cannot overwrite the current session's evidence. Platform unload detaches the
|
|
137
|
+
store and its subscribers. Older diagnostics implementations may omit both methods.
|
|
138
|
+
|
|
139
|
+
Accepted observations are also published once through the context-owned
|
|
140
|
+
`EVENT_P2P_DIAGNOSTIC` channel exported from `/p2p`. Hosts can format that same
|
|
141
|
+
stream for their summary and diagnostic log. Identical events with the same source
|
|
142
|
+
timestamp are coalesced; repeated advertisements of an already-known peer and
|
|
143
|
+
unchanged watch decisions do not produce another diagnostic transition. No
|
|
144
|
+
server-snapshot, document, or batch progress subscription is used for this stream.
|
|
145
|
+
Diagnostic subscribers and source reporting callbacks cannot change authentication,
|
|
146
|
+
replication outcomes, or room lifecycle control flow. Hosts using the general
|
|
147
|
+
EventHub channel should contain exceptions in their own listener, as with their
|
|
148
|
+
other EventHub listeners.
|
|
149
|
+
|
|
150
|
+
Owned observations include room opening, opening completion or failure, closure,
|
|
151
|
+
join-error callbacks, validated first peer advertisements, peer departure,
|
|
152
|
+
authentication acceptance or refusal, classified RPC failures, finite pull
|
|
153
|
+
outcomes, and watch or change-notification boundaries. Authentication refusal,
|
|
154
|
+
RPC timeout, cancellation, and other RPC errors have distinct fixed codes while
|
|
155
|
+
retaining the existing boolean and replication result contracts. Only an explicit
|
|
156
|
+
`false` authentication response indicates refusal. Missing or non-boolean responses
|
|
157
|
+
use `auth-error` with `authentication-unobserved`, while keeping the existing return
|
|
158
|
+
value and admission policy. An unavailable
|
|
159
|
+
remote broadcast status remains unobserved rather than being reported as an
|
|
160
|
+
explicit false result. Broadcast enablement is observed only after feed creation,
|
|
161
|
+
and an unexpected feed error supplies a fixed failure reason when broadcasting
|
|
162
|
+
stops. No diagnostic operation retries, reconnects, or alters a checkpoint.
|
|
163
|
+
|
|
164
|
+
A room-opened observation means that the owned Trystero room is active. It does
|
|
165
|
+
not establish that a Nostr relay accepted its advertisement. In the reviewed
|
|
166
|
+
`@trystero-p2p/nostr` 0.26.0 API, relay `NOTICE`, rejected `OK`, and `CLOSED`
|
|
167
|
+
messages are written internally to the console without an owned callback. The
|
|
168
|
+
public socket getter is shared across rooms and replaces sockets during
|
|
169
|
+
reconnection. This diagnostic stream therefore uses `relay-details-unobserved`
|
|
170
|
+
and does not intercept console output or WebSocket globals, attach to shared
|
|
171
|
+
sockets, or poll. Relay rejection text remains unavailable through this API.
|
|
172
|
+
|
|
173
|
+
The existing detailed RTC failure subscription is also process-wide and supplies
|
|
174
|
+
no peer or room attribution. Its typed failure reasons remain in the existing
|
|
175
|
+
Diag logging path. They are not presented as a failure of an arbitrary owned room
|
|
176
|
+
or peer in the new stream.
|
|
177
|
+
|
|
71
178
|
## Room demands and automation generations
|
|
72
179
|
|
|
73
180
|
`P2PRoomSessionOwner` opens a room while at least one room demand exists. Persistent demands are currently `explicit`, `automatic`, and `rebuild-continuation`. A finite target operation receives an unexported symbol demand for its own lifetime. Removing one demand cannot close a room retained by another persistent or finite demand.
|
package/docs/platform-storage.md
CHANGED
|
@@ -63,8 +63,8 @@ The File System Access API does not expose portable creation times or permit set
|
|
|
63
63
|
|
|
64
64
|
## Unicode path resolution
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
File System Access hosts can use the same NFC comparison and component
|
|
66
|
+
These APIs are published in [Commonlib 0.3.1](https://www.npmjs.com/package/@vrtmrz/livesync-commonlib/v/0.3.1).
|
|
67
|
+
Node and File System Access hosts can use the same NFC comparison and component
|
|
68
68
|
resolution while retaining the names listed by their filesystem. Hosts still
|
|
69
69
|
own the selected identity mode and its database migration.
|
|
70
70
|
|
package/docs/releasing.md
CHANGED
|
@@ -67,6 +67,12 @@ For 0.2.3, the documented exceptions are the `HandlerFunction.setHandler` and `L
|
|
|
67
67
|
|
|
68
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
69
|
|
|
70
|
+
Version 0.4.0 changes the P2P action wire format by adopting Trystero 0.26.0.
|
|
71
|
+
Communicating peers must use compatible transport versions. Custom `IAPIService`
|
|
72
|
+
implementations must also provide `registerCleanup(callback: () => void): void`;
|
|
73
|
+
subclasses of `APIService` inherit its default terminal-unload implementation.
|
|
74
|
+
These changes use the next minor version under the compatibility policy.
|
|
75
|
+
|
|
70
76
|
## Preparing a release
|
|
71
77
|
|
|
72
78
|
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.
|
|
@@ -109,11 +109,13 @@ the package protocol support alone does not mean those host integrations have
|
|
|
109
109
|
shipped.
|
|
110
110
|
|
|
111
111
|
The shared `/storage` resolver, Node and File System Access adapters, and
|
|
112
|
-
explicit `DirectFileManipulator` path-version option are
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
explicit `DirectFileManipulator` path-version option are published in
|
|
113
|
+
[Commonlib 0.3.1](https://www.npmjs.com/package/@vrtmrz/livesync-commonlib/v/0.3.1),
|
|
114
|
+
as described in the [shared storage path design](design_docs/storage_path_resolution.md).
|
|
115
|
+
They are absent from the registry artefact `0.3.0`; select the new immutable
|
|
116
|
+
version rather than a source checkout carrying an older version label. Shared
|
|
117
|
+
path adoption remains in [LiveSync #1315](https://github.com/vrtmrz/obsidian-livesync/pull/1315)
|
|
118
|
+
and [Bridge #92](https://github.com/vrtmrz/livesync-bridge/pull/92).
|
|
117
119
|
|
|
118
120
|
| Consumer surface | Published baseline | Current candidate status |
|
|
119
121
|
| --- | --- | --- |
|
|
@@ -122,7 +124,7 @@ does not make these additions part of the published artefact.
|
|
|
122
124
|
| Bridge | Legacy version `0` remains the default baseline. | The Bridge candidate enables version `1` only by explicit peer opt-in; every peer in a group must use the same path version. Its ordinary case-mismatch check remains separate from the required version check. |
|
|
123
125
|
| WebApp | Legacy version `0` only. | WebApp remains Legacy-only; an explicit version `1` is rejected at startup. The browser storage primitive alone does not enable application support. |
|
|
124
126
|
| WebPeer | A vaultless P2P utility. | It is not an NFC filesystem host. |
|
|
125
|
-
| `DirectFileManipulator` |
|
|
127
|
+
| `DirectFileManipulator` | Commonlib `0.3.1` supports explicit version `1`; omitted or `0` retains Legacy identity. A populated database in the opposite mode is rejected. | The caller still owns physical path handling and coordinated migration. |
|
|
126
128
|
|
|
127
129
|
`PathService` owns logical ID derivation. In the unreleased LiveSync host
|
|
128
130
|
candidate, the CLI resolves NFC-equivalent names against directory entries
|
|
@@ -172,8 +174,7 @@ checkpoints; the P2P source mark and target checkpoint handle database
|
|
|
172
174
|
replacement. These operations do not rewrite IDs or merge histories in place.
|
|
173
175
|
P2P retains its existing source selection during Fetch.
|
|
174
176
|
|
|
175
|
-
|
|
176
|
-
feature. The unpublished `DirectFileManipulator` addition exposes explicit
|
|
177
|
+
Commonlib 0.3.1 `DirectFileManipulator` exposes explicit
|
|
177
178
|
`pathNormalizationVersion` options, defaulting to `0`: missing or `0` retains
|
|
178
179
|
Legacy and rejects an NFC database; `1` accepts NFC and rejects a populated
|
|
179
180
|
Legacy database before feature negotiation can change its declaration. A
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vrtmrz/livesync-commonlib",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -1020,7 +1020,7 @@
|
|
|
1020
1020
|
"@smithy/middleware-apply-body-checksum": "^4.3.9",
|
|
1021
1021
|
"@smithy/types": "^4.14.3",
|
|
1022
1022
|
"@smithy/util-retry": "^4.4.5",
|
|
1023
|
-
"@trystero-p2p/nostr": "0.
|
|
1023
|
+
"@trystero-p2p/nostr": "0.26.0",
|
|
1024
1024
|
"@vrtmrz/value-store": "0.1.0",
|
|
1025
1025
|
"diff-match-patch": "^1.0.5",
|
|
1026
1026
|
"events": "^3.3.0",
|