@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.
- package/README.md +5 -1
- package/dist/common/models/setting.p2p.d.ts +2 -0
- package/dist/common/models/setting.p2p.js +5 -0
- package/dist/interfaces/ServiceModule.d.ts +13 -4
- package/dist/managers/ChunkFetcher.js +10 -0
- package/dist/p2p/P2PService.d.ts +123 -0
- package/dist/p2p/P2PService.js +271 -0
- package/dist/p2p/index.d.ts +8 -0
- package/dist/p2p/index.js +8 -0
- package/dist/pouchdb/LiveSyncLocalDB.d.ts +3 -0
- package/dist/pouchdb/LiveSyncLocalDB.js +23 -3
- package/dist/pouchdb/ReplicatorShim.d.ts +43 -4
- package/dist/pouchdb/ReplicatorShim.js +78 -5
- package/dist/replication/CentralCompatibility.d.ts +38 -0
- package/dist/replication/CentralCompatibility.js +39 -0
- package/dist/replication/CentralRemoteAdministration.d.ts +84 -0
- package/dist/replication/CentralRemoteAdministration.js +71 -0
- package/dist/replication/LiveSyncAbstractReplicator.d.ts +19 -6
- package/dist/replication/ProviderCapability.d.ts +32 -0
- package/dist/replication/ProviderCapability.js +27 -0
- package/dist/replication/RemoteResource.d.ts +85 -0
- package/dist/replication/RemoteResource.js +17 -0
- package/dist/replication/ReplicatorInstance.d.ts +23 -0
- package/dist/replication/ReplicatorProvider.d.ts +249 -0
- package/dist/replication/ReplicatorProvider.js +145 -0
- package/dist/replication/couchdb/LiveSyncReplicator.d.ts +54 -8
- package/dist/replication/couchdb/LiveSyncReplicator.js +411 -158
- package/dist/replication/index.d.ts +10 -0
- package/dist/replication/index.js +70 -0
- package/dist/replication/journal/JournalSyncCore.d.ts +2 -0
- package/dist/replication/journal/JournalSyncCore.js +18 -5
- package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +38 -10
- package/dist/replication/journal/LiveSyncJournalReplicator.js +271 -63
- package/dist/replication/journal/LiveSyncJournalReplicatorEnv.d.ts +8 -0
- package/dist/replication/journal/objectstore/JournalStorageAdapter.d.ts +2 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.d.ts +2 -0
- package/dist/replication/journal/objectstore/MinioStorageAdapter.js +10 -1
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +12 -40
- package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +52 -152
- package/dist/replication/trystero/P2PAutomationCoordinator.d.ts +20 -0
- package/dist/replication/trystero/P2PAutomationCoordinator.js +43 -0
- package/dist/replication/trystero/P2PConnectionProbeAdmission.d.ts +32 -0
- package/dist/replication/trystero/P2PConnectionProbeAdmission.js +10 -0
- package/dist/replication/trystero/P2PReplicationWire.d.ts +31 -0
- package/dist/replication/trystero/P2PReplicationWire.js +50 -0
- package/dist/replication/trystero/P2PRoomSession.d.ts +32 -0
- package/dist/replication/trystero/P2PRoomSession.js +101 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.d.ts +61 -0
- package/dist/replication/trystero/P2PRoomSessionOwner.js +249 -0
- package/dist/replication/trystero/TrysteroReplicator.d.ts +88 -36
- package/dist/replication/trystero/TrysteroReplicator.js +273 -113
- package/dist/replication/trystero/TrysteroReplicatorP2PClient.d.ts +7 -4
- package/dist/replication/trystero/TrysteroReplicatorP2PClient.js +21 -14
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +26 -3
- package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +84 -39
- package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -6
- package/dist/replication/trystero/addP2PEventHandlers.d.ts +8 -12
- package/dist/replication/trystero/addP2PEventHandlers.js +19 -19
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.d.ts +9 -0
- package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +30 -0
- package/dist/replication/trystero/types.d.ts +18 -0
- package/dist/replication/trystero/types.js +5 -1
- package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +3 -3
- package/dist/replication/trystero/useP2PReplicatorCommands.js +6 -8
- package/dist/replication/trystero/useP2PReplicatorFeature.d.ts +16 -7
- package/dist/replication/trystero/useP2PReplicatorFeature.js +72 -55
- package/dist/rpc/RpcRoom.d.ts +15 -1
- package/dist/rpc/RpcRoom.js +176 -41
- package/dist/rpc/RpcSession.d.ts +3 -1
- package/dist/rpc/RpcSession.js +2 -2
- package/dist/rpc/index.d.ts +1 -1
- package/dist/rpc/transports/trysteroUtils.js +2 -1
- package/dist/rpc/types.d.ts +26 -1
- package/dist/serviceFeatures/checkRemoteSize.js +5 -2
- package/dist/serviceModules/Rebuilder.js +66 -56
- package/dist/services/base/AppLifecycleService.d.ts +4 -0
- package/dist/services/base/AppLifecycleService.js +4 -0
- package/dist/services/base/ControlService.js +0 -4
- package/dist/services/base/DatabaseEventService.js +1 -1
- package/dist/services/base/IService.d.ts +105 -8
- package/dist/services/base/ReadinessEvaluator.d.ts +40 -0
- package/dist/services/base/ReadinessEvaluator.js +16 -0
- package/dist/services/base/RemoteConnection.d.ts +3 -0
- package/dist/services/base/RemoteService.js +2 -2
- package/dist/services/base/ReplicationService.d.ts +45 -10
- package/dist/services/base/ReplicationService.js +248 -91
- package/dist/services/base/ReplicationService.readiness.d.ts +52 -0
- package/dist/services/base/ReplicationService.readiness.js +87 -0
- package/dist/services/base/ReplicationService.typedReplication.d.ts +83 -0
- package/dist/services/base/ReplicationService.typedReplication.js +191 -0
- package/dist/services/base/ReplicatorService.activeReplicatorState.d.ts +47 -0
- package/dist/services/base/ReplicatorService.activeReplicatorState.js +107 -0
- package/dist/services/base/ReplicatorService.centralRemoteAdministration.d.ts +5 -0
- package/dist/services/base/ReplicatorService.centralRemoteAdministration.js +44 -0
- package/dist/services/base/ReplicatorService.d.ts +75 -14
- package/dist/services/base/ReplicatorService.js +232 -53
- package/dist/services/base/ReplicatorService.remoteResourceResolver.d.ts +4 -0
- package/dist/services/base/ReplicatorService.remoteResourceResolver.js +11 -0
- package/dist/services/base/TweakValueService.d.ts +3 -2
- package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +1 -1
- package/dist/services/lib/HandlerUtils.d.ts +24 -0
- package/dist/services/lib/HandlerUtils.js +40 -0
- package/docs/development.md +40 -7
- package/docs/p2p-transport-lifecycle.md +135 -11
- package/docs/proven-in-use.md +5 -1
- package/docs/remote-configurations.md +4 -0
- package/docs/service-feature-composition.md +250 -0
- package/package.json +17 -1
- package/dist/replication/trystero/P2PReplicatorBase.d.ts +0 -19
- package/dist/replication/trystero/P2PReplicatorCore.d.ts +0 -8
- package/dist/replication/trystero/P2PReplicatorCore.js +0 -56
- package/dist/replication/trystero/TrysteroReplicatorP2PConnection.d.ts +0 -2
- package/dist/replication/trystero/TrysteroReplicatorP2PConnection.js +0 -4
- /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
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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<
|
|
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)
|
|
71
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
+
};
|