@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,8 +1,11 @@
1
+ /** A stored document carrying the change-feed metadata used by the shim. */
1
2
  export type SomeDocument<T extends object> = PouchDB.Core.ExistingDocument<T> & PouchDB.Core.ChangesMeta;
2
3
  /**
3
- * Minimal subset of the PouchDB public API required by {@link replicateShim}.
4
- * Both a real `PouchDB.Database` and an {@link RpcPouchDBProxy} satisfy this
5
- * interface, allowing replication across an RPC transport.
4
+ * Structural database boundary required by {@link replicateShim}.
5
+ *
6
+ * Listing only the operations used by the algorithm lets a local
7
+ * `PouchDB.Database` and an RPC-backed proxy participate without presenting
8
+ * the proxy as a complete PouchDB implementation.
6
9
  */
7
10
  export type PouchDBShim<T extends object> = {
8
11
  info: () => Promise<PouchDB.Core.DatabaseInfo>;
@@ -13,26 +16,62 @@ export type PouchDBShim<T extends object> = {
13
16
  put: (doc: PouchDB.Core.PutDocument<T>, options?: PouchDB.Core.PutOptions) => Promise<PouchDB.Core.Response>;
14
17
  get: (id: string, options?: PouchDB.Core.GetOptions) => Promise<T & PouchDB.Core.IdMeta & PouchDB.Core.GetMeta>;
15
18
  };
19
+ /** A local PouchDB database or a structural shim with the same required operations. */
16
20
  type CompatibleDatabase<T extends object> = PouchDB.Database<SomeDocument<T>> | PouchDBShim<SomeDocument<T>>;
17
21
  /** Upserts a document by `id`, calling `func` to produce the updated version. */
18
22
  export declare function upsert<V extends object, TDB extends CompatibleDatabase<object> = CompatibleDatabase<any>, T extends SomeDocument<V> = SomeDocument<V>>(db: TDB, id: string, func: (doc: T) => T): Promise<T>;
23
+ /** Batch and cancellation policy shared by finite and continuous shim replication. */
19
24
  export type ShimReplicationOptionBase = {
20
25
  rewind?: boolean;
21
26
  batch_size?: number;
27
+ /** Abort before a new batch starts, or after the current batch settles. */
28
+ signal?: AbortSignal;
22
29
  };
30
+ /** Options for a finite replication which settles after catching up or cancellation. */
23
31
  export type ShimReplicationOneShot = {
24
32
  live?: false;
33
+ /** Legacy cancellation option retained for existing callers. */
25
34
  controller?: AbortController;
26
35
  } & ShimReplicationOptionBase;
36
+ /** Options for replication which continues watching until its controller cancels it. */
27
37
  export type ShimReplicationOptionContinuous = {
28
38
  live: true;
29
39
  controller: AbortController;
30
40
  } & ShimReplicationOptionBase;
41
+ /** Selects finite or continuous shim replication. */
31
42
  export type ShimReplicationOption = ShimReplicationOneShot | ShimReplicationOptionContinuous;
43
+ /** Explicit terminal state returned by {@link replicateShim}. */
44
+ export type ShimReplicationOutcome = ShimReplicationCompleted | ShimReplicationCancelled;
45
+ export type ShimReplicationCompleted = {
46
+ readonly status: "completed";
47
+ };
48
+ export type ShimReplicationCancelled = {
49
+ readonly status: "cancelled";
50
+ };
51
+ /** Details of a document whose requested bulk write did not succeed. */
52
+ export type ShimReplicationWriteFailure = {
53
+ readonly index: number;
54
+ readonly id: string;
55
+ readonly revision: string | undefined;
56
+ readonly result: PouchDB.Core.Response | PouchDB.Core.Error | undefined;
57
+ };
58
+ /**
59
+ * Raised when a batch cannot be committed safely.
60
+ *
61
+ * PouchDB resolves `bulkDocs` even when an individual document fails. The
62
+ * replication shim exposes that failure as an error so that callers cannot
63
+ * mistake a partially written batch for a committed checkpoint.
64
+ */
65
+ export declare class ShimReplicationError extends Error {
66
+ readonly failures: readonly ShimReplicationWriteFailure[];
67
+ constructor(message: string, failures: readonly ShimReplicationWriteFailure[]);
68
+ }
69
+ /** Sequence boundary committed by one successfully written batch. */
32
70
  export type ProgressInfo = {
33
71
  lastSeq: number;
34
72
  maxSeqInBatch: number;
35
73
  };
74
+ /** Receives the documents and committed sequence boundary for each completed batch. */
36
75
  export type ShimReplicationProgressReportFunc<T extends object> = (progress: SomeDocument<T>[], progressInfo: ProgressInfo) => Promise<void>;
37
76
  /**
38
77
  * Replicate documents from `sourceDB` into `targetDB` using a CouchDB-style
@@ -47,5 +86,5 @@ export type ShimReplicationProgressReportFunc<T extends object> = (progress: Som
47
86
  * @param progress Called after each batch with the written documents.
48
87
  * @param option Replication options (live mode, batch size, abort signal).
49
88
  */
50
- export declare function replicateShim<T extends CompatibleDatabase<V>, U extends CompatibleDatabase<V>, V extends object>(targetDB: T, sourceDB: U, progress: ShimReplicationProgressReportFunc<V>, option?: ShimReplicationOption): Promise<void>;
89
+ export declare function replicateShim<T extends CompatibleDatabase<V>, U extends CompatibleDatabase<V>, V extends object>(targetDB: T, sourceDB: U, progress: ShimReplicationProgressReportFunc<V>, option?: ShimReplicationOption): Promise<ShimReplicationOutcome>;
51
90
  export {};
@@ -23,6 +23,14 @@ async function upsert(db, id, func) {
23
23
  throw ex;
24
24
  }
25
25
  }
