@vrtmrz/livesync-commonlib 0.1.4 → 0.1.5

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.
@@ -190,4 +190,28 @@ export declare const TweakValuesTemplate: {
190
190
  };
191
191
  export type TweakValues = Partial<typeof TweakValuesTemplate>;
192
192
  export declare const DEVICE_ID_PREFERRED = "PREFERRED";
193
+ export declare const RemotePreferredTweakStatuses: {
194
+ readonly AVAILABLE: "available";
195
+ readonly NOT_CONFIGURED: "not-configured";
196
+ readonly UNAVAILABLE: "unavailable";
197
+ readonly UNSUPPORTED: "unsupported";
198
+ };
199
+ export type RemotePreferredTweakStatus = (typeof RemotePreferredTweakStatuses)[keyof typeof RemotePreferredTweakStatuses];
200
+ export declare const RemotePreferredTweakNotConfiguredReasons: {
201
+ readonly MILESTONE_MISSING: "milestone-missing";
202
+ readonly PREFERRED_VALUES_MISSING: "preferred-values-missing";
203
+ };
204
+ export type RemotePreferredTweakNotConfiguredReason = (typeof RemotePreferredTweakNotConfiguredReasons)[keyof typeof RemotePreferredTweakNotConfiguredReasons];
205
+ export type RemotePreferredTweakResult = {
206
+ status: typeof RemotePreferredTweakStatuses.AVAILABLE;
207
+ values: TweakValues;
208
+ } | {
209
+ status: typeof RemotePreferredTweakStatuses.NOT_CONFIGURED;
210
+ reason: RemotePreferredTweakNotConfiguredReason;
211
+ } | {
212
+ status: typeof RemotePreferredTweakStatuses.UNAVAILABLE;
213
+ error: unknown;
214
+ } | {
215
+ status: typeof RemotePreferredTweakStatuses.UNSUPPORTED;
216
+ };
193
217
  export {};
@@ -60,11 +60,23 @@ const TweakValuesTemplate = {
60
60
  tweakModified: 0
61
61
  };
62
62
  const DEVICE_ID_PREFERRED = "PREFERRED";
