@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
@@ -0,0 +1,191 @@
1
+ import {
2
+ CAPABILITY_SUPPORT_KINDS,
3
+ NO_INTERACTION,
4
+ isActiveReplicatorContextBoundToSetting,
5
+ replicationBlocked,
6
+ replicationFailed
7
+ } from "../../replication/ReplicatorProvider.js";
8
+ import { asCopy } from "../../common/utils.object.js";
9
+ class TypedReplicationCoordinator {
10
+ constructor(dependencies) {
11
+ this.dependencies = dependencies;
12
+ }
13
+ dependencies;
14
+ /**
15
+ * Execute a user-initiated finite role with the request's interaction authority.
16
+ *
17
+ * The active context is acquired atomically, readiness runs before
18
+ * capability dispatch, and the settings snapshot is captured before the
19
+ * finite activity begins. A failed result invokes recovery only after that
20
+ * activity has settled.
21
+ */
22
+ async runUserInitiated(request) {
23
+ const showMessage = request.interaction.kind === "permitted" && request.interaction.permissions.failureRecovery;
24
+ const ready = await this.acquireReadyContext(showMessage);
25
+ if ("status" in ready) return ready;
26
+ const capability = ready.provider.userInitiatedOneShot;
27
+ if (capability.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) {
28
+ return this.finishFiniteAttempt(replicationBlocked(capability.reason));
29
+ }
30
+ const settings = Object.freeze(asCopy(this.dependencies.currentSettings()));
31
+ return this.finishFiniteAttempt(
32
+ await this.runAdmittedFiniteActivity(
33
+ ready,
34
+ (context) => capability.run(context.replicator, settings, request),
35
+ settings,
36
+ request.interaction,
37
+ showMessage
38
+ )
39
+ );
40
+ }
41
+ /**
42
+ * Execute an unattended finite role without granting interaction authority.
43
+ *
44
+ * Requests carrying interaction authority are rejected before context
45
+ * acquisition. Accepted requests otherwise retain the same readiness,
46
+ * settings-snapshot, activity, and recovery ordering as user work.
47
+ */
48
+ async runUnattended(request) {
49
+ if (request.interaction.kind !== NO_INTERACTION.kind) {
50
+ return replicationBlocked("interaction-required");
51
+ }
52
+ const ready = await this.acquireReadyContext(false);
53
+ if ("status" in ready) return ready;
54
+ const capability = ready.provider.unattendedOneShot;
55
+ if (capability.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) {
56
+ return this.finishFiniteAttempt(replicationBlocked(capability.reason));
57
+ }
58
+ const settings = Object.freeze(asCopy(this.dependencies.currentSettings()));
59
+ return this.finishFiniteAttempt(
60
+ await this.runAdmittedFiniteActivity(
61
+ ready,
62
+ (context) => capability.run(context.replicator, settings, request),
63
+ settings,
64
+ NO_INTERACTION,
65
+ false
66
+ )
67
+ );
68
+ }
69
+ /**
70
+ * Start the provider's continuous role outside finite activity accounting.
71
+ *
72
+ * Capability applicability is established before readiness, because an
73
+ * inapplicable long-lived role must not trigger unrelated preparation or
74
+ * diagnostics. After readiness, the short startup call is admitted against
75
+ * that exact publication. The provider owns the registered long-lived task
76
+ * after startup settles; it does not retain this admission for its lifetime.
77
+ * Startup failures become typed outcomes without invoking the finite-operation
78
+ * recovery handler.
79
+ */
80
+ async startContinuous(request) {
81
+ if (request.interaction.kind !== NO_INTERACTION.kind) {
82
+ return replicationBlocked("interaction-required");
83
+ }
84
+ const context = await this.acquireContext();
85
+ if ("status" in context) return context;
86
+ const capability = context.provider.continuous;
87
+ if (capability.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) {
88
+ return replicationBlocked(capability.reason);
89
+ }
90
+ const ready = await this.acquireReadyContext(false, context);
91
+ if ("status" in ready) return ready;
92
+ const setting = Object.freeze(asCopy(this.dependencies.currentSettings()));
93
+ const admitted = await this.dependencies.replicatorService.runWithActiveReplicatorContext(
94
+ async (activeContext) => {
95
+ if (activeContext !== ready || !isActiveReplicatorContextBoundToSetting(activeContext, setting)) {
96
+ return replicationBlocked("not-ready");
97
+ }
98
+ try {
99
+ return await capability.run(activeContext.replicator, setting, request);
100
+ } catch (error) {
101
+ return replicationFailed(error);
102
+ }
103
+ }
104
+ );
105
+ return admitted ?? replicationBlocked("no-active-replicator");
106
+ }
107
+ /**
108
+ * Request transfer cancellation on one atomically acquired active context.
109
+ *
110
+ * Cancellation deliberately bypasses readiness, activity accounting, and
111
+ * failure-recovery handlers: it is an operator control over existing work,
112
+ * not a request to begin new remote work.
113
+ */
114
+ async stopActiveTransfer() {
115
+ const context = await this.acquireContext();
116
+ if ("status" in context) return context;
117
+ const capability = context.provider.stopActiveTransfer;
118
+ if (capability.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) {
119
+ return replicationBlocked(capability.reason);
120
+ }
121
+ const admitted = await this.dependencies.replicatorService.runWithActiveReplicatorContext(
122
+ async (activeContext) => {
123
+ if (activeContext !== context) return replicationBlocked("not-ready");
124
+ try {
125
+ return await capability.run(activeContext.replicator);
126
+ } catch (error) {
127
+ return replicationFailed(error);
128
+ }
129
+ }
130
+ );
131
+ return admitted ?? replicationBlocked("no-active-replicator");
132
+ }
133
+ finishFiniteAttempt(outcome) {
134
+ this.dependencies.recordFiniteAttempt();
135
+ return outcome;
136
+ }
137
+ async acquireContext() {
138
+ const context = await this.dependencies.replicatorService.acquireActiveReplicatorContext();
139
+ if (context) return context;
140
+ return this.dependencies.replicatorService.hasActiveReplicator() ? replicationBlocked("provider-not-composed") : replicationBlocked("no-active-replicator");
141
+ }
142
+ async acquireReadyContext(showMessage, contextOverride) {
143
+ const context = contextOverride ?? await this.acquireContext();
144
+ if ("status" in context) return context;
145
+ if (!await this.dependencies.checkReadiness(showMessage, context.provider.readiness)) {
146
+ return replicationBlocked("not-ready");
147
+ }
148
+ return context;
149
+ }
150
+ async runFiniteActivity(run) {
151
+ try {
152
+ return await this.dependencies.replicatorService.runFiniteReplicationActivity(run, {
153
+ label: "replication"
154
+ });
155
+ } catch (error) {
156
+ return replicationFailed(error);
157
+ }
158
+ }
159
+ /**
160
+ * Reserve the readiness-tested publication for provider dispatch.
161
+ *
162
+ * Readiness and settings capture intentionally happen before admission. If
163
+ * an ownership transition completes in between, the newly admitted context
164
+ * is rejected instead of running work prepared for an older publication.
165
+ * Failure recovery runs only after the reservation has been released.
166
+ */
167
+ async runAdmittedFiniteActivity(expectedContext, run, setting, interaction, showMessage) {
168
+ const admitted = await this.dependencies.replicatorService.runWithActiveReplicatorContext((context) => {
169
+ if (context !== expectedContext || !isActiveReplicatorContextBoundToSetting(context, setting)) {
170
+ return replicationBlocked("not-ready");
171
+ }
172
+ return this.runFiniteActivity(() => run(context));
173
+ });
174
+ const result = admitted ?? replicationBlocked("no-active-replicator");
175
+ if (result.status === "failed") {
176
+ await this.dependencies.handleFailure(
177
+ Object.freeze({
178
+ context: expectedContext,
179
+ setting,
180
+ outcome: result,
181
+ showMessage,
182
+ interaction
183
+ })
184
+ );
185
+ }
186
+ return result;
187
+ }
188
+ }
189
+ export {
190
+ TypedReplicationCoordinator
191
+ };
@@ -0,0 +1,47 @@
1
+ import type { ReplicatorInstance } from "../../replication/ReplicatorInstance.js";
2
+ import type { ActiveReplicatorContext, ReplicatorProviderDefinition } from "../../replication/ReplicatorProvider.js";
3
+ /** Atomic service publication used as both the active view and generation identity. */
4
+ export interface ActiveReplicatorPublication {
5
+ readonly replicator: ReplicatorInstance;
6
+ readonly replicatorType: string;
7
+ readonly context: ActiveReplicatorContext | undefined;
8
+ }
9
+ /** One admitted use of an exact active publication. */
10
+ export interface ActiveReplicatorReservation {
11
+ readonly context: ActiveReplicatorContext;
12
+ /** Release this reservation. Repeated calls have no effect. */
13
+ release(): void;
14
+ }
15
+ /** Private service-side control over one fenced publication. */
16
+ export interface ActiveReplicatorRetirement {
17
+ readonly publication: ActiveReplicatorPublication;
18
+ /** Wait until every use admitted before the fence has settled. */
19
+ waitForDemandSettlement(): Promise<void>;
20
+ /** Mark physical close complete. Repeated calls have no effect. */
21
+ complete(): void;
22
+ }
23
+ /**
24
+ * Keep active publication, admission, and quiescing retirement as one state.
25
+ *
26
+ * The publication object itself is the generation identity. Retirement first
27
+ * removes it from `current`, then waits only for reservations captured from
28
+ * that object. A replacement cannot be published until physical retirement is
29
+ * explicitly completed.
30
+ */
31
+ export declare class ActiveReplicatorState {
32
+ private activeOwner;
33
+ private retiringOwner;
34
+ private retirement;
35
+ get current(): ActiveReplicatorPublication | undefined;
36
+ publish(provider: ReplicatorProviderDefinition | undefined, replicator: ReplicatorInstance, replicatorType: string, configurationIdentity: string | undefined): void;
37
+ /** Reserve the current typed publication, or reject admission while quiescing. */
38
+ reserve(): ActiveReplicatorReservation | undefined;
39
+ /**
40
+ * Fence the current publication and begin its explicit quiescing transition.
41
+ *
42
+ * Completion is separate from demand settlement so callers cannot publish a
43
+ * replacement between draining admitted work and physically closing the old
44
+ * Replicator.
45
+ */
46
+ beginRetirement(): ActiveReplicatorRetirement | undefined;
47
+ }
@@ -0,0 +1,107 @@
1
+ class ActiveReplicatorPublicationOwner {
2
+ constructor(publication) {
3
+ this.publication = publication;
4
+ }
5
+ publication;
6
+ accepting = true;
7
+ demandCount = 0;
8
+ demandSettlement;
9
+ settleDemand;
10
+ reserve() {
11
+ const context = this.publication.context;
12
+ if (!this.accepting || !context) {
13
+ return void 0;
14
+ }
15
+ this.demandCount++;
16
+ let released = false;
17
+ return {
18
+ context,
19
+ release: () => {
20
+ if (released) return;
21
+ released = true;
22
+ this.demandCount--;
23
+ if (this.demandCount === 0) {
24
+ this.settleDemand?.();
25
+ this.settleDemand = void 0;
26
+ }
27
+ }
28
+ };
29
+ }
30
+ stopAccepting() {
31
+ this.accepting = false;
32
+ }
33
+ waitForDemandSettlement() {
34
+ if (this.demandCount === 0) {
35
+ return Promise.resolve();
36
+ }
37
+ this.demandSettlement ??= new Promise((resolve) => {
38
+ this.settleDemand = resolve;
39
+ });
40
+ return this.demandSettlement;
41
+ }
42
+ get hasDemand() {
43
+ return this.demandCount !== 0;
44
+ }
45
+ }
46
+ class ActiveReplicatorState {
47
+ activeOwner;
48
+ retiringOwner;
49
+ retirement;
50
+ get current() {
51
+ return this.activeOwner?.publication;
52
+ }
53
+ publish(provider, replicator, replicatorType, configurationIdentity) {
54
+ if (this.activeOwner) {
55
+ throw new Error("Cannot replace an active Replicator without beginning retirement.");
56
+ }
57
+ if (this.retiringOwner) {
58
+ throw new Error("Cannot publish an active Replicator before retirement has completed.");
59
+ }
60
+ this.activeOwner = new ActiveReplicatorPublicationOwner({
61
+ replicator,
62
+ replicatorType,
63
+ context: provider && configurationIdentity !== void 0 ? { provider, replicator, configurationIdentity } : void 0
64
+ });
65
+ }
66
+ /** Reserve the current typed publication, or reject admission while quiescing. */
67
+ reserve() {
68
+ return this.activeOwner?.reserve();
69
+ }
70
+ /**
71
+ * Fence the current publication and begin its explicit quiescing transition.
72
+ *
73
+ * Completion is separate from demand settlement so callers cannot publish a
74
+ * replacement between draining admitted work and physically closing the old
75
+ * Replicator.
76
+ */
77
+ beginRetirement() {
78
+ if (this.retirement) return this.retirement;
79
+ const owner = this.activeOwner;
80
+ if (!owner) return void 0;
81
+ owner.stopAccepting();
82
+ this.activeOwner = void 0;
83
+ this.retiringOwner = owner;
84
+ let completed = false;
85
+ const retirement = {
86
+ publication: owner.publication,
87
+ waitForDemandSettlement: () => owner.waitForDemandSettlement(),
88
+ complete: () => {
89
+ if (completed) return;
90
+ if (owner.hasDemand) {
91
+ throw new Error("Cannot complete active Replicator retirement while admitted work remains.");
92
+ }
93
+ if (this.retiringOwner !== owner) {
94
+ throw new Error("Active Replicator retirement no longer owns the quiescing state.");
95
+ }
96
+ completed = true;
97
+ this.retiringOwner = void 0;
98
+ this.retirement = void 0;
99
+ }
100
+ };
101
+ this.retirement = retirement;
102
+ return retirement;
103
+ }
104
+ }
105
+ export {
106
+ ActiveReplicatorState
107
+ };
@@ -0,0 +1,5 @@
1
+ import type { RemoteDBSettings } from "../../common/types.js";
2
+ import { type CentralRemoteAdministrationRequest, type CentralRemoteAdministrationResult } from "../../replication/CentralRemoteAdministration.js";
3
+ import { type ActiveReplicatorContext } from "../../replication/ReplicatorProvider.js";
4
+ /** Stop, then dispatch central-remote administration through the atomically captured provider. */
5
+ export declare function runCentralRemoteAdministrationWithContext(context: ActiveReplicatorContext | undefined, setting: RemoteDBSettings, request: CentralRemoteAdministrationRequest): Promise<CentralRemoteAdministrationResult>;
@@ -0,0 +1,44 @@
1
+ import {
2
+ CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS,
3
+ centralRemoteAdministrationVerificationFailed
4
+ } from "../../replication/CentralRemoteAdministration.js";
5
+ import { CAPABILITY_SUPPORT_KINDS } from "../../replication/ProviderCapability.js";
6
+ import {
7
+ isActiveReplicatorContextBoundToSetting
8
+ } from "../../replication/ReplicatorProvider.js";
9
+ async function stopActiveTransfer(context) {
10
+ const capability = context.provider.stopActiveTransfer;
11
+ if (capability.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) {
12
+ throw new Error("The active Replicator does not support the stop required for central remote administration.");
13
+ }
14
+ const outcome = await capability.run(context.replicator);
15
+ if (outcome.status === "completed") return;
16
+ if (outcome.status === "failed") throw outcome.error;
17
+ throw new Error("The active Replicator transfer did not stop before central remote administration.");
18
+ }
19
+ async function runCentralRemoteAdministrationWithContext(context, setting, request) {
20
+ if (!context) {
21
+ return centralRemoteAdministrationVerificationFailed(
22
+ CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS.NO_ACTIVE_REPLICATOR
23
+ );
24
+ }
25
+ if (!isActiveReplicatorContextBoundToSetting(context, setting)) {
26
+ return centralRemoteAdministrationVerificationFailed(
27
+ CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS.ACTIVE_CONFIGURATION_MISMATCH
28
+ );
29
+ }
30
+ const capability = context.provider.centralRemoteAdministration;
31
+ if (!capability) {
32
+ return centralRemoteAdministrationVerificationFailed(
33
+ CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS.CAPABILITY_NOT_APPLICABLE
34
+ );
35
+ }
36
+ if (capability.kind !== CAPABILITY_SUPPORT_KINDS.SUPPORTED) {
37
+ return centralRemoteAdministrationVerificationFailed(capability.reason);
38
+ }
39
+ await stopActiveTransfer(context);
40
+ return await capability.run(context.replicator, setting, request);
41
+ }
42
+ export {
43
+ runCentralRemoteAdministrationWithContext
44
+ };
@@ -1,11 +1,16 @@
1
- import type { LiveSyncAbstractReplicator } from "../../replication/LiveSyncAbstractReplicator.js";
2
- import type { IReplicatorService } from "./IService.js";
1
+ import type { ReplicatorInstance } from "../../replication/ReplicatorInstance.js";
2
+ import { type MultipleHandlerFunction } from "../lib/HandlerUtils.js";
3
+ import type { IReplicatorService, ReplicatorFactoryCallback } from "./IService.js";
3
4
  import { ServiceBase, type ServiceContext } from "./ServiceBase.js";
