@vrtmrz/livesync-commonlib 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +5 -1
  2. package/dist/common/models/setting.p2p.d.ts +2 -0
  3. package/dist/common/models/setting.p2p.js +5 -0
  4. package/dist/interfaces/ServiceModule.d.ts +13 -4
  5. package/dist/managers/ChunkFetcher.js +10 -0
  6. package/dist/p2p/P2PService.d.ts +123 -0
  7. package/dist/p2p/P2PService.js +271 -0
  8. package/dist/p2p/index.d.ts +8 -0
  9. package/dist/p2p/index.js +8 -0
  10. package/dist/pouchdb/LiveSyncLocalDB.d.ts +3 -0
  11. package/dist/pouchdb/LiveSyncLocalDB.js +23 -3
  12. package/dist/pouchdb/ReplicatorShim.d.ts +43 -4
  13. package/dist/pouchdb/ReplicatorShim.js +78 -5
  14. package/dist/replication/CentralCompatibility.d.ts +38 -0
  15. package/dist/replication/CentralCompatibility.js +39 -0
  16. package/dist/replication/CentralRemoteAdministration.d.ts +84 -0
  17. package/dist/replication/CentralRemoteAdministration.js +71 -0
  18. package/dist/replication/LiveSyncAbstractReplicator.d.ts +19 -6
  19. package/dist/replication/ProviderCapability.d.ts +32 -0
  20. package/dist/replication/ProviderCapability.js +27 -0
  21. package/dist/replication/RemoteResource.d.ts +85 -0
  22. package/dist/replication/RemoteResource.js +17 -0
  23. package/dist/replication/ReplicatorInstance.d.ts +23 -0
  24. package/dist/replication/ReplicatorProvider.d.ts +249 -0
  25. package/dist/replication/ReplicatorProvider.js +145 -0
  26. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +54 -8
  27. package/dist/replication/couchdb/LiveSyncReplicator.js +411 -158
  28. package/dist/replication/index.d.ts +10 -0
  29. package/dist/replication/index.js +70 -0
  30. package/dist/replication/journal/JournalSyncCore.d.ts +2 -0
  31. package/dist/replication/journal/JournalSyncCore.js +18 -5
  32. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +38 -10
  33. package/dist/replication/journal/LiveSyncJournalReplicator.js +271 -63
  34. package/dist/replication/journal/LiveSyncJournalReplicatorEnv.d.ts +8 -0
  35. package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +2 -0
  36. package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -0
  37. package/dist/replication/journal/objectstore/MinioStorageAdapter.js +10 -1
  38. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +12 -40
  39. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +52 -152
  40. package/dist/replication/trystero/P2PAutomationCoordinator.d.ts +20 -0
  41. package/dist/replication/trystero/P2PAutomationCoordinator.js +43 -0
  42. package/dist/replication/trystero/P2PConnectionProbeAdmission.d.ts +32 -0
  43. package/dist/replication/trystero/P2PConnectionProbeAdmission.js +10 -0
  44. package/dist/replication/trystero/P2PReplicationWire.d.ts +31 -0
  45. package/dist/replication/trystero/P2PReplicationWire.js +50 -0
  46. package/dist/replication/trystero/P2PRoomSession.d.ts +32 -0
  47. package/dist/replication/trystero/P2PRoomSession.js +101 -0
  48. package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +61 -0
  49. package/dist/replication/trystero/P2PRoomSessionOwner.js +249 -0
  50. package/dist/replication/trystero/TrysteroReplicator.d.ts +88 -36
  51. package/dist/replication/trystero/TrysteroReplicator.js +273 -113
  52. package/dist/replication/trystero/TrysteroReplicatorP2PClient.d.ts +7 -4
  53. package/dist/replication/trystero/TrysteroReplicatorP2PClient.js +21 -14
  54. package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +26 -3
  55. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +84 -39
  56. package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -6
  57. package/dist/replication/trystero/addP2PEventHandlers.d.ts +8 -12
  58. package/dist/replication/trystero/addP2PEventHandlers.js +19 -19
  59. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.d.ts +9 -0
  60. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +30 -0
  61. package/dist/replication/trystero/types.d.ts +18 -0
  62. package/dist/replication/trystero/types.js +5 -1
  63. package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +3 -3
  64. package/dist/replication/trystero/useP2PReplicatorCommands.js +6 -8
  65. package/dist/replication/trystero/useP2PReplicatorFeature.d.ts +16 -7
  66. package/dist/replication/trystero/useP2PReplicatorFeature.js +72 -55
  67. package/dist/rpc/RpcRoom.d.ts +15 -1
  68. package/dist/rpc/RpcRoom.js +176 -41
  69. package/dist/rpc/RpcSession.d.ts +3 -1
  70. package/dist/rpc/RpcSession.js +2 -2
  71. package/dist/rpc/index.d.ts +1 -1
  72. package/dist/rpc/transports/trysteroUtils.js +2 -1
  73. package/dist/rpc/types.d.ts +26 -1
  74. package/dist/serviceFeatures/checkRemoteSize.js +5 -2
  75. package/dist/serviceModules/Rebuilder.js +66 -56
  76. package/dist/services/base/AppLifecycleService.d.ts +4 -0
  77. package/dist/services/base/AppLifecycleService.js +4 -0
  78. package/dist/services/base/ControlService.js +0 -4
  79. package/dist/services/base/DatabaseEventService.js +1 -1
  80. package/dist/services/base/IService.d.ts +105 -8
  81. package/dist/services/base/ReadinessEvaluator.d.ts +40 -0
  82. package/dist/services/base/ReadinessEvaluator.js +16 -0
  83. package/dist/services/base/RemoteConnection.d.ts +3 -0
  84. package/dist/services/base/RemoteService.js +2 -2
  85. package/dist/services/base/ReplicationService.d.ts +45 -10
  86. package/dist/services/base/ReplicationService.js +248 -91
  87. package/dist/services/base/ReplicationService.readiness.d.ts +52 -0
  88. package/dist/services/base/ReplicationService.readiness.js +87 -0
  89. package/dist/services/base/ReplicationService.typedReplication.d.ts +83 -0
  90. package/dist/services/base/ReplicationService.typedReplication.js +191 -0
  91. package/dist/services/base/ReplicatorService.activeReplicatorState.d.ts +47 -0
  92. package/dist/services/base/ReplicatorService.activeReplicatorState.js +107 -0
  93. package/dist/services/base/ReplicatorService.centralRemoteAdministration.d.ts +5 -0
  94. package/dist/services/base/ReplicatorService.centralRemoteAdministration.js +44 -0
  95. package/dist/services/base/ReplicatorService.d.ts +75 -14
  96. package/dist/services/base/ReplicatorService.js +232 -53
  97. package/dist/services/base/ReplicatorService.remoteResourceResolver.d.ts +4 -0
  98. package/dist/services/base/ReplicatorService.remoteResourceResolver.js +11 -0
  99. package/dist/services/base/TweakValueService.d.ts +3 -2
  100. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +1 -1
  101. package/dist/services/lib/HandlerUtils.d.ts +24 -0
  102. package/dist/services/lib/HandlerUtils.js +40 -0
  103. package/docs/development.md +40 -7
  104. package/docs/p2p-transport-lifecycle.md +135 -11
  105. package/docs/proven-in-use.md +5 -1
  106. package/docs/remote-configurations.md +4 -0
  107. package/docs/service-feature-composition.md +250 -0
  108. package/package.json +17 -1
  109. package/dist/replication/trystero/P2PReplicatorBase.d.ts +0 -19
  110. package/dist/replication/trystero/P2PReplicatorCore.d.ts +0 -8
  111. package/dist/replication/trystero/P2PReplicatorCore.js +0 -56
  112. package/dist/replication/trystero/TrysteroReplicatorP2PConnection.d.ts +0 -2
  113. package/dist/replication/trystero/TrysteroReplicatorP2PConnection.js +0 -4
  114. /package/dist/replication/{trystero/P2PReplicatorBase.js → ReplicatorInstance.js} +0 -0