63
+ const RemotePreferredTweakStatuses = {
64
+ AVAILABLE: "available",
65
+ NOT_CONFIGURED: "not-configured",
66
+ UNAVAILABLE: "unavailable",
67
+ UNSUPPORTED: "unsupported"
68
+ };
69
+ const RemotePreferredTweakNotConfiguredReasons = {
70
+ MILESTONE_MISSING: "milestone-missing",
71
+ PREFERRED_VALUES_MISSING: "preferred-values-missing"
72
+ };
63
73
  export {
64
74
  CompatibleButLossyChanges,
65
75
  DEVICE_ID_PREFERRED,
66
76
  IncompatibleChanges,
67
77
  IncompatibleChangesInSpecificPattern,
78
+ RemotePreferredTweakNotConfiguredReasons,
79
+ RemotePreferredTweakStatuses,
68
80
  TweakValuesDefault,
69
81
  TweakValuesRecommendedTemplate,
70
82
  TweakValuesShouldMatchedTemplate,
@@ -21,7 +21,7 @@ import { SETTING_KEY_P2P_DEVICE_NAME, configURIBase, configURIBaseQR, SuffixData
21
21
  import { configurationNames, LEVEL_ADVANCED, LEVEL_POWER_USER, LEVEL_EDGE_CASE, type ConfigLevel, type ConfigurationItem, statusDisplay, confName, confDesc } from "./models/shared.definition.configNames.js";
22
22
  import type { CustomRegExpSource, CustomRegExpSourceList, ParsedCustomRegExp, Prettify } from "./models/shared.type.util.js";
23
23
  import { ProtocolVersions, type ProtocolVersion, DOCID_SYNC_PARAMETERS, DOCID_JOURNAL_SYNC_PARAMETERS, type SyncParameters, DEFAULT_SYNC_PARAMETERS } from "./models/sync.definition.js";
24
- import { TweakValuesShouldMatchedTemplate, IncompatibleChanges, CompatibleButLossyChanges, IncompatibleChangesInSpecificPattern, TweakValuesRecommendedTemplate, TweakValuesDefault, TweakValuesTemplate, type TweakValues, DEVICE_ID_PREFERRED } from "./models/tweak.definition.js";
24
+ import { TweakValuesShouldMatchedTemplate, IncompatibleChanges, CompatibleButLossyChanges, IncompatibleChangesInSpecificPattern, TweakValuesRecommendedTemplate, TweakValuesDefault, TweakValuesTemplate, type TweakValues, DEVICE_ID_PREFERRED, RemotePreferredTweakStatuses, type RemotePreferredTweakStatus, RemotePreferredTweakNotConfiguredReasons, type RemotePreferredTweakNotConfiguredReason, type RemotePreferredTweakResult } from "./models/tweak.definition.js";
25
25
  import type { diff_result_leaf, dmp_result, diff_result, DIFF_CHECK_RESULT_AUTO, diff_check_result } from "./models/diff.definition.js";
26
26
  import { PREFIXMD_LOGFILE, PREFIXMD_LOGFILE_UC, FlagFilesOriginal, FlagFilesHumanReadable, FLAGMD_REDFLAG, FLAGMD_REDFLAG2, FLAGMD_REDFLAG2_HR, FLAGMD_REDFLAG3, FLAGMD_REDFLAG3_HR } from "./models/redflag.const.js";
27
27
  import { DatabaseConnectingStatuses, type DatabaseConnectingStatus } from "./models/shared.definition.js";
@@ -68,6 +68,11 @@ export { confDesc };
68
68
  export { TweakValuesTemplate };
69
69
  export type { TweakValues };
70
70
  export { DEVICE_ID_PREFERRED };
71
+ export { RemotePreferredTweakStatuses };
72
+ export type { RemotePreferredTweakStatus };
73
+ export { RemotePreferredTweakNotConfiguredReasons };
74
+ export type { RemotePreferredTweakNotConfiguredReason };
75
+ export type { RemotePreferredTweakResult };
71
76
  export type { NodeKey };
72
77
  export type { DeviceInfo };
73
78
  export type { NodeData };
@@ -110,7 +110,9 @@ import {
110
110
  TweakValuesRecommendedTemplate,
111
111
  TweakValuesDefault,
112
112
  TweakValuesTemplate,
113
- DEVICE_ID_PREFERRED
113
+ DEVICE_ID_PREFERRED,
114
+ RemotePreferredTweakStatuses,
115
+ RemotePreferredTweakNotConfiguredReasons
114
116
  } from "./models/tweak.definition.js";
115
117
  import {
116
118
  PREFIXMD_LOGFILE,
@@ -197,6 +199,8 @@ export {
197
199
  REPLICATION_BUSY_TIMEOUT,
198
200
  RESULT_NOT_FOUND,
199
201
  RESULT_TIMED_OUT,
202
+ RemotePreferredTweakNotConfiguredReasons,
203
+ RemotePreferredTweakStatuses,
200
204
  RemoteTypes,
201
205
  SALT_OF_ID,
202
206
  SALT_OF_PASSPHRASE,
@@ -1,4 +1,4 @@
1
- import { type EntryDoc, type DatabaseConnectingStatus, type RemoteDBSettings, type EntryLeaf, type TweakValues, type NodeData } from "../common/types.js";
1
+ import { type EntryDoc, type DatabaseConnectingStatus, type RemoteDBSettings, type EntryLeaf, 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";
@@ -60,7 +60,7 @@ export declare abstract class LiveSyncAbstractReplicator {
60
60
  abstract setPreferredRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
61
61
  abstract fetchRemoteChunks(missingChunks: string[], showResult: boolean): Promise<false | EntryLeaf[]>;
62
62
  abstract getRemoteStatus(setting: RemoteDBSettings): Promise<false | RemoteDBStatus>;
63
- abstract getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<false | TweakValues>;
63
+ abstract getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
64
64
  abstract countCompromisedChunks(setting?: RemoteDBSettings): Promise<number | boolean>;
65
65
  abstract getConnectedDeviceList(setting?: RemoteDBSettings): Promise<false | {
66
66
  node_info: Record<string, NodeData>;
@@ -1,4 +1,4 @@
1
- import { type EntryDoc, type RemoteDBSettings, type EntryLeaf, type TweakValues, type SyncParameters, type DatabaseEntry, type NodeData } from "../../common/types.js";
1
+ import { type EntryDoc, type RemoteDBSettings, type EntryLeaf, type SyncParameters, type DatabaseEntry, type NodeData, type RemotePreferredTweakResult } from "../../common/types.js";
2
2
  import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
3
3
  import type { ServiceHub } from "../../services/ServiceHub.js";
4
4
  export interface LiveSyncCouchDBReplicatorEnv extends LiveSyncReplicatorEnv {
@@ -73,7 +73,7 @@ export declare class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicato
73
73
  tryConnectRemote(setting: RemoteDBSettings, showResult?: boolean): Promise<boolean>;
74
74
  resetRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
75
75
  setPreferredRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
76
- getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<TweakValues | false>;
76
+ getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
77
77
  compactRemote(setting: RemoteDBSettings): Promise<boolean>;
78
78
  getRemoteStatus(setting: RemoteDBSettings): Promise<RemoteDBStatus | false>;
79
79
  countCompromisedChunks(setting?: RemoteDBSettings): Promise<number | boolean>;
@@ -9,7 +9,9 @@ import {
9
9
  TweakValuesTemplate,
10
10
  DOCID_SYNC_PARAMETERS,
11
11
  DEFAULT_SYNC_PARAMETERS,
12
- ProtocolVersions
12
+ ProtocolVersions,
13
+ RemotePreferredTweakNotConfiguredReasons,
14
+ RemotePreferredTweakStatuses
13
15
  } from "../../common/types.js";
14
16
  import {
15
17
  resolveWithIgnoreKnownError,
@@ -22,6 +24,7 @@ import {
22
24
  } from "../../common/utils.js";
23
25
  import { Logger } from "../../common/logger.js";
24
26
  import { checkRemoteVersion, countCompromisedChunks } from "../../pouchdb/negotiation.js";
27
+ import { isErrorOfMissingDoc } from "../../pouchdb/utils_couchdb.js";
25
28
  import { preprocessOutgoing } from "../../pouchdb/encryption.js";
26
29
  import { ensureDatabaseIsCompatible } from "../../pouchdb/LiveSyncDBFunctions.js";
27
30
  import {
@@ -1050,23 +1053,73 @@ ${sendingDocs}/(${sentDocsCount} done)`,
1050
1053
  }
1051
1054
  async getRemotePreferredTweakValues(setting) {
1052
1055
  const uri = setting.couchDB_URI.replace(/\/+$/, "") + (setting.couchDB_DBNAME == "" ? "" : "/" + setting.couchDB_DBNAME);
1053
- const dbRet = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
1056
+ let dbRet;
1057
+ try {
1058
+ dbRet = await this.connectRemoteCouchDBWithSetting(setting, this.isMobile(), true);
1059
+ } catch (ex) {
1060
+ Logger(`Could not connect to the remote database`, LOG_LEVEL_NOTICE);
1061
+ Logger(ex, LOG_LEVEL_VERBOSE);
1062
+ return {
1063
+ status: RemotePreferredTweakStatuses.UNAVAILABLE,
1064
+ error: ex
1065
+ };
1066
+ }
1054
1067
  if (typeof dbRet === "string") {
1055
1068
  Logger(this.translate("liveSyncReplicator.couldNotConnectToURI", { uri, dbRet }), LOG_LEVEL_NOTICE);
1056
- return false;
1069
+ return {
1070
+ status: RemotePreferredTweakStatuses.UNAVAILABLE,
1071
+ error: new Error(dbRet)
1072
+ };
1057
1073
  }
1058
- if (!await checkRemoteVersion(dbRet.db, this.migrate.bind(this), VER)) {
1059
- Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
1060
- return false;
1074
+ try {
1075
+ if (!await checkRemoteVersion(dbRet.db, this.migrate.bind(this), VER)) {
1076
+ const error = new Error("The remote database version is not compatible");
1077
+ Logger(this.translate("liveSyncReplicator.remoteDbCorrupted"), LOG_LEVEL_NOTICE);
1078
+ return {
1079
+ status: RemotePreferredTweakStatuses.UNAVAILABLE,
1080
+ error
1081
+ };
1082
+ }
1083
+ } catch (ex) {
1084
+ Logger(`Could not check the remote database version`, LOG_LEVEL_NOTICE);
1085
+ Logger(ex, LOG_LEVEL_VERBOSE);
1086
+ return {
1087
+ status: RemotePreferredTweakStatuses.UNAVAILABLE,
1088
+ error: ex
1089
+ };
1061
1090
  }
1062
1091
  try {
1063
1092
  const remoteMilestone = await dbRet.db.get(MILESTONE_DOCID);
1064
- if (!remoteMilestone) throw new Error("Remote milestone not found");
1065
- return remoteMilestone?.tweak_values?.[DEVICE_ID_PREFERRED] || false;
1093
+ if (!remoteMilestone) {
1094
+ return {
1095
+ status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
1096
+ reason: RemotePreferredTweakNotConfiguredReasons.MILESTONE_MISSING
1097
+ };
1098
+ }
1099
+ const preferred = remoteMilestone.tweak_values?.[DEVICE_ID_PREFERRED];
1100
+ if (!preferred) {
1101
+ return {
1102
+ status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
1103
+ reason: RemotePreferredTweakNotConfiguredReasons.PREFERRED_VALUES_MISSING
1104
+ };
1105
+ }
1106
+ return {
1107
+ status: RemotePreferredTweakStatuses.AVAILABLE,
1108
+ values: preferred
1109
+ };
1066
1110
  } catch (ex) {
1111
+ if (isErrorOfMissingDoc(ex)) {
1112
+ return {
1113
+ status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
1114
+ reason: RemotePreferredTweakNotConfiguredReasons.MILESTONE_MISSING
1115
+ };
1116
+ }
1067
1117
  Logger(`Could not retrieve remote milestone`, LOG_LEVEL_NOTICE);
1068
1118
  Logger(ex, LOG_LEVEL_VERBOSE);
1069
- return false;
1119
+ return {
1120
+ status: RemotePreferredTweakStatuses.UNAVAILABLE,
1121
+ error: ex
1122
+ };
1070
1123
  }
1071
1124
  }
1072
1125
  async compactRemote(setting) {
@@ -3,7 +3,7 @@ import type { ReplicationCallback, ReplicationStat } from "../LiveSyncAbstractRe
3
3
  import { type SimpleStore } from "../../common/utils.js";
4
4
  import { type CheckPointInfo } from "./JournalSyncTypes.js";
5
5
  import type { LiveSyncJournalReplicatorEnv } from "./LiveSyncJournalReplicatorEnv.js";
6
- import type { IJournalStorage } from "./objectstore/JournalStorageAdapter.js";
6
+ import { type IJournalStorage, type JournalStorageReadResult } from "./objectstore/JournalStorageAdapter.js";
7
7
  type ProcessingEntry = PouchDB.Core.PutDocument<EntryDoc> & PouchDB.Core.GetMeta;
8
8
  export declare class JournalSyncCore {
9
9
  _settings: BucketSyncSetting;
@@ -22,6 +22,7 @@ export declare class JournalSyncCore {
22
22
  getHash(settings: BucketSyncSetting): string;
23
23
  constructor(settings: BucketSyncSetting, store: SimpleStore<CheckPointInfo>, env: LiveSyncJournalReplicatorEnv, storage: IJournalStorage);
24
24
  downloadJson<T>(key: string): Promise<T | false>;
25
+ downloadJsonWithResult<T>(key: string): Promise<JournalStorageReadResult<T>>;
25
26
  uploadJson<T>(key: string, body: T): Promise<boolean>;
26
27
  applyNewConfig(settings: BucketSyncSetting, store: SimpleStore<CheckPointInfo>, env: LiveSyncJournalReplicatorEnv): void;
27
28
  updateInfo(info: Partial<ReplicationStat>): void;
@@ -18,6 +18,9 @@ import {
18
18
  import { shareRunningResult } from "octagonal-wheels/concurrency/lock";
19
19
  import { wrappedDeflate, wrappedInflate } from "../../pouchdb/compress.js";
20
20
  import { CheckPointInfoDefault } from "./JournalSyncTypes.js";
21
+ import {
22
+ JournalStorageReadStatuses
23
+ } from "./objectstore/JournalStorageAdapter.js";
21
24
  import {
22
25
  clearHandlers,
23
26
  createSyncParamsHanderForServer,
@@ -116,6 +119,23 @@ class JournalSyncCore {
116
119
  return false;
117
120
  }
118
121
  }
122
+ async downloadJsonWithResult(key) {
123
+ const result = await this.storage.downloadWithResult(key, true);
124
+ if (result.status !== JournalStorageReadStatuses.AVAILABLE) return result;
125
+ try {
126
+ return {
127
+ status: JournalStorageReadStatuses.AVAILABLE,
128
+ value: JSON.parse(new TextDecoder().decode(result.value))
129
+ };
130
+ } catch (ex) {
131
+ Logger(`Could not parse downloaded json ${key}`);
132
+ Logger(ex, LOG_LEVEL_VERBOSE);
133
+ return {
134
+ status: JournalStorageReadStatuses.UNAVAILABLE,
135
+ error: ex
136
+ };
137
+ }
138
+ }
119
139
  async uploadJson(key, body) {
120
140
  try {
121
141
  const data = new TextEncoder().encode(JSON.stringify(body));
@@ -1,4 +1,4 @@
1
- import { type RemoteDBSettings, type EntryLeaf, type ChunkVersionRange, type TweakValues, type NodeData } from "../../common/types.js";
1
+ import { type RemoteDBSettings, type EntryLeaf, type ChunkVersionRange, type NodeData, type RemotePreferredTweakResult } from "../../common/types.js";
2
2
  import { JournalSyncCore } from "./JournalSyncCore.js";
3
3
  import { LiveSyncAbstractReplicator, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
4
4
  import { type ENSURE_DB_RESULT } from "../../pouchdb/LiveSyncDBFunctions.js";
@@ -29,7 +29,7 @@ export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicato
29
29
  tryConnectRemote(setting: RemoteDBSettings, showResult?: boolean): Promise<boolean>;
30
30
  resetRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
31
31
  setPreferredRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
32
- getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<false | TweakValues>;
32
+ getRemotePreferredTweakValues(_setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
33
33
  getRemoteStatus(setting: RemoteDBSettings): Promise<false | RemoteDBStatus>;
34
34
  countCompromisedChunks(): Promise<number>;
35
35
  getConnectedDeviceList(setting?: RemoteDBSettings): Promise<false | {
@@ -2,7 +2,9 @@ import {
2
2
  LOG_LEVEL_NOTICE,
3
3
  LOG_LEVEL_VERBOSE,
4
4
  DEVICE_ID_PREFERRED,
5
- TweakValuesTemplate
5
+ TweakValuesTemplate,
6
+ RemotePreferredTweakNotConfiguredReasons,
7
+ RemotePreferredTweakStatuses
6
8
  } from "../../common/types.js";
7
9
  import { Logger } from "../../common/logger.js";
8
10
  import { JournalSyncCore } from "./JournalSyncCore.js";
@@ -11,6 +13,7 @@ import { LiveSyncAbstractReplicator } from "../LiveSyncAbstractReplicator.js";
11
13
  import { ensureRemoteIsCompatible } from "../../pouchdb/LiveSyncDBFunctions.js";
12
14
  import { extractObject } from "../../common/utils.js";
13
15
  import { clearHandlers } from "../SyncParamsHandler.js";
16
+ import { JournalStorageReadStatuses } from "./objectstore/JournalStorageAdapter.js";
14
17
  const MILSTONE_DOCID = "_00000000-milestone.json";
15
18
  const currentVersionRange = {
16
19
  min: 0,
@@ -256,18 +259,31 @@ ${ex.message}`, LOG_LEVEL_NOTICE);
256
259
  throw ex;
257
260
  }
258
261
  }
259
- async getRemotePreferredTweakValues(setting) {
260
- try {
261
- const remoteMilestone = await this.client.downloadJson(MILSTONE_DOCID);
262
- if (!remoteMilestone) {
263
- throw new Error("Missing remote milestone");
264
- }
265
- return remoteMilestone?.tweak_values?.[DEVICE_ID_PREFERRED] || false;
266
- } catch (ex) {
267
- Logger(`Could not retrieve remote milestone`, LOG_LEVEL_NOTICE);
268
- Logger(ex, LOG_LEVEL_VERBOSE);
269
- return false;
262
+ async getRemotePreferredTweakValues(_setting) {
263
+ const result = await this.client.downloadJsonWithResult(MILSTONE_DOCID);
264
+ if (result.status === JournalStorageReadStatuses.NOT_FOUND) {
265
+ return {
266
+ status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
267
+ reason: RemotePreferredTweakNotConfiguredReasons.MILESTONE_MISSING
268
+ };
269
+ }
270
+ if (result.status === JournalStorageReadStatuses.UNAVAILABLE) {
271
+ return {
272
+ status: RemotePreferredTweakStatuses.UNAVAILABLE,
273
+ error: result.error
274
+ };
270
275
  }
276
+ const preferred = result.value.tweak_values?.[DEVICE_ID_PREFERRED];
277
+ if (!preferred) {
278
+ return {
279
+ status: RemotePreferredTweakStatuses.NOT_CONFIGURED,
280
+ reason: RemotePreferredTweakNotConfiguredReasons.PREFERRED_VALUES_MISSING
281
+ };
282
+ }
283
+ return {
284
+ status: RemotePreferredTweakStatuses.AVAILABLE,
285
+ values: preferred
286
+ };
271
287
  }
272
288
  async getRemoteStatus(setting) {
273
289
  const testClient = new MinioStorageAdapter(setting, this.env);
@@ -1,8 +1,23 @@
1
1
  import type { RemoteDBStatus } from "../../LiveSyncAbstractReplicator.js";
2
2
  import type { BucketSyncSetting } from "../../../common/types.js";
3
+ export declare const JournalStorageReadStatuses: {
4
+ readonly AVAILABLE: "available";
5
+ readonly NOT_FOUND: "not-found";
6
+ readonly UNAVAILABLE: "unavailable";
7
+ };
8
+ export type JournalStorageReadResult<T> = {
9
+ status: typeof JournalStorageReadStatuses.AVAILABLE;
10
+ value: T;
11
+ } | {
12
+ status: typeof JournalStorageReadStatuses.NOT_FOUND;
13
+ } | {
14
+ status: typeof JournalStorageReadStatuses.UNAVAILABLE;
15
+ error: unknown;
16
+ };
3
17
  export interface IJournalStorage {
4
18
  upload(key: string, data: Uint8Array, mime: string): Promise<boolean>;
5
19
  download(key: string, ignoreCache?: boolean): Promise<Uint8Array | false>;
20
+ downloadWithResult(key: string, ignoreCache?: boolean): Promise<JournalStorageReadResult<Uint8Array>>;
6
21
  listFiles(from: string, limit?: number): Promise<string[]>;
7
22
  deleteFiles(keys: string[]): Promise<boolean>;
8
23
  isAvailable(): Promise<boolean>;
@@ -0,0 +1,8 @@
1
+ const JournalStorageReadStatuses = {
2
+ AVAILABLE: "available",
3
+ NOT_FOUND: "not-found",
4
+ UNAVAILABLE: "unavailable"
5
+ };
6
+ export {
7
+ JournalStorageReadStatuses
8
+ };
@@ -1,7 +1,7 @@
1
1
  import { S3 } from "@aws-sdk/client-s3";
2
2
  import { type BucketSyncSetting } from "../../../common/types.js";
3
3
  import type { RemoteDBStatus } from "../../LiveSyncAbstractReplicator.js";
4
- import type { IJournalStorage } from "./JournalStorageAdapter.js";
4
+ import { type IJournalStorage, type JournalStorageReadResult } from "./JournalStorageAdapter.js";
5
5
  import type { LiveSyncJournalReplicatorEnv } from "../LiveSyncJournalReplicatorEnv.js";
6
6
  export declare class MinioStorageAdapter implements IJournalStorage {
7
7
  _instance?: S3;
@@ -13,6 +13,7 @@ export declare class MinioStorageAdapter implements IJournalStorage {
13
13
  get customHeaders(): [string, string][];
14
14
  _getClient(): S3;
15
15
  upload(key: string, data: Uint8Array, mime: string): Promise<boolean>;
16
+ downloadWithResult(key: string, ignoreCache?: boolean): Promise<JournalStorageReadResult<Uint8Array>>;
16
17
  download(key: string, ignoreCache?: boolean): Promise<Uint8Array | false>;
17
18
  listFiles(from: string, limit?: number): Promise<string[]>;
18
19
  deleteFiles(keys: string[]): Promise<boolean>;
@@ -11,6 +11,9 @@ import { ConfiguredRetryStrategy } from "@smithy/util-retry";
11
11
  import { promiseWithResolvers } from "octagonal-wheels/promises";
12
12
  import { LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../../common/types.js";
13
13
  import { Logger } from "../../../common/logger.js";
14
+ import {
15
+ JournalStorageReadStatuses
16
+ } from "./JournalStorageAdapter.js";
14
17
  import { parseHeaderValues } from "../../../common/utils.js";
15
18
  import { runWithTrackedPhysicalRequest } from "../../../services/lib/remoteActivity.js";
16
19
  class MinioStorageAdapter {
@@ -110,7 +113,7 @@ class MinioStorageAdapter {
110
113
  }
111
114
  return false;
112
115
  }
113
- async download(key, ignoreCache = false) {
116
+ async downloadWithResult(key, ignoreCache = false) {
114
117
  const client = this._getClient();
115
118
  const cmd = new GetObjectCommand({
116
119
  Bucket: this._settings.bucket,
@@ -121,15 +124,31 @@ class MinioStorageAdapter {
121
124
  return await this.runTrackedRequest(async () => {
122
125
  const r = await client.send(cmd);
123
126
  if (r.Body) {
124
- return new Uint8Array(await r.Body.transformToByteArray());
127
+ return {
128
+ status: JournalStorageReadStatuses.AVAILABLE,
129
+ value: new Uint8Array(await r.Body.transformToByteArray())
130
+ };
125
131
  }
126
- return false;
132
+ return {
133
+ status: JournalStorageReadStatuses.UNAVAILABLE,
134
+ error: new Error(`The response body for ${key} was empty`)
135
+ };
127
136
  });
128
137
  } catch (ex) {
138
+ if (isMissingObjectError(ex)) {
139
+ return { status: JournalStorageReadStatuses.NOT_FOUND };
140
+ }
129
141
  Logger(`Could not download ${key}`);
130
142
  Logger(ex, LOG_LEVEL_VERBOSE);
143
+ return {
144
+ status: JournalStorageReadStatuses.UNAVAILABLE,
145
+ error: ex
146
+ };
131
147
  }
132
- return false;
148
+ }
149
+ async download(key, ignoreCache = false) {
150
+ const result = await this.downloadWithResult(key, ignoreCache);
151
+ return result.status === JournalStorageReadStatuses.AVAILABLE ? result.value : false;
133
152
  }
134
153
  async listFiles(from, limit) {
135
154
  const client = this._getClient();
@@ -199,6 +218,12 @@ class MinioStorageAdapter {
199
218
  }
200
219
  }
201
220
  }
221
+ function isMissingObjectError(error) {
222
+ if (!error || typeof error !== "object") return false;
223
+ const { name, Code, code } = error;
224
+ const errorCode = Code ?? code ?? name;
225
+ return errorCode === "NoSuchKey" || errorCode === "NotFound";
226
+ }
202
227
  export {
203
228
  MinioStorageAdapter
204
229
  };
@@ -1,4 +1,4 @@
1
- import { type RemoteDBSettings, type EntryLeaf, type TweakValues, type LOG_LEVEL, type NodeData } from "../../common/types.js";
1
+ import { type RemoteDBSettings, type EntryLeaf, type RemotePreferredTweakResult, type LOG_LEVEL, type NodeData } from "../../common/types.js";
2
2
  import { LiveSyncAbstractReplicator, type LiveSyncReplicatorEnv, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
3
3
  import { TrysteroReplicator } from "./TrysteroReplicator.js";
4
4
  import { P2PHost, type AcceptanceDecision, type RevokeAcceptanceDecision } from "./TrysteroReplicatorP2PServer.js";
@@ -81,7 +81,7 @@ export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicat
81
81
  setPreferredRemoteTweakSettings(_setting: RemoteDBSettings): Promise<void>;
82
82
  fetchRemoteChunks(_missingChunks: string[], _showResult: boolean): Promise<false | EntryLeaf[]>;
83
83
  getRemoteStatus(_setting: RemoteDBSettings): Promise<false | RemoteDBStatus>;
84
- getRemotePreferredTweakValues(_setting: RemoteDBSettings): Promise<false | TweakValues>;
84
+ getRemotePreferredTweakValues(_setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
85
85
  countCompromisedChunks(): Promise<number>;
86
86
  getConnectedDeviceList(_setting?: RemoteDBSettings): Promise<false | {
87
87
  node_info: Record<string, NodeData>;
@@ -1,5 +1,6 @@
1
1
  import { Logger } from "../../common/logger.js";
2
2
  import {
3
+ RemotePreferredTweakStatuses,
3
4
  LOG_LEVEL_NOTICE,
4
5
  LOG_LEVEL_INFO,
5
6
  LOG_LEVEL_VERBOSE,
@@ -369,7 +370,7 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
369
370
  "Trying to get tweak values but P2P replication does not support to do this. This operation has been ignored",
370
371
  LOG_LEVEL_INFO
371
372
  );
372
- return Promise.resolve(false);
373
+ return Promise.resolve({ status: RemotePreferredTweakStatuses.UNSUPPORTED });
373
374
  }
374
375
  countCompromisedChunks() {
375
376
  Logger("P2P Replicator cannot count compromised chunks", LOG_LEVEL_VERBOSE);
@@ -1,6 +1,6 @@
1
1
  import type { FetchHttpHandler } from "@smithy/fetch-http-handler";
2
2
  import { type LOG_LEVEL } from "octagonal-wheels/common/logger";
3
- import type { AnyEntry, AUTO_MERGED, CouchDBCredentials, diff_result, DocumentID, EntryDoc, EntryHasPath, FileEventItem, FilePath, FilePathWithPrefix, LoadedEntry, MetaEntry, MISSING_OR_ERROR, ObsidianLiveSyncSettings, RemoteDBSettings, SettingsMigrationState, TweakValues, UXFileInfo, UXFileInfoStub } from "../../common/types.js";
3
+ import type { AnyEntry, AUTO_MERGED, CouchDBCredentials, diff_result, DocumentID, EntryDoc, EntryHasPath, FileEventItem, FilePath, FilePathWithPrefix, LoadedEntry, MetaEntry, MISSING_OR_ERROR, ObsidianLiveSyncSettings, RemoteDBSettings, RemotePreferredTweakResult, SettingsMigrationState, TweakValues, UXFileInfo, UXFileInfoStub } from "../../common/types.js";
4
4
  import type { LiveSyncLocalDB } from "../../pouchdb/LiveSyncLocalDB.js";
5
5
  import type { LiveSyncAbstractReplicator } from "../../replication/LiveSyncAbstractReplicator.js";
6
6
  import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";
@@ -221,7 +221,7 @@ export interface ISettingService {
221
221
  deleteSmallConfig(key: string): void;
222
222
  }
223
223
  export interface ITweakValueService {
224
- fetchRemotePreferred(trialSetting: RemoteDBSettings): Promise<TweakValues | false>;
224
+ fetchRemotePreferred(trialSetting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
225
225
  checkAndAskResolvingMismatched(preferred: Partial<TweakValues>): Promise<[TweakValues | boolean, boolean]>;
226
226
  askResolvingMismatched(preferredSource: TweakValues): Promise<"OK" | "CHECKAGAIN" | "IGNORE">;
227
227
  checkAndAskUseRemoteConfiguration(settings: RemoteDBSettings): Promise<{
@@ -1,4 +1,4 @@
1
- import type { RemoteDBSettings, TweakValues } from "../../common/types.js";
1
+ import type { RemoteDBSettings, RemotePreferredTweakResult, TweakValues } from "../../common/types.js";
2
2
  import type { ITweakValueService } from "./IService.js";
3
3
  import { ServiceBase, type ServiceContext } from "./ServiceBase.js";
4
4
  /**
@@ -9,7 +9,7 @@ export declare abstract class TweakValueService<T extends ServiceContext = Servi
9
9
  * Fetch and trial the remote database settings to determine if they are preferred.
10
10
  * @param trialSetting The remote database settings to connect.
11
11
  */
12
- abstract fetchRemotePreferred(trialSetting: RemoteDBSettings): Promise<TweakValues | false>;
12
+ abstract fetchRemotePreferred(trialSetting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
13
13
  /**
14
14
  * Check and ask the user to resolve any mismatched tweak values.
15
15
  * @param preferred The preferred tweak values to check against.
@@ -1,7 +1,7 @@
1
1
  import type { ServiceContext } from "../../base/ServiceBase.js";
2
2
  import { TweakValueService } from "../../base/TweakValueService.js";
3
3
  export declare class InjectableTweakValueService<T extends ServiceContext> extends TweakValueService<T> {
4
- fetchRemotePreferred: import("../../lib/HandlerUtils.js").HandlerFunction<(trialSetting: import("../../../settings.js").RemoteDBSettings) => Promise<import("../../../common/types.js").TweakValues | false>, unknown>;
4
+ fetchRemotePreferred: import("../../lib/HandlerUtils.js").HandlerFunction<(trialSetting: import("../../../settings.js").RemoteDBSettings) => Promise<import("../../../common/types.js").RemotePreferredTweakResult>, unknown>;
5
5
  checkAndAskResolvingMismatched: import("../../lib/HandlerUtils.js").HandlerFunction<(preferred: Partial<import("../../../common/types.js").TweakValues>) => Promise<[import("../../../common/types.js").TweakValues | boolean, boolean]>, unknown>;
6
6
  askResolvingMismatched: import("../../lib/HandlerUtils.js").HandlerFunction<(preferredSource: import("../../../common/types.js").TweakValues) => Promise<"OK" | "CHECKAGAIN" | "IGNORE">, unknown>;
7
7
  checkAndAskUseRemoteConfiguration: import("../../lib/HandlerUtils.js").HandlerFunction<(settings: import("../../../settings.js").RemoteDBSettings) => Promise<{
@@ -20,6 +20,8 @@ Commonlib's chunk-reachability tests use real PouchDB revision trees. They verif
20
20
 
21
21
  Commonlib's injected CouchDB tests verify that a mixed remote chunk batch preserves available documents when another requested ID is absent. Its chunk-delivery tests separately verify that available chunks reach local storage and only absent IDs are marked unavailable. Self-hosted LiveSync supplies the real CouchDB connection and start-up composition, so device-specific replication timing remains downstream integration evidence rather than Commonlib unit coverage.
22
22
 
23
+ Commonlib's remote-preference result contract distinguishes available synchronisation settings, a remote which has not stored those settings yet, a failed read, and a remote type which does not support central preferences. Focused CouchDB and Object Storage tests cover absent milestone data separately from transport failures, while Self-hosted LiveSync owns the initialisation choices, cancellation policy, and user-facing explanation.
24
+
23
25
  Most replication and storage services used by the plug-in still enter through explicit `compat/*` paths. Those imports prove that the published compatibility boundary supports the current migration; they are not examples of a finished high-level client API.
24
26
 
25
27
  The plug-in's manual onboarding and Remote Databases pane use the focused `/remote-configurations` entry to create opaque profile IDs, retain user-visible names, and select main and P2P remotes. Commonlib owns the in-memory profile result contract; Self-hosted LiveSync adds dialogue, Setup URI classification, Fetch or Rebuild scheduling, persistence, restart ordering, and real-Obsidian presentation checks.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vrtmrz/livesync-commonlib",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Shared data, storage, and replication primitives for Self-hosted LiveSync clients.",
5
5
  "type": "module",
6
6
  "license": "MIT",