26
+ class ShimReplicationError extends Error {
27
+ constructor(message, failures) {
28
+ super(message);
29
+ this.failures = failures;
30
+ this.name = "ShimReplicationError";
31
+ }
32
+ failures;
33
+ }
26
34
  function parseSeq(seq) {
27
35
  return parseInt(String(seq).split("-")[0], 10);
28
36
  }
@@ -40,11 +48,24 @@ function sortBySeq(docs, changesResults) {
40
48
  }
41
49
  return docs.slice().sort((a, b) => (maxSeqById.get(a._id) ?? 0) - (maxSeqById.get(b._id) ?? 0));
42
50
  }
51
+ function isSuccessfulBulkDocResult(result) {
52
+ return typeof result === "object" && result !== null && "ok" in result && result.ok === true;
53
+ }
54
+ function isCancellationError(error) {
55
+ if (typeof error !== "object" || error === null) return false;
56
+ return "code" in error && error.code === "CANCELLED" || "name" in error && error.name === "AbortError";
57
+ }
43
58
  async function replicateShim(targetDB, sourceDB, progress, option = {}) {
59
+ const signal = option.signal ?? option.controller?.signal;
60
+ if (signal?.aborted) {
61
+ Logger(`Replication cancelled`, LOG_LEVEL_VERBOSE);
62
+ return { status: "cancelled" };
63
+ }
44
64
  try {
45
65
  const [targetDBInfo, sourceDBInfo] = await Promise.all([targetDB.info(), sourceDB.info()]);
46
66
  const maxNumSeq = parseSeq(sourceDBInfo.update_seq);
47
- await serialized(`replication-${targetDBInfo.db_name}-${sourceDBInfo.db_name}`, async () => {
67
+ const outcome = await serialized(`replication-${targetDBInfo.db_name}-${sourceDBInfo.db_name}`, async () => {
68
+ if (signal?.aborted) return { status: "cancelled" };
48
69
  Logger(
49
70
  `Replication ${sourceDBInfo.db_name} (${sourceDBInfo.update_seq}) \u2192 ${targetDBInfo.db_name} (${targetDBInfo.update_seq})`,
50
71
  LOG_LEVEL_VERBOSE
@@ -66,17 +87,62 @@ async function replicateShim(targetDB, sourceDB, progress, option = {}) {
66
87
  Logger(`Starting from seq ${since}`, LOG_LEVEL_VERBOSE);
67
88
  const batchSize = option.batch_size ?? 33;
68
89
  while (true) {
90
+ if (signal?.aborted) return { status: "cancelled" };
69
91
  const changes = await sourceDB.changes({ since, style: "all_docs", limit: batchSize });
70
- if (changes.results.length === 0) break;
71
- if (option.controller?.signal?.aborted) break;
92
+ if (changes.results.length === 0) {
93
+ return signal?.aborted ? { status: "cancelled" } : { status: "completed" };
94
+ }
95
+ if (signal?.aborted) return { status: "cancelled" };
72
96
  const changesResults = changes.results;
73
97
  const revsDiffParam = buildRevsDiffParam(changesResults);
74
98
  const diff = await targetDB.revsDiff(revsDiffParam);
99
+ if (signal?.aborted) return { status: "cancelled" };
75
100
  const missingRequests = Object.entries(diff).filter(([, entry]) => entry.missing !== void 0).flatMap(([id, entry]) => entry.missing.map((rev) => ({ id, rev })));
76
101
  if (missingRequests.length > 0) {
77
102
  const bulkGetResult = await sourceDB.bulkGet({ docs: missingRequests, revs: true });
78
103
  const fetchedDocs = bulkGetResult.results.flatMap((r) => r.docs).filter((d) => "ok" in d).map((d) => d.ok);
79
- await targetDB.bulkDocs(fetchedDocs, { new_edits: false });
104
+ if (signal?.aborted) return { status: "cancelled" };
105
+ const fetchedRevisionKeys = new Set(
106
+ fetchedDocs.map((doc) => `${doc._id}\0${String(doc._rev)}`)
107
+ );
108
+ const unavailableRequests = missingRequests.filter(
109
+ ({ id, rev }) => !fetchedRevisionKeys.has(`${id}\0${rev}`)
110
+ );
111
+ if (unavailableRequests.length > 0) {
112
+ const unavailableFailures = unavailableRequests.map(
113
+ (request, index) => ({
114
+ index,
115
+ id: request.id,
116
+ revision: request.rev,
117
+ result: void 0
118
+ })
119
+ );
120
+ throw new ShimReplicationError(
121
+ `Could not fetch ${unavailableRequests.length} required revision(s)`,
122
+ unavailableFailures
123
+ );
124
+ }
125
+ const writeResults = await targetDB.bulkDocs(fetchedDocs, { new_edits: false });
126
+ const failedWrites = writeResults.flatMap((result, index) => {
127
+ if (isSuccessfulBulkDocResult(result)) return [];
128
+ const resultId = typeof result === "object" && result !== null && "id" in result ? result.id : void 0;
129
+ const owningIndex = typeof resultId === "string" ? fetchedDocs.findIndex((doc) => doc._id === resultId) : -1;
130
+ const owningDocument = fetchedDocs[owningIndex >= 0 ? owningIndex : index];
131
+ return [
132
+ {
133
+ index: owningIndex >= 0 ? owningIndex : index,
134
+ id: typeof resultId === "string" ? resultId : owningDocument?._id ?? "",
135
+ revision: owningDocument?._rev,
136
+ result
137
+ }
138
+ ];
139
+ });
140
+ if (failedWrites.length > 0) {
141
+ throw new ShimReplicationError(
142
+ `Target rejected ${failedWrites.length} document write(s)`,
143
+ failedWrites
144
+ );
145
+ }
80
146
  const uniqueIds = [...new Set(changesResults.map((c) => c.id))];
81
147
  const refreshResult = await targetDB.bulkGet({ docs: uniqueIds.map((id) => ({ id })) });
82
148
  const refreshedDocs = refreshResult.results.flatMap((r) => r.docs).filter((d) => "ok" in d).map((d) => d.ok);
@@ -94,16 +160,23 @@ async function replicateShim(targetDB, sourceDB, progress, option = {}) {
94
160
  ...doc,
95
161
  since
96
162
  }));
163
+ if (signal?.aborted) return { status: "cancelled" };
97
164
  }
98
165
  });
166
+ Logger(outcome.status === "cancelled" ? `Replication cancelled` : `Replication completed`, LOG_LEVEL_VERBOSE);
167
+ return outcome;
99
168
  } catch (ex) {
169
+ if (signal?.aborted && isCancellationError(ex)) {
170
+ Logger(`Replication cancelled`, LOG_LEVEL_VERBOSE);
171
+ return { status: "cancelled" };
172
+ }
100
173
  Logger(`Replication failed`, LOG_LEVEL_VERBOSE);
101
174
  Logger(ex, LOG_LEVEL_VERBOSE);
102
175
  throw ex;
103
176
  }
104
- Logger(`Replication completed`, LOG_LEVEL_VERBOSE);
105
177
  }
106
178
  export {
179
+ ShimReplicationError,
107
180
  replicateShim,
108
181
  upsert
109
182
  };
@@ -0,0 +1,38 @@
1
+ import type { TweakValues } from "../common/types.js";
2
+ /** Stable machine states for one central-remote compatibility assessment. */
3
+ export declare const CENTRAL_COMPATIBILITY_STATUSES: Readonly<{
4
+ readonly NOT_ASSESSED: "not-assessed";
5
+ readonly ACCEPTED: "accepted";
6
+ readonly REJECTED: "rejected";
7
+ }>;
8
+ /** Stable machine reasons carried by a rejected compatibility assessment. */
9
+ export declare const CENTRAL_COMPATIBILITY_REJECTION_REASONS: Readonly<{
10
+ readonly INCOMPATIBLE_VERSION: "incompatible-version";
11
+ readonly NODE_LOCKED: "node-locked";
12
+ readonly NODE_CLEANED: "node-cleaned";
13
+ readonly TWEAK_MISMATCH: "tweak-mismatch";
14
+ }>;
15
+ export type CentralCompatibilityRejectionReason = (typeof CENTRAL_COMPATIBILITY_REJECTION_REASONS)[keyof typeof CENTRAL_COMPATIBILITY_REJECTION_REASONS];
16
+ export declare const CENTRAL_COMPATIBILITY_NOT_ASSESSED: Readonly<{
17
+ readonly status: "not-assessed";
18
+ }>;
19
+ export declare const CENTRAL_COMPATIBILITY_ACCEPTED: Readonly<{
20
+ readonly status: "accepted";
21
+ }>;
22
+ export interface CentralCompatibilityRejected {
23
+ readonly status: typeof CENTRAL_COMPATIBILITY_STATUSES.REJECTED;
24
+ readonly reason: CentralCompatibilityRejectionReason;
25
+ readonly preferredTweakValue?: Readonly<TweakValues>;
26
+ }
27
+ /** Immutable result produced by one provider-local compatibility assessment. */
28
+ export type CentralCompatibilityDecision = typeof CENTRAL_COMPATIBILITY_NOT_ASSESSED | typeof CENTRAL_COMPATIBILITY_ACCEPTED | CentralCompatibilityRejected;
29
+ /** Stack-local sink used while a provider owns the connection or client being assessed. */
30
+ export type CentralCompatibilityDecisionRecorder = (decision: CentralCompatibilityDecision) => void;
31
+ /** Immutable recovery input projected from the exact failed replication attempt. */
32
+ export interface CentralCompatibilityRecoveryHint {
33
+ readonly reason: CentralCompatibilityRejectionReason;
34
+ readonly preferredTweakValue?: Readonly<TweakValues>;
35
+ }
36
+ export declare function centralCompatibilityRejected(reason: CentralCompatibilityRejectionReason, preferredTweakValue?: TweakValues): CentralCompatibilityRejected;
37
+ /** Project only a rejected decision into the generic replication failure path. */
38
+ export declare function centralCompatibilityRecoveryHint(decision: CentralCompatibilityDecision): CentralCompatibilityRecoveryHint | undefined;
@@ -0,0 +1,39 @@
1
+ const CENTRAL_COMPATIBILITY_STATUSES = Object.freeze({
2
+ NOT_ASSESSED: "not-assessed",
3
+ ACCEPTED: "accepted",
4
+ REJECTED: "rejected"
5
+ });
6
+ const CENTRAL_COMPATIBILITY_REJECTION_REASONS = Object.freeze({
7
+ INCOMPATIBLE_VERSION: "incompatible-version",
8
+ NODE_LOCKED: "node-locked",
9
+ NODE_CLEANED: "node-cleaned",
10
+ TWEAK_MISMATCH: "tweak-mismatch"
11
+ });
12
+ const CENTRAL_COMPATIBILITY_NOT_ASSESSED = Object.freeze({
13
+ status: CENTRAL_COMPATIBILITY_STATUSES.NOT_ASSESSED
14
+ });
15
+ const CENTRAL_COMPATIBILITY_ACCEPTED = Object.freeze({
16
+ status: CENTRAL_COMPATIBILITY_STATUSES.ACCEPTED
17
+ });
18
+ function centralCompatibilityRejected(reason, preferredTweakValue) {
19
+ return Object.freeze({
20
+ status: CENTRAL_COMPATIBILITY_STATUSES.REJECTED,
21
+ reason,
22
+ ...preferredTweakValue === void 0 ? {} : { preferredTweakValue: Object.freeze({ ...preferredTweakValue }) }
23
+ });
24
+ }
25
+ function centralCompatibilityRecoveryHint(decision) {
26
+ if (decision.status !== CENTRAL_COMPATIBILITY_STATUSES.REJECTED) return void 0;
27
+ return Object.freeze({
28
+ reason: decision.reason,
29
+ ...decision.preferredTweakValue === void 0 ? {} : { preferredTweakValue: decision.preferredTweakValue }
30
+ });
31
+ }
32
+ export {
33
+ CENTRAL_COMPATIBILITY_ACCEPTED,
34
+ CENTRAL_COMPATIBILITY_NOT_ASSESSED,
35
+ CENTRAL_COMPATIBILITY_REJECTION_REASONS,
36
+ CENTRAL_COMPATIBILITY_STATUSES,
37
+ centralCompatibilityRecoveryHint,
38
+ centralCompatibilityRejected
39
+ };
@@ -0,0 +1,84 @@
1
+ import type { RemoteDBSettings } from "../common/types.js";
2
+ import type { ReplicatorInstance } from "./ReplicatorInstance.js";
3
+ /** Stable machine identifiers for central-remote administration mutations. */
4
+ export declare const CENTRAL_REMOTE_ADMINISTRATION_ACTIONS: Readonly<{
5
+ readonly MARK_RESOLVED: "mark-resolved";
6
+ readonly LOCK: "lock";
7
+ readonly UNLOCK: "unlock";
8
+ }>;
9
+ /** Stable machine identifiers for central-remote administration settlement. */
10
+ export declare const CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES: Readonly<{
11
+ readonly VERIFIED: "verified";
12
+ readonly VERIFICATION_FAILED: "verification-failed";
13
+ }>;
14
+ /** Stable machine identifiers for provider-specific observations. */
15
+ export declare const CENTRAL_REMOTE_ADMINISTRATION_OBSERVATION_KINDS: Readonly<{
16
+ readonly MILESTONE: "milestone";
17
+ }>;
18
+ /** Stable machine reasons which must not be used as display or translation keys. */
19
+ export declare const CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS: Readonly<{
20
+ readonly NO_ACTIVE_REPLICATOR: "no-active-replicator";
21
+ readonly ACTIVE_CONFIGURATION_MISMATCH: "active-configuration-mismatch";
22
+ readonly CAPABILITY_NOT_IMPLEMENTED: "capability-not-implemented";
23
+ readonly CAPABILITY_NOT_APPLICABLE: "capability-not-applicable";
24
+ readonly LOCAL_IDENTITY_UNAVAILABLE: "local-identity-unavailable";
25
+ readonly CONNECTION_FAILED: "connection-failed";
26
+ readonly MILESTONE_NOT_FOUND: "milestone-not-found";
27
+ readonly MILESTONE_READ_FAILED: "milestone-read-failed";
28
+ readonly POSTCONDITION_MISMATCH: "postcondition-mismatch";
29
+ }>;
30
+ export type CentralRemoteAdministrationAction = (typeof CENTRAL_REMOTE_ADMINISTRATION_ACTIONS)[keyof typeof CENTRAL_REMOTE_ADMINISTRATION_ACTIONS];
31
+ export type CentralRemoteAdministrationFailureReason = (typeof CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS)[keyof typeof CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS];
32
+ /** One requested mutation of the central milestone protocol. */
33
+ export interface CentralRemoteAdministrationRequest {
34
+ readonly action: CentralRemoteAdministrationAction;
35
+ }
36
+ /** Provider-independent projection of a central milestone after mutation. */
37
+ export interface MilestoneCentralRemoteAdministrationObservation {
38
+ readonly kind: typeof CENTRAL_REMOTE_ADMINISTRATION_OBSERVATION_KINDS.MILESTONE;
39
+ readonly locked: boolean;
40
+ readonly accepted: boolean;
41
+ readonly nodeId: string;
42
+ }
43
+ export type CentralRemoteAdministrationObservation = MilestoneCentralRemoteAdministrationObservation;
44
+ /** The requested mutation was observed in the provider's central milestone. */
45
+ export interface CentralRemoteAdministrationVerified {
46
+ readonly status: typeof CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES.VERIFIED;
47
+ readonly observation: CentralRemoteAdministrationObservation;
48
+ }
49
+ /** The request settled without a verified postcondition. */
50
+ export interface CentralRemoteAdministrationVerificationFailed {
51
+ readonly status: typeof CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES.VERIFICATION_FAILED;
52
+ readonly reason: CentralRemoteAdministrationFailureReason;
53
+ readonly observation?: CentralRemoteAdministrationObservation;
54
+ readonly detail?: unknown;
55
+ }
56
+ /**
57
+ * Settled central-remote administration result.
58
+ *
59
+ * Provider runners return verification failures. They must allow mutation
60
+ * exceptions to reject so a compatibility exit policy cannot hide a failed
61
+ * write behind an earlier command's historical success code.
62
+ */
63
+ export type CentralRemoteAdministrationResult = CentralRemoteAdministrationVerified | CentralRemoteAdministrationVerificationFailed;
64
+ /** Operations required by a provider which owns central remote administration. */
65
+ export interface CentralRemoteAdministrationReplicator extends ReplicatorInstance {
66
+ readonly nodeid: string;
67
+ markRemoteResolved(setting: RemoteDBSettings): Promise<void>;
68
+ markRemoteLocked(setting: RemoteDBSettings, locked: boolean, lockByClean: boolean): Promise<void>;
69
+ }
70
+ /** Provider boundary which must narrow the active instance before mutation. */
71
+ export type CentralRemoteAdministrationRunner = (replicator: ReplicatorInstance, setting: RemoteDBSettings, request: CentralRemoteAdministrationRequest) => Promise<CentralRemoteAdministrationResult>;
72
+ /** Construct a verified result from the provider's postcondition observation. */
73
+ export declare function centralRemoteAdministrationVerified(observation: CentralRemoteAdministrationObservation): CentralRemoteAdministrationVerified;
74
+ /** Construct a stable verification failure without converting mutation exceptions. */
75
+ export declare function centralRemoteAdministrationVerificationFailed(reason: CentralRemoteAdministrationFailureReason, options?: {
76
+ readonly observation?: CentralRemoteAdministrationObservation;
77
+ readonly detail?: unknown;
78
+ }): CentralRemoteAdministrationVerificationFailed;
79
+ /** Narrow one settled administration result to its verified arm. */
80
+ export declare function isCentralRemoteAdministrationVerified(result: CentralRemoteAdministrationResult): result is CentralRemoteAdministrationVerified;
81
+ /** Apply the legacy mutation selected by a typed administration request. */
82
+ export declare function applyCentralRemoteAdministrationMutation(replicator: CentralRemoteAdministrationReplicator, setting: RemoteDBSettings, action: CentralRemoteAdministrationAction): Promise<void>;
83
+ /** Return whether an observed milestone proves the requested postcondition. */
84
+ export declare function milestoneSatisfiesCentralRemoteAdministration(action: CentralRemoteAdministrationAction, observation: MilestoneCentralRemoteAdministrationObservation): boolean;
@@ -0,0 +1,71 @@
1
+ import { CAPABILITY_UNAVAILABLE_REASONS } from "./ProviderCapability.js";
2
+ const CENTRAL_REMOTE_ADMINISTRATION_ACTIONS = Object.freeze({
3
+ MARK_RESOLVED: "mark-resolved",
4
+ LOCK: "lock",
5
+ UNLOCK: "unlock"
6
+ });
7
+ const CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES = Object.freeze({
8
+ VERIFIED: "verified",
9
+ VERIFICATION_FAILED: "verification-failed"
10
+ });
11
+ const CENTRAL_REMOTE_ADMINISTRATION_OBSERVATION_KINDS = Object.freeze({
12
+ MILESTONE: "milestone"
13
+ });
14
+ const CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS = Object.freeze({
15
+ NO_ACTIVE_REPLICATOR: "no-active-replicator",
16
+ ACTIVE_CONFIGURATION_MISMATCH: "active-configuration-mismatch",
17
+ CAPABILITY_NOT_IMPLEMENTED: CAPABILITY_UNAVAILABLE_REASONS.NOT_IMPLEMENTED,
18
+ CAPABILITY_NOT_APPLICABLE: CAPABILITY_UNAVAILABLE_REASONS.NOT_APPLICABLE,
19
+ LOCAL_IDENTITY_UNAVAILABLE: "local-identity-unavailable",
20
+ CONNECTION_FAILED: "connection-failed",
21
+ MILESTONE_NOT_FOUND: "milestone-not-found",
22
+ MILESTONE_READ_FAILED: "milestone-read-failed",
23
+ POSTCONDITION_MISMATCH: "postcondition-mismatch"
24
+ });
25
+ function centralRemoteAdministrationVerified(observation) {
26
+ return { status: CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES.VERIFIED, observation };
27
+ }
28
+ function centralRemoteAdministrationVerificationFailed(reason, options = {}) {
29
+ return {
30
+ status: CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES.VERIFICATION_FAILED,
31
+ reason,
32
+ ...options
33
+ };
34
+ }
35
+ function isCentralRemoteAdministrationVerified(result) {
36
+ return result.status === CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES.VERIFIED;
37
+ }
38
+ async function applyCentralRemoteAdministrationMutation(replicator, setting, action) {
39
+ switch (action) {
40
+ case CENTRAL_REMOTE_ADMINISTRATION_ACTIONS.MARK_RESOLVED:
41
+ await replicator.markRemoteResolved(setting);
42
+ return;
43
+ case CENTRAL_REMOTE_ADMINISTRATION_ACTIONS.LOCK:
44
+ await replicator.markRemoteLocked(setting, true, false);
45
+ return;
46
+ case CENTRAL_REMOTE_ADMINISTRATION_ACTIONS.UNLOCK:
47
+ await replicator.markRemoteLocked(setting, false, false);
48
+ return;
49
+ }
50
+ }
51
+ function milestoneSatisfiesCentralRemoteAdministration(action, observation) {
52
+ switch (action) {
53
+ case CENTRAL_REMOTE_ADMINISTRATION_ACTIONS.MARK_RESOLVED:
54
+ return observation.accepted;
55
+ case CENTRAL_REMOTE_ADMINISTRATION_ACTIONS.LOCK:
56
+ return observation.locked;
57
+ case CENTRAL_REMOTE_ADMINISTRATION_ACTIONS.UNLOCK:
58
+ return !observation.locked;
59
+ }
60
+ }
61
+ export {
62
+ CENTRAL_REMOTE_ADMINISTRATION_ACTIONS,
63
+ CENTRAL_REMOTE_ADMINISTRATION_FAILURE_REASONS,
64
+ CENTRAL_REMOTE_ADMINISTRATION_OBSERVATION_KINDS,
65
+ CENTRAL_REMOTE_ADMINISTRATION_RESULT_STATUSES,
66
+ applyCentralRemoteAdministrationMutation,
67
+ centralRemoteAdministrationVerificationFailed,
68
+ centralRemoteAdministrationVerified,
69
+ isCentralRemoteAdministrationVerified,
70
+ milestoneSatisfiesCentralRemoteAdministration
71
+ };
@@ -1,7 +1,8 @@
1
- import { type EntryDoc, type DatabaseConnectingStatus, type RemoteDBSettings, type EntryLeaf, type TweakValues, type RemotePreferredTweakResult, type NodeData } from "../common/types.js";
1
+ import { type EntryDoc, type DatabaseConnectingStatus, type RemoteDBSettings, type TweakValues, type RemotePreferredTweakResult, type NodeData } from "../common/types.js";
2
2
  import type { RequiredServices } from "../interfaces/ServiceModule.js";
3
3
  import type { TranslationParameters } from "../services/base/MessageTranslator.js";
4
4
  import type { CommonlibMessageKey } from "../services/base/CommonlibMessages.js";
5
+ import type { ReplicatorInstance } from "./ReplicatorInstance.js";
5
6
  export type ReplicationCallback = (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<boolean> | boolean;
6
7
  export type ReplicationStat = {
7
8
  sent: number;
@@ -12,6 +13,13 @@ export type ReplicationStat = {
12
13
  lastSyncPushSeq: number;
13
14
  syncStatus: DatabaseConnectingStatus;
14
15
  };
16
+ /**
17
+ * Constructor environment shared by the legacy concrete Replicator facades.
18
+ *
19
+ * This compatibility boundary supplies host services to those facades. Active
20
+ * provider capabilities and their ownership are declared separately by the
21
+ * replication provider contracts.
22
+ */
15
23
  export interface LiveSyncReplicatorEnv {
16
24
  services: RequiredServices<"API" | "appLifecycle" | "setting" | "vault" | "database" | "databaseEvents" | "keyValueDB" | "replication" | "config" | "UI" | "replicator" | "remote">;
17
25
  }
@@ -19,7 +27,14 @@ export type RemoteDBStatus = {
19
27
  [key: string]: unknown;
20
28
  estimatedSize?: number;
21
29
  };
22
- export declare abstract class LiveSyncAbstractReplicator {
30
+ /**
31
+ * Legacy compatibility facade around the small active Replicator contract.
32
+ *
33
+ * New active-provider control flow depends only on {@link ReplicatorInstance}.
34
+ * The additional abstract members remain temporarily for existing maintenance
35
+ * and setup consumers and are not capabilities promised by every provider.
36
+ */
37
+ export declare abstract class LiveSyncAbstractReplicator implements ReplicatorInstance {
23
38
  syncStatus: DatabaseConnectingStatus;
24
39
  docArrived: number;
25
40
  docSent: number;
@@ -45,23 +60,21 @@ export declare abstract class LiveSyncAbstractReplicator {
45
60
  env: LiveSyncReplicatorEnv;
46
61
  initializeDatabaseForReplication(): Promise<boolean>;
47
62
  constructor(env: LiveSyncReplicatorEnv);
48
- abstract terminateSync(): void;
63
+ abstract terminateSync(): void | Promise<void>;
49
64
  abstract openReplication(setting: RemoteDBSettings, keepAlive: boolean, showResult: boolean, ignoreCleanLock: boolean): Promise<void | boolean>;
50
65
  updateInfo: () => void;
51
66
  abstract tryConnectRemote(setting: RemoteDBSettings, showResult?: boolean): Promise<boolean>;
52
67
  abstract replicateAllToServer(setting: RemoteDBSettings, showingNotice?: boolean): Promise<boolean>;
53
68
  abstract replicateAllFromServer(setting: RemoteDBSettings, showingNotice?: boolean): Promise<boolean>;
54
- abstract closeReplication(): void;
69
+ abstract closeReplication(): void | Promise<void>;
55
70
  abstract tryResetRemoteDatabase(setting: RemoteDBSettings): Promise<void>;
56
71
  abstract tryCreateRemoteDatabase(setting: RemoteDBSettings): Promise<void>;
57
72
  abstract markRemoteLocked(setting: RemoteDBSettings, locked: boolean, lockByClean: boolean): Promise<void>;
58
73
  abstract markRemoteResolved(setting: RemoteDBSettings): Promise<void>;
59
74
  abstract resetRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
60
75
  abstract setPreferredRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
61
- abstract fetchRemoteChunks(missingChunks: string[], showResult: boolean): Promise<false | EntryLeaf[]>;
62
76
  abstract getRemoteStatus(setting: RemoteDBSettings): Promise<false | RemoteDBStatus>;
63
77
  abstract getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
64
- abstract countCompromisedChunks(setting?: RemoteDBSettings): Promise<number | boolean>;
65
78
  abstract getConnectedDeviceList(setting?: RemoteDBSettings): Promise<false | {
66
79
  node_info: Record<string, NodeData>;
67
80
  accepted_nodes: string[];
@@ -0,0 +1,32 @@
1
+ /** Stable machine values for provider capability availability. */
2
+ export declare const CAPABILITY_SUPPORT_KINDS: Readonly<{
3
+ readonly SUPPORTED: "supported";
4
+ readonly NOT_IMPLEMENTED: "not-implemented";
5
+ readonly NOT_APPLICABLE: "not-applicable";
6
+ }>;
7
+ /** Stable machine reasons for an unavailable provider capability. */
8
+ export declare const CAPABILITY_UNAVAILABLE_REASONS: Readonly<{
9
+ readonly NOT_IMPLEMENTED: "capability-not-implemented";
10
+ readonly NOT_APPLICABLE: "capability-not-applicable";
11
+ }>;
12
+ /** A declared role is available, but its implementation is not included. */
13
+ export type NotImplementedCapability = {
14
+ readonly kind: typeof CAPABILITY_SUPPORT_KINDS.NOT_IMPLEMENTED;
15
+ readonly reason: typeof CAPABILITY_UNAVAILABLE_REASONS.NOT_IMPLEMENTED;
16
+ };
17
+ /** A declared role does not apply to this provider. */
18
+ export type NotApplicableCapability = {
19
+ readonly kind: typeof CAPABILITY_SUPPORT_KINDS.NOT_APPLICABLE;
20
+ readonly reason: typeof CAPABILITY_UNAVAILABLE_REASONS.NOT_APPLICABLE;
21
+ };
22
+ /** One available capability paired with the only runner allowed to perform its role. */
23
+ export type SupportedCapability<TRole> = {
24
+ readonly kind: typeof CAPABILITY_SUPPORT_KINDS.SUPPORTED;
25
+ readonly run: TRole;
26
+ };
27
+ /** Provider capability declaration correlated with the role runner. */
28
+ export type CapabilitySupport<TRole> = SupportedCapability<TRole> | NotImplementedCapability | NotApplicableCapability;
29
+ export declare const CAPABILITY_NOT_IMPLEMENTED: NotImplementedCapability;
30
+ export declare const CAPABILITY_NOT_APPLICABLE: NotApplicableCapability;
31
+ /** Declare one implemented provider capability without repeating its machine value. */
32
+ export declare function supportedCapability<TRole>(run: TRole): SupportedCapability<TRole>;
@@ -0,0 +1,27 @@
1
+ const CAPABILITY_SUPPORT_KINDS = Object.freeze({
2
+ SUPPORTED: "supported",
3
+ NOT_IMPLEMENTED: "not-implemented",
4
+ NOT_APPLICABLE: "not-applicable"
5
+ });
6
+ const CAPABILITY_UNAVAILABLE_REASONS = Object.freeze({
7
+ NOT_IMPLEMENTED: "capability-not-implemented",
8
+ NOT_APPLICABLE: "capability-not-applicable"
9
+ });
10
+ const CAPABILITY_NOT_IMPLEMENTED = Object.freeze({
11
+ kind: CAPABILITY_SUPPORT_KINDS.NOT_IMPLEMENTED,
12
+ reason: CAPABILITY_UNAVAILABLE_REASONS.NOT_IMPLEMENTED
13
+ });
14
+ const CAPABILITY_NOT_APPLICABLE = Object.freeze({
15
+ kind: CAPABILITY_SUPPORT_KINDS.NOT_APPLICABLE,
16
+ reason: CAPABILITY_UNAVAILABLE_REASONS.NOT_APPLICABLE
17
+ });
18
+ function supportedCapability(run) {
19
+ return { kind: CAPABILITY_SUPPORT_KINDS.SUPPORTED, run };
20
+ }
21
+ export {
22
+ CAPABILITY_NOT_APPLICABLE,
23
+ CAPABILITY_NOT_IMPLEMENTED,
24
+ CAPABILITY_SUPPORT_KINDS,
25
+ CAPABILITY_UNAVAILABLE_REASONS,
26
+ supportedCapability
27
+ };