@@ -1,23 +1,135 @@
1
- # P2P transport lifecycle
1
+ ---
2
+ date: 2026-08-28
3
+ commonlib-version: "0.1.20"
4
+ self-hosted-livesync-version: "1.0.21"
5
+ status: unreleased
6
+ ---
2
7
 
3
- This guide is for developers composing Commonlib's Trystero P2P service. It describes resource ownership and shutdown semantics; it is not a general Trystero API reference.
8
+ # P2P service and transport lifecycle
9
+
10
+ This design document records the implemented Commonlib Trystero P2P service. It defines the current resource owners, demand and automation lifecycles, replacement fences, and shutdown semantics. It is not a migration plan or a general Trystero API reference.
4
11
 
5
12
  ## Ownership layers
6
13
 
7
- The P2P composition has three distinct owners:
14
+ The P2P composition deliberately separates stable host policy from one replaceable room membership:
8
15
 
9
- - `useP2PReplicatorFeature` owns the current `LiveSyncTrysteroReplicator` exposed to a host;
10
- - the Commonlib P2P host owns LiveSync RPC, advertisements, client proxies, diagnostics, and one Trystero room binding; and
16
+ - `useP2PReplicatorFeature` creates one P2P service composition for the host lifetime and registers non-owning active-provider adapters over it;
17
+ - the service composition holds focused stable contract views and one host lifecycle view over a private context;
18
+ - the private context owns explicit-connect and explicit-disconnect intent, the disconnect veto, host lifecycle closure, application lifecycle generation, delayed AutoStart work, and the resource references shared by those views;
19
+ - `P2PRoomSessionOwner` owns persistent and finite room demands, serialised room construction and retirement, effective-binding comparison, publication of the current session, and the stable `P2PAutomationCoordinator`;
20
+ - `P2PAutomationCoordinator` owns baseline-transfer de-duplication across room replacement, including in-flight work until settlement and completed peer baselines for the current automation generation and logical database identity;
21
+ - each `P2PRoomSession` owns one Trystero room binding, its LiveSync RPC surface, advertisements, client proxies, diagnostics, its session-lifetime controller, and the finite replication operations admitted through that room; and
11
22
  - Trystero owns the underlying shared WebRTC peers and Nostr relay clients.
12
23
 