4
5
  import type { SettingService } from "./SettingService.js";
5
6
  import type { AppLifecycleService } from "./AppLifecycleService.js";
6
7
  import { UnresolvedErrorManager } from "./UnresolvedErrorManager.js";
7
8
  import type { DatabaseEventService } from "./DatabaseEventService.js";
8
9
  import { type AsyncActivityOptions, type AsyncActivityRunner } from "../../interfaces/AsyncActivityRunner.js";
10
+ import type { ActiveReplicatorContext, ReplicatorProviderDefinitionMap } from "../../replication/ReplicatorProvider.js";
11
+ import type { RemoteDBSettings } from "../../common/types.js";
12
+ import type { RemoteResourceKind, RemoteResourceMap } from "../../replication/RemoteResource.js";
13
+ import type { CentralRemoteAdministrationRequest, CentralRemoteAdministrationResult } from "../../replication/CentralRemoteAdministration.js";
9
14
  export interface ReplicatorServiceDependencies {
10
15
  settingService: SettingService;
11
16
  appLifecycleService: AppLifecycleService;
@@ -14,7 +19,13 @@ export interface ReplicatorServiceDependencies {
14
19
  registerLifecycleHandlers?: boolean;
15
20
  }
16
21
  /**
17
- * The ReplicatorService provides methods for managing replication.
22
+ * Own the active Replicator publication and its serialised lifecycle transitions.
23
+ *
24
+ * This service composes provider definitions, retires active instances when
25
+ * their configuration identity changes, and publishes the provider and
26
+ * Replicator atomically. Remote administration is dispatched through the
27
+ * admitted active context; `ReplicationService` owns synchronisation
28
+ * readiness and replication dispatch.
18
29
  */
19
30
  export declare abstract class ReplicatorService<T extends ServiceContext = ServiceContext> extends ServiceBase<T> implements IReplicatorService {
20
31
  protected dependencies: ReplicatorServiceDependencies;
@@ -23,8 +34,9 @@ export declare abstract class ReplicatorService<T extends ServiceContext = Servi
23
34
  _log: (msg: unknown, level?: import("octagonal-wheels/common/logger").LOG_LEVEL, key?: string) => void;
24
35
  private settingService;
25
36
  private databaseEventService;
26
- private _activeReplicator;
27
- private _replicatorType;
37
+ private readonly _activeReplicatorState;
38
+ private _providerDefinitions;
39
+ private _transition;
28
40
  private appLifecycleService;
29
41
  _unresolvedErrorManager: UnresolvedErrorManager;
30
42
  constructor(context: T, dependencies: ReplicatorServiceDependencies);
@@ -36,26 +48,75 @@ export declare abstract class ReplicatorService<T extends ServiceContext = Servi
36
48
  /** Runs one finite replication and exposes its document-delivery lifetime. */
37
49
  runFiniteReplicationActivity<TValue>(task: () => TValue | PromiseLike<TValue>, options?: AsyncActivityOptions): Promise<TValue>;
38
50
  private runRemoteActivity;
51
+ /** Request a reversible transfer stop without retiring the active publication. */
39
52
  private suspendReplication;
40
53
  private reinitialiseReplicator;
41
- private disposeReplicator;
54
+ /** Request transfer cancellation for either a reversible pause or terminal retirement. */
55
+ private requestPublicationTransferStop;
56
+ /** Fence new work, request cancellation, and drain only this publication's admitted tasks. */
57
+ private quiesceActiveReplicator;
58
+ /** Close one drained publication and release its private retirement state. */
59
+ private closeRetiringReplicator;
60
+ private disposeReplicatorNow;
42
61
  private discardFailedReplicator;
62
+ private enqueueTransition;
43
63
  private _initialiseReplicator;
64
+ private getProviderDefinition;
65
+ private createReplicator;
66
+ private createReplicatorForSetting;
67
+ private isCurrentProviderConfiguration;
68
+ private publishActiveReplicator;
69
+ private initialiseReplicatorNow;
44
70
  /**
45
- * Close the active replication if any.
46
- * Not used currently.
71
+ * Fence new work, drain admitted work, and close the active Replicator.
72
+ *
73
+ * The command is serialised with every other ownership transition. Callers
74
+ * must not await it from inside `runWithActiveReplicatorContext()`, because
75
+ * retirement waits for that admitted callback to settle.
47
76
  */
48
- readonly onCloseActiveReplication: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<() => Promise<boolean>>;
77
+ onCloseActiveReplication(): Promise<boolean>;
49
78
  /**
50
79
  * Get a new replicator instance based on the provided settings.
51
80
  */
52
- readonly getNewReplicator: import("../lib/HandlerUtils.js").MultipleHandlerFunction<(settingOverride?: Partial<import("../../common/types.js").ObsidianLiveSyncSettings>) => Promise<LiveSyncAbstractReplicator | undefined | false>, unknown>;
53
- readonly onReplicatorInitialised: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<() => Promise<boolean>>;
81
+ private readonly legacyGetNewReplicator;
82
+ readonly getNewReplicator: MultipleHandlerFunction<ReplicatorFactoryCallback>;
83
+ registerReplicatorProviderDefinitions(definitions: ReplicatorProviderDefinitionMap): void;
54
84
  /**
55
- * Get the currently active replicator instance.
56
- * If no active replicator, return undefined but that is the fatal situation (on Obsidian).
85
+ * Return a non-owning synchronous view for lifecycle diagnostics and focused tests.
86
+ *
87
+ * Production work must use an admitted acquisition or callback boundary so that
88
+ * the context cannot be retired while it is in use.
57
89
  */
58
- getActiveReplicator(): LiveSyncAbstractReplicator | undefined;
90
+ protected inspectActiveReplicatorContext(): ActiveReplicatorContext | undefined;
91
+ createRemoteResource<TKind extends RemoteResourceKind>(kind: TKind, setting: RemoteDBSettings): Promise<RemoteResourceMap[TKind] | undefined>;
92
+ /** Apply and verify one provider-declared action before permitting a later switch. */
93
+ runCentralRemoteAdministration(request: CentralRemoteAdministrationRequest): Promise<CentralRemoteAdministrationResult>;
94
+ acquireActiveReplicatorContext(): Promise<ActiveReplicatorContext | undefined>;
95
+ /**
96
+ * Admit one callback against the publication ordered before later transitions.
97
+ *
98
+ * Release never enters the transition queue. The callback must not initiate
99
+ * or await a settings, database, or Replicator lifecycle transition which
100
+ * would wait for this same reservation.
101
+ */
102
+ runWithActiveReplicatorContext<TResult>(task: (context: ActiveReplicatorContext) => TResult | PromiseLike<TResult>): Promise<TResult | undefined>;
103
+ readonly onBeforeReplicatorPublication: import("../lib/HandlerUtils.js").BooleanMultipleHandlerFunction<() => Promise<boolean>>;
104
+ /**
105
+ * Return the legacy unreserved view of the active Replicator.
106
+ *
107
+ * Missing active state retains its established Notice-level diagnostic.
108
+ * New work must acquire or admit an `ActiveReplicatorContext` instead.
109
+ */
110
+ getActiveReplicator(): ReplicatorInstance | undefined;
111
+ /**
112
+ * Classify whether a compatibility active Replicator exists without
113
+ * acquiring or returning it.
114
+ *
115
+ * This side-effect-free, non-owning predicate exists only for
116
+ * compatibility-state classification. Work must acquire or admit an
117
+ * `ActiveReplicatorContext` before using a Replicator.
118
+ */
119
+ hasActiveReplicator(): boolean;
59
120
  replicationStatics: import("octagonal-wheels/dataobject/reactive_v2").ReactiveSource<{
60
121
  sent: number;
61
122
  arrived: number;