13
- Consumers must retain the service-feature result and resolve `result.replicator` at the point of use. A settings or database lifecycle transition can replace the replicator. A command, event listener, or view which captured the former instance could otherwise reopen a transport which the current service no longer owns.
24
+ The implemented ownership topology is:
25
+
26
+ ```text
27
+ host composition
28
+ └── useP2PReplicatorFeature()
29
+ └── P2P service composition stable projection identity
30
+ ├── host lifecycle view host-only application lifecycle operations
31
+ ├── focused views stable, non-owning capability projections
32
+ ├── compatibility Replicator deprecated, non-owning façade
33
+ └── private P2P service context stable policy and shared references
34
+ └── P2PRoomSessionOwner stable session owner and lifecycle queue
35
+ ├── P2PAutomationCoordinator stable across room replacement
36
+ └── current P2PRoomSession replaceable room-membership owner
37
+ ├── P2PHost and TrysteroReplicator
38
+ │ session-bound RPC and transfer state
39
+ └── Trystero room membership and action namespace
40
+ └── Trystero runtime shared relay clients and physical peers
41
+ ```
42
+
43
+ Only `P2PRoomSessionOwner` constructs, publishes, replaces, and retires the current `P2PRoomSession`. Module-level service operations change its demand set through the private context. Views, active-provider adapters, the host lifecycle projection, and the compatibility Replicator do not acquire session ownership.
44
+
45
+ | Owner | State which survives room replacement | State retired with one room session |
46
+ | --------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
47
+ | P2P service composition | stable lifecycle and contract-view identity | none |
48
+ | private P2P service context | explicit-disconnect veto, host lifecycle closure, and lifecycle generation | delayed AutoStart callback is cancelled or invalidated before lifecycle retirement |
49
+ | `P2PRoomSessionOwner` | persistent and finite room-demand bookkeeping, lifecycle queue, and automation coordinator | published session and its effective binding |
50
+ | `P2PAutomationCoordinator` | completed baseline peer names for the current generation and in-flight promises until settlement | none merely because a room session retires; a lifecycle or identity change clears completed records and prevents stale publication |
51
+ | `P2PRoomSession` | none | room, host, Replicator, advertisements, clients, callbacks, feeds, and admitted finite-operation controllers |
52
+ | Trystero | implementation-owned relay clients and potentially shared physical peers | the departed LiveSync room's membership, action namespace, and callbacks are detached through the Trystero room API |
53
+
54
+ Ordinary consumers use the focused views returned by the service feature. They do not receive the room session, raw host, raw peer connection, or concrete Replicator. `ReplicatorService` receives a fresh non-owning active adapter when it selects P2P as the main remote. Disposing or replacing that adapter neither leaves the service-owned room nor cancels work owned by another service consumer. The explicit stop capability cancels current finite transfers without retiring the room.
55
+
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
+
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 the room or peer connection |
68
+
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
+
71
+ ## Room demands and automation generations
72
+
73
+ `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.
74
+
75
+ Acquiring a finite room demand opens or reuses a session and then supplies that session to the operation. `P2PRoomSession.runFiniteOperation()` has a different responsibility: it admits one abortable transfer into an already owned session. It does not acquire or release a room demand. Configured-target discovery and execution are admitted as one such session operation, so retirement cancels and awaits the outer orchestration as well as any peer transfer which it has started. This separation prevents the settlement of one transfer from closing a room retained by AutoStart or another operation.
76
+
77
+ The private service state owns the explicit-disconnect veto. A finite demand or AutoStart cannot reopen the room while that veto is set. A later explicit connect clears it. A completed Rebuild may acquire the distinct `rebuild-continuation` demand because that action continues an already authorised workflow; it does not clear the veto for later AutoStart work.
78
+
79
+ Host lifecycle closure also sets a separate private reversible state before it invalidates delayed automation and closes the room. Settings reconciliation and finite stable-view operations cannot add a room demand while that state is set. Explicit connect, a separately authorised Rebuild continuation, or AutoStart scheduling after host resume clears it; settings reconciliation alone does not. Clearing this lifecycle state does not clear the explicit-disconnect veto: resumed AutoStart still observes the veto, while Rebuild retains its separate authority.
80
+
81
+ AutoStart is scheduled against the current application lifecycle generation. Suspension, unload, explicit disconnect, and other lifecycle closes invalidate the generation and clear the delayed callback. The host's resumed-lifecycle scheduling establishes the next generation in which AutoStart may clear host lifecycle closure, and the callback then re-reads current settings before it asks the owner to add the `automatic` demand. WebPeer and other maintained hosts dispatch the shared resumed-lifecycle hook rather than scheduling a second raw room open.
82
+
83
+ The stable `P2PAutomationCoordinator` de-duplicates the initial baseline for one normalised peer name across AutoSync and configured-target synchronisation. A transport-only room replacement does not repeat a completed baseline when the peer namespace and local database identity remain unchanged. Profile selection and policy changes do not replace the room. A new application lifecycle, a changed effective P2P peer namespace, or a different local database object clears completed baselines. The persisted profile identifier is not part of that namespace. In-flight operations are neither cancelled nor cleared by the coordinator: a request for the same normalised peer name may continue to share the existing promise until it settles. Work admitted under an older generation may settle, but cannot publish completion into the current generation. AutoWatch is not another baseline transfer: after an accepted peer advertises that it is broadcasting, it subscribes to later changes.
84
+
85
+ ## Unattended target orchestration
86
+
87
+ Configured `P2P_SyncOnReplication` targets use `P2PTargetedTransfer.synchroniseConfiguredTargets()`. The generic unattended OneShot provider delegates to the same operation without peer-selection or peer-acceptance dialogues. The operation:
88
+
89
+ 1. acquires a finite room demand;
90
+ 2. waits for configured peer advertisements for a bounded period;
91
+ 3. evaluates each advertised peer against persisted and automatic admission policy without prompting;
92
+ 4. shares an accepted peer's baseline with AutoSync through `P2PAutomationCoordinator`; and
93
+ 5. returns an explicit completed, partial, blocked, cancelled, or failed provider outcome.
94
+
95
+ Missing, rejected, and undecided targets remain visible in the target result rather than being reported as a successful headless operation. Denial takes precedence over acceptance. A headless undecided peer is not prompted and is not synchronised.
96
+
97
+ Peer replication uses the ordinary common readiness gates, but declares central-remote preparation as not applicable. It therefore retains online, version, pending-file, clean-up, and host policy checks without trying to retrieve or initialise a CouchDB Security Seed. CouchDB and Object Storage providers declare central-remote preparation as required.
98
+
99
+ ## Setup connection-probe admission
100
+
101
+ The current P2P Setup check establishes only whether its signalling transport can be opened. It does not validate peer discovery, room credentials against another device, or a TURN or WebRTC data path. Self-hosted LiveSync injects the service's `P2PConnectionProbeAdmission` view into every maintained opening of that Setup dialogue.
102
+
103
+ The view delegates one complete decision to `P2PRoomSessionOwner`'s existing lifecycle queue. It receives the requested relay settings and a continuation which owns any short-lived raw Replicator and temporary database:
104
+
105
+ - a serving room whose active relay set covers every requested relay settles as `observed-active` without entering the continuation;
106
+ - a serving room which does not cover the requested relay set settles as `blocked` with `active-p2p-relay-binding-conflict`, again without entering the continuation; and
107
+ - an idle owner runs the continuation and does not settle admission until that caller-owned trial has released its resources.
108
+
109
+ Relay admission shares the transport's split-and-trim projection and compares de-duplicated sets. Ordering, duplicates, and surrounding whitespace therefore do not require another relay for this admission decision. It deliberately does not infer URI canonicalisation which Trystero's relay key does not implement.
110
+
111
+ The continuation runs while the lifecycle queue owns serialisation. It must not await another lifecycle transition on the same service. A rejected continuation leaves the queue usable for a later request.
112
+
113
+ This boundary adds no second room owner, process-global relay lease, or reference count. It does not change raw `TrysteroReplicator.dispose()` semantics. Observation and blocking never construct the raw trial, while idle admission keeps construction, probe execution, disposal, and temporary-database destruction inside one awaited continuation.
114
+
115
+ ## Active transfer cancellation
116
+
117
+ The provider's active-transfer stop capability asks the current `P2PRoomSession` to cancel every finite replication operation which it has admitted. The request returns after cancellation has been delivered; the operations then settle cooperatively. The session keeps the room, peer discovery, and RPC service available for later work. Stopping an active transfer is therefore distinct from disconnecting or replacing the P2P transport.
118
+
119
+ Each finite operation receives an effective abort signal derived from the session lifetime, its operation controller, and any caller or incoming RPC request signal. Cancellation is cooperative: an atomic database write which has already begun may settle, and its matching checkpoint may be recorded, before the operation reports cancellation. It is not a rollback promise.
120
+
121
+ Retiring the session first rejects new finite work, then aborts and awaits all admitted operations before releasing the room. Code admitted into the session must observe its effective signal and must not detach unowned work.
122
+
123
+ Configuration exchange and diagnostic reads are session-scoped requests, but are not finite replication operations. A request admitted before retirement may therefore settle against its originating session. Peer-admission decisions are persisted policy changes and intentionally settle independently of room replacement. None of these operations is included in active-transfer cancellation; peer callback and status fencing remains part of session retirement.
14
124
 
15
125
  ## Normal close
16
126
 
17
- `LiveSyncTrysteroReplicator.close()` serialises the close with any in-flight open operation. It stops LiveSync-owned work, closes the RPC room, calls `room.leave()`, clears instance references, pauses relay reconnection, and closes the current Nostr relay WebSockets.
127
+ The transport lifecycle view delegates an explicit disconnect to its module-level service operation. That operation sets the private disconnect veto, invalidates delayed automation, and asks `P2PRoomSessionOwner.close()` to clear every current demand and serialise retirement with any in-flight open operation. Retirement stops LiveSync-owned work, closes the RPC room, calls `room.leave()`, clears instance references, pauses relay reconnection, and closes the current Nostr relay WebSockets.
18
128
 
19
129
  Normal close deliberately does not call `close()` on the `RTCPeerConnection` values returned by `room.getPeers()`. Trystero 0.25 may share a physical peer across rooms. Leaving a room detaches its callbacks and action namespace, while Trystero may retain the idle physical peer for approximately 123 seconds for reuse. The retained peer cannot carry traffic for the departed room.
20
130
 
131
+ A host which constructs a raw `TrysteroReplicator` inside an admitted idle trial must call `dispose()` when it discards that object. `close()` releases the current transport while preserving host-lifetime subscriptions for a possible later open; `dispose()` performs that close and then releases those subscriptions permanently.
132
+
21
133
  Likewise, an incoming peer-leave notification removes LiveSync advertisement and client state without directly closing the underlying peer. Trystero decides whether that peer is shared, stale, or ready for destruction.
22
134
 
23
135
  The host command named 'Disconnect from the Signalling Server' means:
@@ -31,11 +143,21 @@ It is therefore a logical LiveSync disconnection and a physical signalling-serve
31
143
 
32
144
  ## Reconnection and replacement
33
145
 
34
- An explicit open resumes Trystero relay reconnection before joining the configured room. Settings application may change the relay URLs, room ID, passphrase, or TURN configuration, so the owning service normally closes and discards the former LiveSync instance before constructing the replacement. Concurrent replacement requests share the same in-flight replacement and receive the same new owner.
146
+ An explicit connect resumes Trystero relay reconnection before joining the configured room. Active-provider adapter acquisition is independent of that room lifecycle and cannot replace or close the service context.
147
+
148
+ The room-session owner compares an explicit effective-transport identity, device identity, and the local database object on every open. The transport identity contains the effective App ID, room ID, passphrase, normalised signalling relay and TURN configuration, effective connection path, RPC message-size bound, and diagnostic RTC mode. Persisted profile identifiers, P2P enablement, and automation or admission policy are not transport identity. An equivalent representation, such as surrounding whitespace in a relay list, therefore keeps the serving room. A changed binding serialises retirement of the complete session before opening and publishing its replacement.
149
+
150
+ Each room session captures one immutable settings snapshot for room join options, RPC framing, device identity, and the database object. A separate `currentSettings()` host port supplies policy evaluated on the open room. AutoSync and AutoWatch read it when a peer advertises, configured-target synchronisation reads it when the operation starts, and admission and headless policy are read when a peer is evaluated. A settings save does not replay an existing advertisement or infer whether an existing watch was manual or automatic. AutoBroadcast is reconciled explicitly on the current Replicator, without replacing the room. Configuration exchange continues to use the immutable session snapshot.
151
+
152
+ After a candidate room opens, the owner rechecks current enablement, demand, database identity, device identity, and effective transport identity before publication. This prevents changed credentials, transport, or database state from being applied partly to the former room or exposed through a stale candidate. No fixed close-to-open delay is required: lifecycle operations are serialised, and peer readiness is observed through discovery.
153
+
154
+ The stable service also participates directly in the local database lifecycle. Reset preparation and explicit database close await room retirement before the database managers are torn down and the physical PouchDB handle is destroyed or closed. The later database-initialisation hook remains a defensive retirement boundary before a replacement session is opened. The non-owning active adapter does not provide this fencing.
155
+
156
+ Saving settings reconciles the demand owned by AutoStart and applies AutoBroadcast to the current room, but cannot clear host lifecycle closure. Disabling AutoStart removes the `automatic` demand but leaves a room retained by explicit connect, a Rebuild continuation, or finite work. Disabling P2P clears every demand and retires the room through the lifecycle queue, including an in-flight open. An explicit disconnect establishes a service-lifetime veto which a later automatic-start event cannot clear; a later explicit connect clears it.
35
157
 
36
- A retained `LiveSyncTrysteroReplicator` also compares the effective outgoing message bound and connection path on every open. An unchanged signature keeps the serving transport, while a changed signature serialises closure of that transport before opening its replacement. This prevents a selected compatibility profile from appearing to take effect while the former room binding is still active. No fixed close-to-open delay is required: lifecycle operations are serialised, and peer readiness is observed through discovery.
158
+ A successful rebuild notification is a separately authorised continuation of the owning rebuild workflow, rather than an automatic-start request. It may therefore reopen the room independently of that automatic-start veto after the replacement database is ready.
37
159
 
38
- Saving settings with P2P or automatic start disabled always requests `close()`, even when the room has not started serving. This cancels an in-flight open through the same lifecycle queue, so a connection cannot appear after the disabling setting has been applied.
160
+ Peer arrival and departure callbacks are bound directly to the `P2PRoomSession` which created the host. The global peer events remain available for status, logs, and legacy observation, but maintained service composition does not route them back into whichever session happens to be current. Retiring a session detaches its peer callbacks before closing the room, so a late advertisement from the former host cannot start work in its replacement. Once the host is disposed, delayed callbacks can no longer publish peer or status events which might overwrite the replacement's UI state. Host-lifetime subscriptions are released when that session is disposed, rather than during the temporary transport shutdown used before a room join.
39
161
 
40
162
  Do not replace a relay socket's `onclose` handler. Trystero 0.25 shares relay clients by URL and uses its own handler to retire and recreate them. Use the exported pause and resume functions around explicit disconnection instead.
41
163
 
@@ -55,7 +177,9 @@ Commonlib consequently does not expose a forced physical-disconnection command.
55
177
 
56
178
  Maintain all five boundaries when this lifecycle changes:
57
179
 
58
- - Commonlib unit tests must prove that normal close leaves the room without directly closing Trystero-owned peers, that disabling settings cancel a pending open, that concurrent replacement requests leave one current owner, and that a changed compatibility signature replaces a serving transport while an unchanged signature remains idempotent;
180
+ - Commonlib unit tests must prove that normal close leaves the room without directly closing Trystero-owned peers, that database close handlers all settle before physical close, that disabling P2P cancels a pending open, that host lifecycle closure blocks settings reconciliation and finite room demand until a declared resume boundary, that removing one policy demand retains a room held by another demand, that concurrent replacement requests leave one current owner, that a changed effective binding replaces a serving transport while an equivalent representation, profile-only change, or policy-only change retains it, that AutoBroadcast is reconciled on that retained room, that a rebuild-owned continuation can reopen independently of an automatic-start veto, and that retired peer callbacks and host subscriptions cannot reach a replacement;
181
+ - connection-probe admission tests must prove compatible active observation, typed blocking before an additional relay is opened, complete idle-trial serialisation and clean-up, representation-insensitive relay coverage, and lifecycle-queue recovery after a rejected trial;
182
+ - target-aware automation tests must prove bounded delayed advertisement discovery, non-interactive acceptance outcomes, explicit partial and blocked results, baseline de-duplication across AutoSync and configured targets, cancellation of delayed AutoStart after suspension, and a finite demand beside an AutoStart-held room;
59
183
  - Commonlib rebuild tests must prove that first-device P2P initialisation does not reset a remote database and that an additional-device P2P Fetch performs one explicit peer-selection pass before resuming reflection;
60
184
  - the Self-hosted LiveSync Compose P2P lifecycle test must replace a current replicator, rediscover the same real peer, perform bidirectional RPC, and verify transferred content from a separate process;
61
185
  - the Self-hosted LiveSync real-Obsidian P2P Setup URI workflow must generate the second-device URI on the first device, accept both peer directions visibly, and verify a two-way note round-trip; and
@@ -28,6 +28,8 @@ Commonlib's full offline scanner distinguishes completed, deliberately skipped,
28
28
 
29
29
  Commonlib's local-database lifecycle tests distinguish the settings-selected database from the active wrapper. They verify replacement before a changed-suffix reset, fail-closed cleanup after rejected physical preparation, application readiness after the requested scan and completion hooks, and the prevention of rebuilt events and later remote mutation after a failed local reset. Self-hosted LiveSync owns settings persistence, setup flags, recovery dialogue, and real-Obsidian rebuild confirmation; those host results remain downstream evidence rather than Commonlib guarantees.
30
30
 
31
+ The focused `/replication` entry defines the provider capability, configuration identity, same-kind reconciliation, interaction authority, and truthful outcome contracts used when a host composes replication. A host selects its fixed provider subset and registers each definition during composition; an omitted role remains an explicit unsupported result rather than falling back to another provider. Commonlib serialises active replacement, rejects a candidate whose configuration changes before publication, and provides a transition-aware context acquisition boundary. The entry does not own user dialogue, P2P room policy, or selection of unknown providers.
32
+
31
33
  Most replication and storage services used by the plug-in still enter through explicit `compat/*` paths. Those imports prove that the published compatibility boundary supports the current migration; they are not examples of a finished high-level client API.
32
34
 
33
35
  The plug-in's manual onboarding and Remote Databases pane use the focused `/remote-configurations` entry to create opaque profile IDs, retain user-visible names, and select main and P2P remotes. Commonlib owns the in-memory profile result contract; Self-hosted LiveSync adds dialogue, Setup URI classification, Fetch or Rebuild scheduling, persistence, restart ordering, and real-Obsidian presentation checks.
@@ -56,10 +58,12 @@ The browser and Node storage implementations run through the same Commonlib resu
56
58
 
57
59
  The browser service composition in `src/apps/browser/createLiveSyncBrowserServiceHub.ts` is owned by Self-hosted LiveSync. It combines Commonlib's injectable service contracts and data implementations with LiveSync-owned browser API, UI, settings persistence, restart policy, and Vault identity. Commonlib therefore does not provide or select a general browser host.
58
60
 
59
- The Obsidian plug-in, CLI, WebApp, and WebPeer compose P2P ownership through `useP2PReplicatorFeature`. Commands and views retain its live result object and resolve the current replicator when an action occurs. Downstream Deno and Compose P2P E2E cover headless peer discovery and synchronisation, including the WebApp-to-WebPeer-to-CLI journey. Application-owned Chromium checks cover the browser UI flows, and the public Pages deployment has separate post-deployment smoke validation. The real-Obsidian Setup URI workflow additionally covers first-device local initialisation, one-pass additional-device Fetch, explicit peer approval, Vault reflection, and a two-way note round-trip.
61
+ The Obsidian plug-in, CLI, WebApp, and WebPeer compose P2P ownership through `useP2PReplicatorFeature`. The feature now returns focused views over one stable service owner, including connection-probe admission, and supplies non-owning adapters when P2P is selected as the main remote. Commands, Setup, and diagnostics can therefore avoid concrete-class checks and raw room access; existing panes retain the deprecated compatibility façade while they migrate. Downstream Deno and Compose P2P E2E cover headless peer discovery and synchronisation, including the WebApp-to-WebPeer-to-CLI journey. Application-owned Chromium checks cover the browser UI flows, and the public Pages deployment has separate post-deployment smoke validation. The real-Obsidian Setup URI workflow additionally covers first-device local initialisation, one-pass additional-device Fetch, explicit peer approval, Vault reflection, and a two-way note round-trip.
60
62
 
61
63
  Commonlib's focused P2P compatibility tests establish legacy defaulting, bounded outgoing message sizes, connection-string and QR-setting round trips, application of the configured bound across its Trystero server and client surfaces, relay-only ICE mapping when a valid TURN URL is present, and replacement of a serving transport when an effective compatibility value changes. They do not prove that a particular network path requires a smaller preset or that a configured TURN service can allocate a relay. Self-hosted LiveSync owns profile selection, user-facing controls, and real-Obsidian composition; device- and network-specific connectivity remains downstream or reporter-environment evidence.
62
64
 
65
+ Focused room-session and RPC tests additionally establish that a manual stop delivers cancellation to every concurrent finite replication while leaving the room reusable, that session retirement rejects new work and awaits admitted work before leaving, and that caller cancellation reaches a cancellation-aware incoming RPC handler. The replication-shim tests establish cancellation at safe batch boundaries and preserve the checkpoint for an atomic database write which has already settled. Self-hosted LiveSync composes the provider stop capability into its manual command; physical transport interruption and operating-system process termination remain host and runtime boundaries rather than rollback guarantees.
66
+
63
67
  This is evidence that the current package boundary can be consumed without the Obsidian host, but it is not yet a recommended standalone Commonlib client factory. The high-level lifecycle, readiness, error, and disposal contract remains future package work after Self-hosted LiveSync 1.0 rather than a gate for that release.
64
68
 
65
69
  ## LiveSync Bridge
@@ -26,6 +26,8 @@ const profile: RemoteConfiguration = upsertRemoteConfigurationInPlace(settings,
26
26
 
27
27
  The helper serialises the connection fields into the profile URI. When `activate` is true, it selects that profile through `activeConfigurationId` and projects the profile back onto the compatibility fields. The host still owns persistence, restart policy, credential acquisition, and user confirmation.
28
28
 
29
+ A Setup URI is a compatibility and persistence representation, not the active Replicator identity. Each provider must instead project the effective fields which bind its own connection resources. Either representation can contain connection credentials and must remain private; do not log it or use it as a display label.
30
+
29
31
  ## Identity and display names
30
32
 
31
33
  Profile IDs are opaque identity. Omit `id` to allocate a new one and preserve every existing profile. Pass a known `id` only when deliberately updating that profile.
@@ -44,6 +46,8 @@ There is no special profile named or identified as `default`. The selected main
44
46
 
45
47
  The outgoing message bound and connection path are ordinary P2P profile properties retained in its connection URI. Separate profiles may intentionally share a Group ID and credentials while selecting different transport compatibility values; only the profile selected by `P2P_ActiveRemoteConfigurationId` supplies the active flat P2P projection.
46
48
 
49
+ The selected profile ID is persisted identity, not an effective transport fingerprint. Selecting another P2P profile which projects the same effective room and transport values retains the current room; a change to those effective values replaces it. Profile presentation and selection changes therefore remain separate from transport lifecycle comparison.
50
+
47
51
  Selecting a profile updates the compatibility fields immediately. Existing replication services may therefore continue to consume those fields while profile-aware hosts treat the profile map and selection IDs as authoritative persisted state.
48
52
 
49
53
  ## Import and migration boundary
@@ -0,0 +1,250 @@
1
+ ---
2
+ date: 2026-08-30
3
+ commonlib-version: "0.1.20"
4
+ self-hosted-livesync-version: "1.0.21"
5
+ status: accepted
6
+ ---
7
+
8
+ # Service feature composition
9
+
10
+ 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
+
12
+ The choice is primarily about dependency direction and ownership. Mutable state alone does not require a class or a ServiceModule.
13
+
14
+ ## Choose the narrowest composition form
15
+
16
+ | Need | Preferred form | Reason |
17
+ | --------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
18
+ | Add one check, notification, or transformation to an existing contract | Register a handler on the owning Service | The Service already defines the ordering, result, and lifetime. |
19
+ | Connect several Services or ServiceModules to one feature | serviceFeature | Dependencies remain explicit, and host-specific composition stays outside the domain operation. |
20
+ | Retain state used only by one composed feature | Private context created by a serviceFeature | State remains bounded without becoming a shared service locator. |
21
+ | Share one long-lived operational capability between several consumers | ServiceModule | The host constructs one typed capability and makes its lifetime explicit. |
22
+ | Own an external resource with identity, replacement, abort, or disposal semantics | Focused class or resource owner, usually composed by a serviceFeature | The object lifetime is part of the contract rather than an implementation convenience. |
23
+ | Integrate with an existing legacy application Module | Keep the existing Module at that boundary | Preserve the established integration, but do not widen it into a new general pattern. |
24
+
25
+ ## Declare dependencies at the boundary
26
+
27
+ Use `NecessaryServices` to state the exact Services and ServiceModules which a feature consumes:
28
+
29
+ ```typescript
30
+ type IndexFeatureHost = NecessaryServices<"appLifecycle" | "API", "storageAccess">;
31
+
32
+ export const useIndexFeature = createServiceFeature((host: IndexFeatureHost) => {
33
+ const { appLifecycle, API } = host.services;
34
+ const { storageAccess } = host.serviceModules;
35
+
36
+ appLifecycle.onLoaded.addHandler(async () => {
37
+ const paths = await storageAccess.getFileNames();
38
+ API.addLog(`Indexed ${paths.length} paths`);
39
+ return true;
40
+ });
41
+ });
42
+ ```
43
+
44
+ This signature is both documentation and a compile-time boundary. Adding an unrelated Service to the host object does not make it available to the feature. If the operation needs another collaborator, add it deliberately to the declared contract.
45
+
46
+ A serviceFeature is a composition function, not a runtime registry entry. Call it from the host composition after its dependencies exist:
47
+
48
+ ```typescript
49
+ useIndexFeature({
50
+ services: {
51
+ context: serviceHub.context,
52
+ appLifecycle: serviceHub.appLifecycle,
53
+ API: serviceHub.API,
54
+ },
55
+ serviceModules: { storageAccess },
56
+ });
57
+ ```
58
+
59
+ Do not bind a serviceFeature to an application object merely to obtain `this`. Pass the narrow host value directly.
60
+
61
+ ## Separate an operation from its composition
62
+
63
+ When an operation has meaningful sequencing or failure behaviour, keep it independently callable and let the serviceFeature own only construction and registration.
64
+
65
+ `prepareDatabaseForUse.ts` follows this split. Its operation accepts explicit collaborators and returns the application result:
66
+
67
+ ```typescript
68
+ type PrepareDatabaseHost = NecessaryServices<
69
+ "appLifecycle" | "setting" | "vault" | "path" | "database" | "databaseEvents" | "fileProcessing" | "replicator",
70
+ never
71
+ >;
72
+
73
+ export async function prepareDatabaseForUse(
74
+ host: PrepareDatabaseHost,
75
+ log: LogFunction,
76
+ errorManager: UnresolvedErrorManager,
77
+ showingNotice = false,
78
+ reopenDatabase = true,
79
+ ignoreSuspending = false
80
+ ): Promise<boolean> {
81
+ // Open, scan, run completion handlers, commit pending events, and mark ready.
82
+ }
83
+ ```
84
+
85
+ The corresponding composition constructs the owned error manager and registers one adapter function:
86
+
87
+ ```typescript
88
+ export function usePrepareDatabaseForUse(host: PrepareDatabaseHost): void {
89
+ const log = createInstanceLogFunction("SF:prepareDatabaseForUse", host.services.API);
90
+ const errorManager = new UnresolvedErrorManager(host.services.appLifecycle, host.services.context.events);
91
+
92
+ host.services.databaseEvents.initialiseDatabase.addHandler(
93
+ (showingNotice = false, reopenDatabase = true, ignoreSuspending = false) =>
94
+ prepareDatabaseForUse(host, log, errorManager, showingNotice, reopenDatabase, ignoreSuspending)
95
+ );
96
+ }
97
+ ```
98
+
99
+ Test the operation's sequencing and failure short-circuiting separately from the handler registration. This keeps a lifecycle rename from forcing every domain test through an application-shaped fixture.
100
+
101
+ ## Keep feature-local state private
102
+
103
+ State which exists for one predicate, cache, queue, or scheduling policy can remain in a factory or private context. It does not need to become a property on the application core.
104
+
105
+ `targetFilter.ts` uses one factory per stateful predicate. The ignore-file factory owns its settings snapshot and cache:
106
+
107
+ ```typescript
108
+ type IgnoreFileHost = NecessaryServices<"setting" | "appLifecycle", "storageAccess">;
109
+
110
+ export function isAcceptedByIgnoreFilesFactory(host: IgnoreFileHost, log: LogFunction) {
111
+ let ignoreFiles: string[] = [];
112
+ const ignoreFileCacheMap = new Map<string, string[] | undefined | false>();
113
+
114
+ const refreshSettings = () => {
115
+ ignoreFiles = host.services.setting
116
+ .currentSettings()
117
+ .ignoreFiles.split(",")
118
+ .map((value) => value.trim());
119
+ return Promise.resolve(true);
120
+ };
121
+
122
+ host.services.setting.onSettingRealised.addHandler(refreshSettings);
123
+ return async function isAcceptedByIgnoreFiles(file: string | UXFileInfoStub): Promise<boolean> {
124
+ // Evaluate the current path with the private cache.
125
+ };
126
+ }
127
+ ```
128
+
129
+ `useTargetFilters` then creates the predicates and registers their public effects in the required order. The cache is not exposed merely so that composition code can reach it.
130
+
131
+ Use one private context when several functions share the same state:
132
+
133
+ ```typescript
134
+ type DeferredTaskHost = NecessaryServices<"appLifecycle", never>;
135
+
136
+ interface DeferredTaskContext {
137
+ pending: Set<Promise<unknown>>;
138
+ stopped: boolean;
139
+ }
140
+
141
+ interface DeferredTaskControl {
142
+ track(task: Promise<unknown>): void;
143
+ }
144
+
145
+ function trackTask(context: DeferredTaskContext, task: Promise<unknown>): void {
146
+ if (!context.stopped) context.pending.add(task);
147
+ }
148
+
149
+ function stopTasks(context: DeferredTaskContext): void {
150
+ context.stopped = true;
151
+ context.pending.clear();
152
+ }
153
+
154
+ export function useDeferredTasks(host: DeferredTaskHost): DeferredTaskControl {
155
+ const context: DeferredTaskContext = { pending: new Set(), stopped: false };
156
+ host.services.appLifecycle.onUnload.addHandler(() => {
157
+ stopTasks(context);
158
+ return Promise.resolve(true);
159
+ });
160
+ return { track: (task) => trackTask(context, task) };
161
+ }
162
+ ```
163
+
164
+ Return only the focused view which another consumer needs. Do not return the private context or add it to the complete Service Hub.
165
+
166
+ ## Use a ServiceModule for a shared operational capability
167
+
168
+ A ServiceModule is appropriate when several consumers must use the same long-lived capability or resource owner. Current examples include `storageAccess`, `databaseFileAccess`, `fileHandler`, and `rebuilder`.
169
+
170
+ Before adding one, verify all of the following:
171
+
172
+ - more than one consumer needs the same identity or lifetime;
173
+ - its public operations form a coherent capability;
174
+ - construction and disposal belong to the host composition;
175
+ - its dependencies can be declared without accepting the complete application core; and
176
+ - a private serviceFeature context would not provide the same behaviour more simply.
177
+
178
+ Do not promote a value to `ServiceModules` merely because a test needs to reach it. Inject the focused view into the current consumer instead.
179
+
180
+ ## Use a class only when identity or lifetime is part of the contract
181
+
182
+ A class is justified when behaviour depends on stable identity, replaceable implementations, serialised ownership, or an explicit resource lifecycle such as `abort()`, `close()`, or `dispose()`.
183
+
184
+ The P2P composition is an example at a larger scale: room-session and transport owners have identities, concurrent operations must be fenced, and shutdown order matters. Their classes own those invariants. The surrounding serviceFeature connects them to Services and gives each consumer only a focused view.
185
+
186
+ A class is usually unnecessary when it would only:
187
+
188
+ - store collaborators which can be function parameters;
189
+ - group private helper functions;
190
+ - provide a convenient `this` value;
191
+ - mirror the Service Hub; or
192
+ - expose state solely for tests.
193
+
194
+ ## Test interactions at the composition boundary
195
+
196
+ Use London School tests for a serviceFeature. Verify its observable collaboration rather than reconstructing the complete application:
197
+
198
+ ```typescript
199
+ it("registers the database preparation operation", async () => {
200
+ let handler: (() => Promise<boolean>) | undefined;
201
+ const addHandler = vi.fn((value) => {
202
+ handler = value;
203
+ });
204
+ const prepare = vi.fn(async () => true);
205
+
206
+ useExampleDatabaseFeature(makeHost({ addHandler, prepare }));
207
+
208
+ expect(addHandler).toHaveBeenCalledOnce();
209
+ await expect(handler?.()).resolves.toBe(true);
210
+ expect(prepare).toHaveBeenCalledOnce();
211
+ });
212
+ ```
213
+
214
+ For a state owner, test transitions and invariants directly. For an operation, verify collaborator order, the first rejected result, and which later effects do not run.
215
+
216
+ Stop and review the design when a focused test requires:
217
+
218
+ - a complete application-core fixture;
219
+ - a deep chain of mocks;
220
+ - import-order substitution or process-global mutation;
221
+ - unrelated Services merely to construct the subject; or
222
+ - access to private state instead of an observable result.
223
+
224
+ This friction is a design-review signal. It is not a reason to publish more internals or add a broader test helper.
225
+
226
+ ## Working with legacy Modules
227
+
228
+ Existing hosts may still use `AbstractModule` or an equivalent legacy layer. Retain that boundary when its identity, registration, or host integration is part of the current change. New Commonlib composition must not require a host to provide its complete application core.
229
+
230
+ When modifying an existing Module:
231
+
232
+ 1. identify the operation or state owner which can have a narrow contract;
233
+ 2. move that bounded part only when the current behaviour needs it;
234
+ 3. keep host-specific UI and registration at the application boundary;
235
+ 4. add interaction tests around the extracted boundary; and
236
+ 5. avoid an unrelated mechanical conversion of neighbouring Modules.
237
+
238
+ The maintained Self-hosted LiveSync guidance documents the additional risks of its `AbstractModule` and `AbstractObsidianModule` implementations.
239
+
240
+ ## Review checklist
241
+
242
+ Before accepting a new composition, confirm that:
243
+
244
+ - the owner of each mutable state and external resource is clear;
245
+ - dependencies appear in the function or constructor signature;
246
+ - the feature receives no broader host object than it uses;
247
+ - handler ordering and failure results belong to the owning Service contract;
248
+ - returned values are focused consumer views rather than service locators;
249
+ - disposal and replacement are explicit when resources outlive one call; and
250
+ - focused interaction tests can be written without a broad fixture.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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",
@@ -52,6 +52,16 @@
52
52
  "import": "./dist/platform/node/index.js",
53
53
  "default": "./dist/platform/node/index.js"
54
54
  },
55
+ "./p2p": {
56
+ "types": "./dist/p2p/index.d.ts",
57
+ "import": "./dist/p2p/index.js",
58
+ "default": "./dist/p2p/index.js"
59
+ },
60
+ "./replication": {
61
+ "types": "./dist/replication/index.d.ts",
62
+ "import": "./dist/replication/index.js",
63
+ "default": "./dist/replication/index.js"
64
+ },
55
65
  "./rpc": {
56
66
  "types": "./dist/rpc/index.d.ts",
57
67
  "import": "./dist/rpc/index.js",
@@ -626,6 +636,12 @@
626
636
  "node": [
627
637
  "dist/platform/node/index.d.ts"
628
638
  ],
639
+ "p2p": [
640
+ "dist/p2p/index.d.ts"
641
+ ],
642
+ "replication": [
643
+ "dist/replication/index.d.ts"
644
+ ],
629
645
  "rpc": [
630
646
  "dist/rpc/index.d.ts"
631
647
  ],
@@ -1,19 +0,0 @@
1
- import type { LOG_LEVEL } from "octagonal-wheels/common/logger";
2
- import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
3
- import type { ReactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
4
- import type { P2PSyncSetting, EntryDoc } from "../../common/types.js";
5
- import type { Confirm } from "../../interfaces/Confirm.js";
6
- import type { InjectableServiceHub } from "../../services/InjectableServices.js";
7
- export interface P2PReplicatorBase {
8
- storeP2PStatusLine: ReactiveSource<string>;
9
- settings: P2PSyncSetting;
10
- _log(msg: unknown, level?: LOG_LEVEL): void;
11
- _notice(msg: unknown, key?: string): void;
12
- getSettings(): P2PSyncSetting;
13
- getDB: () => PouchDB.Database<EntryDoc>;
14
- confirm: Confirm;
15
- simpleStore(): SimpleStore<unknown>;
16
- handleReplicatedDocuments(docs: EntryDoc[]): Promise<boolean>;
17
- init(): Promise<this>;
18
- services: InjectableServiceHub;
19
- }
@@ -1,8 +0,0 @@
1
- import type { NecessaryServices } from "../../interfaces/ServiceModule.js";
2
- import type { P2PPaneParams } from "./UseP2PReplicatorResult.js";
3
- export type P2PViewFactory = (leaf: unknown) => unknown;
4
- /**
5
- * @deprecated Compose `useP2PReplicatorFeature` with host-specific commands
6
- * and UI instead. This wrapper remains for source compatibility.
7
- */
8
- export declare function useP2PReplicator(host: NecessaryServices<"API" | "appLifecycle" | "setting" | "vault" | "database" | "databaseEvents" | "keyValueDB" | "replication" | "config" | "UI" | "replicator" | "remote", never>, viewTypeAndFactory?: [viewType: string, factory: P2PViewFactory]): P2PPaneParams;
@@ -1,56 +0,0 @@
1
- import { REMOTE_P2P } from "../../common/types.js";
2
- import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
3
- import { EVENT_REQUEST_OPEN_P2P } from "../../events/coreEvents.js";
4
- import { P2PLogCollector } from "./P2PLogCollector.js";
5
- import { useP2PReplicatorCommands } from "./useP2PReplicatorCommands.js";
6
- import { useP2PReplicatorFeature } from "./useP2PReplicatorFeature.js";
7
- function useP2PReplicator(host, viewTypeAndFactory) {
8
- const activeReplicator = useP2PReplicatorFeature(host);
9
- const events = host.services.context.events;
10
- const p2pLogCollector = new P2PLogCollector(events);
11
- const storeP2PStatusLine = reactiveSource("");
12
- p2pLogCollector.p2pReplicationLine.onChanged((line) => {
13
- storeP2PStatusLine.value = line.value;
14
- });
15
- if (viewTypeAndFactory) {
16
- const [viewType, factory] = viewTypeAndFactory;
17
- const openPane = () => host.services.API.showWindow(viewType);
18
- useP2PReplicatorCommands(host, activeReplicator);
19
- host.services.appLifecycle.onInitialise.addHandler(() => {
20
- host.services.API.registerWindow(viewType, factory);
21
- events.onEvent(EVENT_REQUEST_OPEN_P2P, () => void openPane());
22
- host.services.API.addCommand({
23
- id: "open-p2p-replicator",
24
- name: "P2P Sync : Open P2P Replicator",
25
- callback: () => void openPane()
26
- });
27
- host.services.API.addCommand({
28
- id: "replicate-now-by-p2p",
29
- name: "Replicate now by P2P",
30
- checkCallback: (isChecking) => {
31
- const replicator = activeReplicator.replicator;
32
- const settings = host.services.setting.currentSettings();
33
- if (isChecking) {
34
- if (settings.remoteType == REMOTE_P2P) return false;
35
- return replicator.server?.isServing ?? false;
36
- }
37
- void replicator.replicateFromCommand(false);
38
- }
39
- });
40
- host.services.API.addRibbonIcon("waypoints", "P2P Replicator", () => {
41
- void openPane();
42
- })?.classList.add("livesync-ribbon-replicate-p2p");
43
- return Promise.resolve(true);
44
- });
45
- }
46
- return {
47
- get replicator() {
48
- return activeReplicator.replicator;
49
- },
50
- p2pLogCollector,
51
- storeP2PStatusLine
52
- };
53
- }
54
- export {
55
- useP2PReplicator
56
- };
@@ -1,2 +0,0 @@
1
- import { TrysteroReplicatorP2PServer } from "./TrysteroReplicatorP2PServer.js";
2
- export { TrysteroReplicatorP2PServer as TrysteroConnection };