@vrtmrz/livesync-commonlib 0.1.0-rc.0 → 0.1.0-rc.10

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 (148) hide show
  1. package/README.md +101 -17
  2. package/dist/API/DirectFileManipulatorV2.d.ts +3 -0
  3. package/dist/common/configForDoc.d.ts +1 -0
  4. package/dist/common/configForDoc.js +24 -7
  5. package/dist/common/models/setting.const.defaults.d.ts +19 -0
  6. package/dist/common/models/setting.const.defaults.js +21 -2
  7. package/dist/common/models/setting.const.preferred.js +1 -2
  8. package/dist/common/models/setting.lifecycle.d.ts +33 -0
  9. package/dist/common/models/setting.lifecycle.js +121 -0
  10. package/dist/common/models/setting.type.d.ts +10 -4
  11. package/dist/common/models/shared.definition.configNames.js +3 -3
  12. package/dist/common/models/tweak.definition.d.ts +1 -1
  13. package/dist/common/models/tweak.definition.js +1 -6
  14. package/dist/common/settingConstants.d.ts +3 -2
  15. package/dist/common/settingConstants.js +6 -6
  16. package/dist/common/types.d.ts +4 -2
  17. package/dist/common/types.js +16 -1
  18. package/dist/context.d.ts +3 -1
  19. package/dist/context.js +4 -0
  20. package/dist/interfaces/Confirm.d.ts +10 -2
  21. package/dist/interfaces/DatabaseFileAccess.d.ts +8 -0
  22. package/dist/interfaces/DatabaseRebuilder.d.ts +12 -2
  23. package/dist/interfaces/FileReflectionProvenance.d.ts +32 -0
  24. package/dist/interfaces/FileReflectionProvenance.js +25 -0
  25. package/dist/managers/ConflictManager.js +16 -7
  26. package/dist/managers/EntryManager/EntryManager.d.ts +1 -0
  27. package/dist/managers/EntryManager/EntryManager.js +5 -1
  28. package/dist/managers/EntryManager/EntryManagerImpls.d.ts +1 -0
  29. package/dist/managers/EntryManager/EntryManagerImpls.js +29 -1
  30. package/dist/platform/node/index.d.ts +1 -0
  31. package/dist/platform/node/index.js +4 -0
  32. package/dist/platform/node/standardIo.d.ts +24 -0
  33. package/dist/platform/node/standardIo.js +39 -0
  34. package/dist/platform/node/storage.d.ts +10 -1
  35. package/dist/platform/node/storage.js +86 -25
  36. package/dist/platform/standardIo.d.ts +19 -0
  37. package/dist/platform/standardIo.js +0 -0
  38. package/dist/pouchdb/LiveSyncLocalDB.d.ts +1 -0
  39. package/dist/pouchdb/LiveSyncLocalDB.js +3 -0
  40. package/dist/remoteConfigurations.d.ts +9 -0
  41. package/dist/remoteConfigurations.js +14 -0
  42. package/dist/replication/LiveSyncAbstractReplicator.d.ts +3 -3
  43. package/dist/replication/couchdb/LiveSyncReplicator.d.ts +0 -1
  44. package/dist/replication/couchdb/LiveSyncReplicator.js +0 -3
  45. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +0 -1
  46. package/dist/replication/journal/LiveSyncJournalReplicator.js +0 -3
  47. package/dist/replication/trystero/LiveSyncTrysteroReplicator.d.ts +5 -3
  48. package/dist/replication/trystero/LiveSyncTrysteroReplicator.js +42 -30
  49. package/dist/replication/trystero/P2PReplicatorCore.d.ts +2 -6
  50. package/dist/replication/trystero/P2PReplicatorCore.js +16 -86
  51. package/dist/replication/trystero/TrysteroReplicator.d.ts +2 -1
  52. package/dist/replication/trystero/TrysteroReplicator.js +10 -4
  53. package/dist/replication/trystero/TrysteroReplicatorP2PServer.d.ts +4 -5
  54. package/dist/replication/trystero/TrysteroReplicatorP2PServer.js +22 -30
  55. package/dist/replication/trystero/UseP2PReplicatorResult.d.ts +10 -2
  56. package/dist/replication/trystero/addP2PEventHandlers.d.ts +4 -2
  57. package/dist/replication/trystero/addP2PEventHandlers.js +9 -10
  58. package/dist/replication/trystero/types.d.ts +2 -0
  59. package/dist/replication/trystero/useP2PReplicatorCommands.d.ts +1 -1
  60. package/dist/replication/trystero/useP2PReplicatorCommands.js +3 -1
  61. package/dist/replication/trystero/useP2PReplicatorFeature.js +25 -15
  62. package/dist/rpc/RpcRoom.d.ts +1 -1
  63. package/dist/rpc/RpcRoom.js +22 -16
  64. package/dist/rpc/transports/TrysteroTransport.d.ts +2 -0
  65. package/dist/rpc/transports/TrysteroTransport.js +24 -17
  66. package/dist/rpc/transports/trysteroRoomEvents.d.ts +14 -0
  67. package/dist/rpc/transports/trysteroRoomEvents.js +34 -0
  68. package/dist/serviceFeatures/checkRemoteSize.d.ts +5 -3
  69. package/dist/serviceFeatures/checkRemoteSize.js +51 -11
  70. package/dist/serviceFeatures/remoteConfig.d.ts +37 -1
  71. package/dist/serviceFeatures/remoteConfig.js +90 -0
  72. package/dist/serviceModules/Rebuilder.d.ts +3 -6
  73. package/dist/serviceModules/Rebuilder.js +31 -22
  74. package/dist/serviceModules/ServiceDatabaseFileAccessBase.d.ts +5 -0
  75. package/dist/serviceModules/ServiceDatabaseFileAccessBase.js +69 -13
  76. package/dist/serviceModules/ServiceFileHandlerBase.d.ts +20 -0
  77. package/dist/serviceModules/ServiceFileHandlerBase.js +224 -40
  78. package/dist/services/BrowserServices.d.ts +2 -0
  79. package/dist/services/BrowserServices.js +4 -1
  80. package/dist/services/HeadlessServices.d.ts +2 -0
  81. package/dist/services/HeadlessServices.js +2 -1
  82. package/dist/services/base/CommonlibMessages.d.ts +318 -0
  83. package/dist/services/base/CommonlibMessages.js +313 -0
  84. package/dist/services/base/IService.d.ts +13 -6
  85. package/dist/services/base/KeyValueDBService.d.ts +1 -1
  86. package/dist/services/base/KeyValueDBService.js +3 -1
  87. package/dist/services/base/MessageTranslator.d.ts +5 -2
  88. package/dist/services/base/MessageTranslator.js +21 -0
  89. package/dist/services/base/ReplicationService.d.ts +4 -2
  90. package/dist/services/base/ReplicationService.js +11 -16
  91. package/dist/services/base/ServiceBase.d.ts +1 -1
  92. package/dist/services/base/ServiceBase.js +2 -4
  93. package/dist/services/base/SettingService.d.ts +17 -9
  94. package/dist/services/base/SettingService.js +38 -19
  95. package/dist/services/implements/headless/HeadlessAPIService.d.ts +3 -3
  96. package/dist/services/implements/headless/HeadlessAPIService.js +2 -2
  97. package/dist/services/implements/injectable/InjectableTweakValueService.d.ts +3 -3
  98. package/dist/settings.d.ts +10 -0
  99. package/dist/settings.js +32 -0
  100. package/dist/worker/bgWorker.js +1 -1
  101. package/docs/conflict-resolution.md +153 -0
  102. package/docs/development.md +108 -8
  103. package/docs/p2p-transport-lifecycle.md +62 -0
  104. package/docs/platform-standard-io.md +44 -0
  105. package/docs/platform-storage.md +4 -0
  106. package/docs/proven-in-use.md +65 -0
  107. package/docs/releasing.md +26 -6
  108. package/docs/remote-configurations.md +63 -0
  109. package/docs/settings-lifecycle.md +85 -0
  110. package/package.json +17 -27
  111. package/dist/common/i18n.d.ts +0 -18
  112. package/dist/common/i18n.js +0 -7193
  113. package/dist/common/messages/combinedMessages.dev.d.ts +0 -7
  114. package/dist/common/messages/combinedMessages.dev.js +0 -44
  115. package/dist/common/messages/combinedMessages.prod.d.ts +0 -9264
  116. package/dist/common/messages/combinedMessages.prod.js +0 -9271
  117. package/dist/common/messages/de.d.ts +0 -297
  118. package/dist/common/messages/de.js +0 -7
  119. package/dist/common/messages/def.d.ts +0 -1119
  120. package/dist/common/messages/def.js +0 -7
  121. package/dist/common/messages/es.d.ts +0 -689
  122. package/dist/common/messages/es.js +0 -7
  123. package/dist/common/messages/fr.d.ts +0 -582
  124. package/dist/common/messages/fr.js +0 -7
  125. package/dist/common/messages/he.d.ts +0 -583
  126. package/dist/common/messages/he.js +0 -7
  127. package/dist/common/messages/ja.d.ts +0 -838
  128. package/dist/common/messages/ja.js +0 -7
  129. package/dist/common/messages/ko.d.ts +0 -800
  130. package/dist/common/messages/ko.js +0 -7
  131. package/dist/common/messages/ru.d.ts +0 -858
  132. package/dist/common/messages/ru.js +0 -7
  133. package/dist/common/messages/zh-tw.d.ts +0 -384
  134. package/dist/common/messages/zh-tw.js +0 -7
  135. package/dist/common/messages/zh.d.ts +0 -1093
  136. package/dist/common/messages/zh.js +0 -7
  137. package/dist/common/messagesJson/de.json +0 -295
  138. package/dist/common/messagesJson/en.json +0 -1117
  139. package/dist/common/messagesJson/es.json +0 -687
  140. package/dist/common/messagesJson/fr.json +0 -580
  141. package/dist/common/messagesJson/he.json +0 -581
  142. package/dist/common/messagesJson/ja.json +0 -836
  143. package/dist/common/messagesJson/ko.json +0 -798
  144. package/dist/common/messagesJson/ru.json +0 -856
  145. package/dist/common/messagesJson/zh-tw.json +0 -382
  146. package/dist/common/messagesJson/zh.json +0 -1091
  147. package/dist/common/rosetta.d.ts +0 -42
  148. package/dist/common/rosetta.js +0 -57
@@ -1,16 +1,40 @@
1
+ import { constants as nodeFsConstants } from "node:fs";
1
2
  import * as nodeFsPromises from "node:fs/promises";
2
3
  import * as nodePath from "node:path";
3
4
  import { validateStoragePath } from "../storagePath.js";
5
+ const NO_FOLLOW = nodeFsConstants.O_NOFOLLOW ?? 0;
6
+ class SymbolicLinkStoragePathError extends Error {
7
+ constructor(path) {
8
+ super(`Storage paths must not contain symbolic links: ${path}`);
9
+ this.name = "SymbolicLinkStoragePathError";
10
+ }
11
+ }
4
12
  class NodeStorageAdapter {
13
+ rootPath;
5
14
  constructor(rootPath) {
6
- this.rootPath = rootPath;
15
+ this.rootPath = nodePath.resolve(rootPath);
7
16
  }
8
- rootPath;
9
- resolvePath(path, allowRoot = true) {
10
- return nodePath.join(this.rootPath, validateStoragePath(path, allowRoot));
17
+ async resolvePath(path, allowRoot = true) {
18
+ const fullPath = nodePath.join(this.rootPath, validateStoragePath(path, allowRoot));
19
+ await this.assertNoSymbolicLinks(fullPath);
20
+ return fullPath;
21
+ }
22
+ async assertNoSymbolicLinks(fullPath) {
23
+ const relativePath = nodePath.relative(this.rootPath, fullPath);
24
+ let currentPath = this.rootPath;
25
+ for (const segment of relativePath.split(nodePath.sep).filter(Boolean)) {
26
+ currentPath = nodePath.join(currentPath, segment);
27
+ try {
28
+ const stat = await nodeFsPromises.lstat(currentPath);
29
+ if (stat.isSymbolicLink()) throw new SymbolicLinkStoragePathError(currentPath);
30
+ } catch (error) {
31
+ if (error.code === "ENOENT") return;
32
+ throw error;
33
+ }
34
+ }
11
35
  }
12
36
  async exists(path) {
13
- const fullPath = this.resolvePath(path);
37
+ const fullPath = await this.resolvePath(path);
14
38
  try {
15
39
  await nodeFsPromises.access(fullPath);
16
40
  return true;
@@ -20,14 +44,15 @@ class NodeStorageAdapter {
20
44
  }
21
45
  async trystat(path) {
22
46
  try {
23
- const stat = await nodeFsPromises.stat(this.resolvePath(path));
47
+ const stat = await nodeFsPromises.stat(await this.resolvePath(path));
24
48
  return {
25
49
  size: stat.size,
26
50
  mtime: Math.floor(stat.mtimeMs),
27
51
  ctime: Math.floor(stat.ctimeMs),
28
52
  type: stat.isDirectory() ? "folder" : "file"
29
53
  };
30
- } catch {
54
+ } catch (error) {
55
+ if (error instanceof SymbolicLinkStoragePathError) throw error;
31
56
  return null;
32
57
  }
33
58
  }
@@ -35,10 +60,12 @@ class NodeStorageAdapter {
35
60
  return await this.trystat(path);
36
61
  }
37
62
  async mkdir(path) {
38
- await nodeFsPromises.mkdir(this.resolvePath(path), { recursive: true });
63
+ const fullPath = await this.resolvePath(path);
64
+ await nodeFsPromises.mkdir(fullPath, { recursive: true });
65
+ await this.assertNoSymbolicLinks(fullPath);
39
66
  }
40
67
  async remove(path) {
41
- const fullPath = this.resolvePath(path, false);
68
+ const fullPath = await this.resolvePath(path, false);
42
69
  const stat = await nodeFsPromises.stat(fullPath);
43
70
  if (stat.isDirectory()) {
44
71
  await nodeFsPromises.rm(fullPath, { recursive: true, force: true });
@@ -47,33 +74,55 @@ class NodeStorageAdapter {
47
74
  }
48
75
  }
49
76
  async read(path) {
50
- return await nodeFsPromises.readFile(this.resolvePath(path, false), "utf-8");
77
+ const handle = await nodeFsPromises.open(
78
+ await this.resolvePath(path, false),
79
+ nodeFsConstants.O_RDONLY | NO_FOLLOW
80
+ );
81
+ try {
82
+ return await handle.readFile("utf-8");
83
+ } finally {
84
+ await handle.close();
85
+ }
51
86
  }
52
87
  async readBinary(path) {
53
- const buffer = await nodeFsPromises.readFile(this.resolvePath(path, false));
54
- return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
88
+ const handle = await nodeFsPromises.open(
89
+ await this.resolvePath(path, false),
90
+ nodeFsConstants.O_RDONLY | NO_FOLLOW
91
+ );
92
+ try {
93
+ const buffer = await handle.readFile();
94
+ return buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
95
+ } finally {
96
+ await handle.close();
97
+ }
55
98
  }
56
99
  async write(path, data, options) {
57
- const fullPath = this.resolvePath(path, false);
100
+ const fullPath = await this.resolvePath(path, false);
58
101
  await nodeFsPromises.mkdir(nodePath.dirname(fullPath), { recursive: true });
59
- await nodeFsPromises.writeFile(fullPath, data, "utf-8");
60
- await this.applyTimes(fullPath, options);
102
+ await this.writeFile(fullPath, data, options, false);
61
103
  }
62
104
  async writeBinary(path, data, options) {
63
- const fullPath = this.resolvePath(path, false);
105
+ const fullPath = await this.resolvePath(path, false);
64
106
  await nodeFsPromises.mkdir(nodePath.dirname(fullPath), { recursive: true });
65
- await nodeFsPromises.writeFile(fullPath, new Uint8Array(data));
66
- await this.applyTimes(fullPath, options);
107
+ await this.writeFile(fullPath, new Uint8Array(data), options, false);
67
108
  }
68
109
  async append(path, data, options) {
69
- const fullPath = this.resolvePath(path, false);
110
+ const fullPath = await this.resolvePath(path, false);
70
111
  await nodeFsPromises.mkdir(nodePath.dirname(fullPath), { recursive: true });
71
- await nodeFsPromises.appendFile(fullPath, data, "utf-8");
72
- await this.applyTimes(fullPath, options);
112
+ await this.writeFile(fullPath, data, options, true);
113
+ }
114
+ /** Atomically rename or move an entry without leaving the injected root. */
115
+ async rename(sourcePath, targetPath) {
116
+ const fullSourcePath = await this.resolvePath(sourcePath, false);
117
+ const fullTargetPath = await this.resolvePath(targetPath, false);
118
+ await nodeFsPromises.mkdir(nodePath.dirname(fullTargetPath), { recursive: true });
119
+ await this.assertNoSymbolicLinks(fullSourcePath);
120
+ await this.assertNoSymbolicLinks(fullTargetPath);
121
+ await nodeFsPromises.rename(fullSourcePath, fullTargetPath);
73
122
  }
74
123
  async list(basePath) {
75
124
  try {
76
- const entries = await nodeFsPromises.readdir(this.resolvePath(basePath), { withFileTypes: true });
125
+ const entries = await nodeFsPromises.readdir(await this.resolvePath(basePath), { withFileTypes: true });
77
126
  const files = [];
78
127
  const folders = [];
79
128
  for (const entry of entries) {
@@ -82,15 +131,27 @@ class NodeStorageAdapter {
82
131
  else if (entry.isFile()) files.push(entryPath);
83
132
  }
84
133
  return { files, folders };
85
- } catch {
134
+ } catch (error) {
135
+ if (error instanceof SymbolicLinkStoragePathError) throw error;
86
136
  return { files: [], folders: [] };
87
137
  }
88
138
  }
89
- async applyTimes(fullPath, options) {
139
+ async writeFile(fullPath, data, options, append) {
140
+ await this.assertNoSymbolicLinks(fullPath);
141
+ const flags = nodeFsConstants.O_WRONLY | nodeFsConstants.O_CREAT | NO_FOLLOW | (append ? nodeFsConstants.O_APPEND : nodeFsConstants.O_TRUNC);
142
+ const handle = await nodeFsPromises.open(fullPath, flags, 438);
143
+ try {
144
+ await handle.writeFile(data, typeof data === "string" ? "utf-8" : void 0);
145
+ await this.applyTimes(handle, options);
146
+ } finally {
147
+ await handle.close();
148
+ }
149
+ }
150
+ async applyTimes(handle, options) {
90
151
  if (!options?.mtime && !options?.ctime) return;
91
152
  const accessTime = options.mtime ? new Date(options.mtime) : /* @__PURE__ */ new Date();
92
153
  const modificationTime = options.mtime ? new Date(options.mtime) : /* @__PURE__ */ new Date();
93
- await nodeFsPromises.utimes(fullPath, accessTime, modificationTime);
154
+ await handle.utimes(accessTime, modificationTime);
94
155
  }
95
156
  }
96
157
  function createNodeStorage({ rootPath }) {
@@ -0,0 +1,19 @@
1
+ /** A chunk written to standard output or standard error. */
2
+ export type StandardIoChunk = string | Uint8Array;
3
+ /**
4
+ * Host-provided standard input and output for a command-line composition.
5
+ *
6
+ * The contract deliberately excludes process lifecycle, logging policy, and
7
+ * terminal capability detection. Hosts own those concerns separately. None of
8
+ * the write operations add a newline implicitly.
9
+ */
10
+ export interface StandardIo {
11
+ /** Read standard input to completion and decode it as UTF-8 text. */
12
+ readStdin(): Promise<string>;
13
+ /** Ask one line-oriented question and return the entered text. */
14
+ prompt(question: string): Promise<string>;
15
+ /** Write a text or binary chunk to standard output. */
16
+ writeStdout(chunk: StandardIoChunk): void;
17
+ /** Write a text or binary chunk to standard error. */
18
+ writeStderr(chunk: StandardIoChunk): void;
19
+ }
File without changes
@@ -190,6 +190,7 @@ export declare class LiveSyncLocalDB {
190
190
  getDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean): Promise<false | LoadedEntry>;
191
191
  getDBEntryFromMeta(meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean): Promise<false | LoadedEntry>;
192
192
  deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
193
+ storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
193
194
  putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
194
195
  getConflictedDoc(path: FilePathWithPrefix, rev: string): Promise<false | diff_result_leaf>;
195
196
  tryAutoMerge(path: FilePathWithPrefix, enableMarkdownAutoMerge: boolean): AutoMergeResult;
@@ -364,6 +364,9 @@ class LiveSyncLocalDB {
364
364
  async deleteDBEntry(path, opt) {
365
365
  return await this.managers.entryManager.deleteDBEntry(path, opt);
366
366
  }
367
+ async storeDeletionAtRevision(path, baseRevision) {
368
+ return await this.managers.entryManager.storeDeletionAtRevision(path, baseRevision);
369
+ }
367
370
  async putDBEntry(note, onlyChunks, conflictBaseRev) {
368
371
  return await this.managers.entryManager.putDBEntry(note, onlyChunks, conflictBaseRev);
369
372
  }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Focused public primitives for storing and selecting multiple remote connection profiles.
3
+ *
4
+ * The compatibility fields remain available to existing hosts, but new profile-management code
5
+ * should use this entry point instead of importing the wider service feature module.
6
+ */
7
+ export { activateP2PRemoteConfiguration, activateRemoteConfiguration, createRemoteConfigurationId, suggestRemoteConfigurationName, upsertRemoteConfigurationInPlace, } from "./serviceFeatures/remoteConfig.js";
8
+ export type { SerializableRemoteConfigurationType, UpsertRemoteConfigurationOptions, } from "./serviceFeatures/remoteConfig.js";
9
+ export type { RemoteConfiguration } from "./common/models/setting.type.js";
@@ -0,0 +1,14 @@
1
+ import {
2
+ activateP2PRemoteConfiguration,
3
+ activateRemoteConfiguration,
4
+ createRemoteConfigurationId,
5
+ suggestRemoteConfigurationName,
6
+ upsertRemoteConfigurationInPlace
7
+ } from "./serviceFeatures/remoteConfig.js";
8
+ export {
9
+ activateP2PRemoteConfiguration,
10
+ activateRemoteConfiguration,
11
+ createRemoteConfigurationId,
12
+ suggestRemoteConfigurationName,
13
+ upsertRemoteConfigurationInPlace
14
+ };
@@ -1,6 +1,7 @@
1
1
  import { type EntryDoc, type DatabaseConnectingStatus, type RemoteDBSettings, type EntryLeaf, type TweakValues, 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
+ import type { CommonlibMessageKey } from "../services/base/CommonlibMessages.js";
4
5
  export type ReplicationCallback = (e: PouchDB.Core.ExistingDocument<EntryDoc>[]) => Promise<boolean> | boolean;
5
6
  export type ReplicationStat = {
6
7
  sent: number;
@@ -34,11 +35,10 @@ export declare abstract class LiveSyncAbstractReplicator {
34
35
  remoteLockedAndDeviceNotAccepted: boolean;
35
36
  tweakSettingsMismatched: boolean;
36
37
  preferredTweakValue?: TweakValues;
37
- abstract get isChunkSendingSupported(): boolean;
38
38
  get database(): import("../pouchdb/LiveSyncLocalDB.js").LiveSyncLocalDB;
39
39
  get rawDatabase(): PouchDB.Database<EntryDoc>;
40
40
  get currentSettings(): import("../common/types.js").ObsidianLiveSyncSettings;
41
- protected translate<TKey extends string>(key: TKey, params?: TranslationParameters): string;
41
+ protected translate(key: CommonlibMessageKey, params?: TranslationParameters): string;
42
42
  sendChunks(setting: RemoteDBSettings, remoteDB: PouchDB.Database<EntryDoc> | undefined, showResult: boolean, fromSeq?: number | string): Promise<boolean>;
43
43
  abstract getReplicationPBKDF2Salt(setting: RemoteDBSettings, refresh?: boolean): Promise<Uint8Array<ArrayBuffer>>;
44
44
  ensurePBKDF2Salt(setting: RemoteDBSettings, showMessage?: boolean, useCache?: boolean): Promise<boolean>;
@@ -49,7 +49,7 @@ export declare abstract class LiveSyncAbstractReplicator {
49
49
  abstract openReplication(setting: RemoteDBSettings, keepAlive: boolean, showResult: boolean, ignoreCleanLock: boolean): Promise<void | boolean>;
50
50
  updateInfo: () => void;
51
51
  abstract tryConnectRemote(setting: RemoteDBSettings, showResult?: boolean): Promise<boolean>;
52
- abstract replicateAllToServer(setting: RemoteDBSettings, showingNotice?: boolean, sendChunksInBulkDisabled?: boolean): Promise<boolean>;
52
+ abstract replicateAllToServer(setting: RemoteDBSettings, showingNotice?: boolean): Promise<boolean>;
53
53
  abstract replicateAllFromServer(setting: RemoteDBSettings, showingNotice?: boolean): Promise<boolean>;
54
54
  abstract closeReplication(): void;
55
55
  abstract tryResetRemoteDatabase(setting: RemoteDBSettings): Promise<void>;
@@ -5,7 +5,6 @@ export interface LiveSyncCouchDBReplicatorEnv extends LiveSyncReplicatorEnv {
5
5
  services: ServiceHub;
6
6
  }
7
7
  export declare class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
8
- get isChunkSendingSupported(): boolean;
9
8
  isMobile(): boolean;
10
9
  constructor(env: LiveSyncCouchDBReplicatorEnv);
11
10
  getInitialSyncParameters(setting: RemoteDBSettings): Promise<SyncParameters>;
@@ -85,9 +85,6 @@ async function* genReplication(s, signal) {
85
85
  }
86
86
  }
87
87
  class LiveSyncCouchDBReplicator extends LiveSyncAbstractReplicator {
88
- get isChunkSendingSupported() {
89
- return true;
90
- }
91
88
  isMobile() {
92
89
  return this.env.services.API.isMobile();
93
90
  }
@@ -7,7 +7,6 @@ import { type SimpleStore } from "../../common/utils.js";
7
7
  import type { LiveSyncJournalReplicatorEnv } from "./LiveSyncJournalReplicatorEnv.js";
8
8
  export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
9
9
  env: LiveSyncJournalReplicatorEnv;
10
- get isChunkSendingSupported(): boolean;
11
10
  get client(): JournalSyncCore;
12
11
  get simpleStore(): SimpleStore<CheckPointInfo>;
13
12
  _client: JournalSyncCore;
@@ -19,9 +19,6 @@ const currentVersionRange = {
19
19
  current: 2
20
20
  };
21
21
  class LiveSyncJournalReplicator extends LiveSyncAbstractReplicator {
22
- get isChunkSendingSupported() {
23
- return false;
24
- }
25
22
  get client() {
26
23
  return this.setupJournalSyncClient();
27
24
  }
@@ -18,13 +18,15 @@ export interface LiveSyncTrysteroReplicatorEnv extends LiveSyncReplicatorEnv {
18
18
  export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
19
19
  private _p2pHost?;
20
20
  private _replicator?;
21
+ private _lifecycleOperation;
22
+ private _shouldBeOpen;
21
23
  get openReplicationUI(): (showResult: boolean) => Promise<boolean | void>;
22
24
  get rawReplicator(): TrysteroReplicator;
23
25
  get rawHost(): P2PHost;
24
- get isChunkSendingSupported(): boolean;
25
26
  getReplicationPBKDF2Salt(_setting: RemoteDBSettings, _refresh?: boolean): Promise<Uint8Array<ArrayBuffer>>;
26
27
  terminateSync(): void;
27
28
  private _buildEnv;
29
+ private _enqueueLifecycleOperation;
28
30
  open(): Promise<void>;
29
31
  close(): Promise<void>;
30
32
  closeReplication(): void;
@@ -36,7 +38,7 @@ export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicat
36
38
  onNewPeer(peer: Advertisement): Promise<void>;
37
39
  onPeerLeaved(peerId: string): void;
38
40
  replicateFromCommand(showResult?: boolean): Promise<void>;
39
- replicateFrom(peerId: string, showNotice?: boolean): Promise<{
41
+ replicateFrom(peerId: string, showNotice?: boolean, skipOrdinaryReplicationPolicy?: boolean): Promise<{
40
42
  error: any;
41
43
  ok?: undefined;
42
44
  } | {
@@ -67,7 +69,7 @@ export declare class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicat
67
69
  makeSureOpened(): Promise<void>;
68
70
  openReplication(_setting: RemoteDBSettings, _keepAlive: boolean, showResult: boolean, _ignoreCleanLock: boolean): Promise<void | boolean>;
69
71
  tryConnectRemote(_setting: RemoteDBSettings, _showResult?: boolean): Promise<boolean>;
70
- replicateAllToServer(_setting: RemoteDBSettings, _showingNotice?: boolean, _sendChunksInBulkDisabled?: boolean): Promise<boolean>;
72
+ replicateAllToServer(_setting: RemoteDBSettings, _showingNotice?: boolean): Promise<boolean>;
71
73
  selectPeer(settingPeerName: string, r: TrysteroReplicator, logLevel: LOG_LEVEL): Promise<string | false>;
72
74
  tryUntilSuccess<T>(func: () => Promise<T | false>, repeat: number, logLevel: LOG_LEVEL): Promise<T | false>;
73
75
  replicateAllFromServer(setting: RemoteDBSettings, showingNotice?: boolean): Promise<boolean>;
@@ -18,6 +18,8 @@ import { delay } from "octagonal-wheels/promises";
18
18
  class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
19
19
  _p2pHost;
20
20
  _replicator;
21
+ _lifecycleOperation = Promise.resolve();
22
+ _shouldBeOpen = false;
21
23
  get openReplicationUI() {
22
24
  return this.env.openReplicationUI;
23
25
  }
@@ -27,9 +29,6 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
27
29
  get rawHost() {
28
30
  return this._p2pHost;
29
31
  }
30
- get isChunkSendingSupported() {
31
- return false;
32
- }
33
32
  getReplicationPBKDF2Salt(_setting, _refresh) {
34
33
  return Promise.resolve(new Uint8Array(32));
35
34
  }
@@ -59,6 +58,7 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
59
58
  return services.API.confirm;
60
59
  },
61
60
  runFiniteReplicationActivity: (task, options) => services.replicator.runFiniteReplicationActivity(task, options),
61
+ canStartOrdinaryReplication: (showMessage = false) => services.replication.onCheckReplicationReady(showMessage),
62
62
  processReplicatedDocs: async (docs) => {
63
63
  const settings = services.setting.currentSettings();
64
64
  if (settings.suspendParseReplicationResult) {
@@ -75,36 +75,48 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
75
75
  }
76
76
  };
77
77
  }
78
+ _enqueueLifecycleOperation(operation) {
79
+ const queued = this._lifecycleOperation.catch(() => void 0).then(operation);
80
+ this._lifecycleOperation = queued.catch(() => void 0);
81
+ return queued;
82
+ }
78
83
  async open() {
79
84
  if (!this.env.services.setting.currentSettings().P2P_Enabled) {
80
85
  Logger(this.translate("P2P.NotEnabled"), LOG_LEVEL_NOTICE);
81
86
  return;
82
87
  }
83
- if (this._replicator && this._p2pHost?.isServing) {
84
- Logger("P2P replicator is already open.");
85
- return;
86
- }
87
- try {
88
- const env = this._buildEnv();
89
- const host = new P2PHost(env);
90
- const replicator = new TrysteroReplicator(env, host);
91
- this._p2pHost = host;
92
- this._replicator = replicator;
93
- await replicator.open();
94
- } catch (e) {
95
- Logger(e instanceof Error ? e.message : "Error while opening P2P connection", LOG_LEVEL_NOTICE);
96
- Logger(e, LOG_LEVEL_VERBOSE);
97
- this._p2pHost = void 0;
98
- this._replicator = void 0;
99
- }
88
+ this._shouldBeOpen = true;
89
+ await this._enqueueLifecycleOperation(async () => {
90
+ if (!this._shouldBeOpen) return;
91
+ if (this._replicator && this._p2pHost?.isServing) {
92
+ Logger("P2P replicator is already open.");
93
+ return;
94
+ }
95
+ try {
96
+ const env = this._buildEnv();
97
+ const host = new P2PHost(env);
98
+ const replicator = new TrysteroReplicator(env, host);
99
+ this._p2pHost = host;
100
+ this._replicator = replicator;
101
+ await replicator.open();
102
+ } catch (e) {
103
+ Logger(e instanceof Error ? e.message : "Error while opening P2P connection", LOG_LEVEL_NOTICE);
104
+ Logger(e, LOG_LEVEL_VERBOSE);
105
+ this._p2pHost = void 0;
106
+ this._replicator = void 0;
107
+ }
108
+ });
100
109
  }
101
110
  async close() {
102
- if (this._replicator) {
103
- this._replicator.disableBroadcastChanges();
104
- await this._replicator.close();
105
- this._replicator = void 0;
106
- }
107
- this._p2pHost = void 0;
111
+ this._shouldBeOpen = false;
112
+ await this._enqueueLifecycleOperation(async () => {
113
+ if (this._replicator) {
114
+ this._replicator.disableBroadcastChanges();
115
+ await this._replicator.close();
116
+ this._replicator = void 0;
117
+ }
118
+ this._p2pHost = void 0;
119
+ });
108
120
  }
109
121
  closeReplication() {
110
122
  this._replicator?.disconnectFromServer();
@@ -138,11 +150,11 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
138
150
  { label: "replication" }
139
151
  );
140
152
  }
141
- async replicateFrom(peerId, showNotice = false) {
153
+ async replicateFrom(peerId, showNotice = false, skipOrdinaryReplicationPolicy = false) {
142
154
  const replicator = this._replicator;
143
155
  if (!replicator) throw new Error("P2P replicator is not open");
144
156
  return await this.env.services.replicator.runFiniteReplicationActivity(
145
- () => replicator.replicateFrom(peerId, showNotice),
157
+ () => skipOrdinaryReplicationPolicy ? replicator.replicateFrom(peerId, showNotice, false, true) : replicator.replicateFrom(peerId, showNotice),
146
158
  { label: "replication" }
147
159
  );
148
160
  }
@@ -200,7 +212,7 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
200
212
  tryConnectRemote(_setting, _showResult) {
201
213
  return Promise.resolve(false);
202
214
  }
203
- replicateAllToServer(_setting, _showingNotice, _sendChunksInBulkDisabled) {
215
+ replicateAllToServer(_setting, _showingNotice) {
204
216
  return Promise.resolve(false);
205
217
  }
206
218
  async selectPeer(settingPeerName, r, logLevel) {
@@ -303,7 +315,7 @@ class LiveSyncTrysteroReplicator extends LiveSyncAbstractReplicator {
303
315
  }
304
316
  this.env.services.setting.currentSettings().P2P_RebuildFrom = "";
305
317
  Logger("Fetching from peer " + peerId + ".", logLevel);
306
- const rep = await r.replicateFrom(peerId, showingNotice);
318
+ const rep = await r.replicateFrom(peerId, showingNotice, false, true);
307
319
  if (rep.ok) {
308
320
  Logger("P2P Fetching has been succeed from " + peerId + ".", logLevel);
309
321
  return true;
@@ -2,11 +2,7 @@ import type { NecessaryServices } from "../../interfaces/ServiceModule.js";
2
2
  import type { P2PPaneParams } from "./UseP2PReplicatorResult.js";
3
3
  export type P2PViewFactory = (leaf: unknown) => unknown;
4
4
  /**
5
- * ServiceFeature: P2P Replicator lifecycle management.
6
- * Binds a LiveSyncTrysteroReplicator to the host's lifecycle events,
7
- * following the same middleware style as useOfflineScanner.
8
- *
9
- * @param viewTypeAndFactory Optional [viewType, factory] pair for registering the P2P pane view.
10
- * When provided, also registers commands and ribbon icon via services.API.
5
+ * @deprecated Compose `useP2PReplicatorFeature` with host-specific commands
6
+ * and UI instead. This wrapper remains for source compatibility.
11
7
  */
12
8
  export declare function useP2PReplicator(host: NecessaryServices<"API" | "appLifecycle" | "setting" | "vault" | "database" | "databaseEvents" | "keyValueDB" | "replication" | "config" | "UI" | "replicator" | "remote", never>, viewTypeAndFactory?: [viewType: string, factory: P2PViewFactory]): P2PPaneParams;
@@ -1,110 +1,34 @@
1
- import { AutoAccepting, REMOTE_P2P } from "../../common/types.js";
2
- import { reactiveSource } from "octagonal-wheels/dataobject/reactive";
1
+ import { REMOTE_P2P } from "../../common/types.js";
2
+ import { reactiveSource } from "octagonal-wheels/dataobject/reactive_v2";
3
3
  import { EVENT_REQUEST_OPEN_P2P } from "../../events/coreEvents.js";
4
- import { LiveSyncTrysteroReplicator } from "./LiveSyncTrysteroReplicator.js";
5
- import { Logger, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE } from "../../common/logger.js";
6
4
  import { P2PLogCollector } from "./P2PLogCollector.js";
7
- import { addP2PEventHandlers } from "./addP2PEventHandlers.js";
8
- import { compatGlobal } from "../../common/coreEnvFunctions.js";
5
+ import { useP2PReplicatorCommands } from "./useP2PReplicatorCommands.js";
6
+ import { useP2PReplicatorFeature } from "./useP2PReplicatorFeature.js";
9
7
  function useP2PReplicator(host, viewTypeAndFactory) {
10
- const env = { services: host.services };
11
- let replicator = new LiveSyncTrysteroReplicator(env);
12
- const activeReplicator = {
13
- get instance() {
14
- return replicator;
15
- }
16
- };
8
+ const activeReplicator = useP2PReplicatorFeature(host);
17
9
  const events = host.services.context.events;
18
- addP2PEventHandlers(activeReplicator.instance, events);
19
10
  const p2pLogCollector = new P2PLogCollector(events);
20
11
  const storeP2PStatusLine = reactiveSource("");
21
12
  p2pLogCollector.p2pReplicationLine.onChanged((line) => {
22
13
  storeP2PStatusLine.value = line.value;
23
14
  });
24
- host.services.appLifecycle.onResumed.addHandler(() => {
25
- const settings = host.services.setting.currentSettings();
26
- if (settings.P2P_Enabled && settings.P2P_AutoStart) {
27
- compatGlobal.setTimeout(() => void replicator.open(), 100);
28
- }
29
- return Promise.resolve(true);
30
- });
31
- host.services.appLifecycle.onUnload.addHandler(async () => {
32
- await replicator.close();
33
- return true;
34
- });
35
- host.services.appLifecycle.onSuspending.addHandler(async () => {
36
- await replicator.close();
37
- return true;
38
- });
39
- host.services.databaseEvents.onDatabaseInitialisation.addHandler(async () => {
40
- await replicator.close();
41
- return true;
42
- });
43
- host.services.setting.suspendExtraSync.addHandler(() => {
44
- const s = host.services.setting.currentSettings();
45
- if (s.remoteType !== REMOTE_P2P) {
46
- s.P2P_Enabled = false;
47
- }
48
- s.P2P_AutoAccepting = AutoAccepting.NONE;
49
- s.P2P_AutoBroadcast = false;
50
- s.P2P_AutoStart = false;
51
- s.P2P_AutoSyncPeers = "";
52
- s.P2P_AutoWatchPeers = "";
53
- return Promise.resolve(true);
54
- });
55
- host.services.replicator.getNewReplicator.addHandler(
56
- async (settingOverride = {}) => {
57
- const settings = { ...host.services.setting.currentSettings(), ...settingOverride };
58
- if (settings.remoteType == REMOTE_P2P) {
59
- try {
60
- await replicator.close();
61
- } catch (e) {
62
- Logger(`Error closing existing p2p replicator`);
63
- Logger(e, LOG_LEVEL_VERBOSE);
64
- }
65
- const newReplicator = new LiveSyncTrysteroReplicator({ services: host.services });
66
- replicator = newReplicator;
67
- return Promise.resolve(replicator);
68
- }
69
- return void 0;
70
- }
71
- );
72
15
  if (viewTypeAndFactory) {
73
16
  const [viewType, factory] = viewTypeAndFactory;
74
17
  const openPane = () => host.services.API.showWindow(viewType);
18
+ useP2PReplicatorCommands(host, activeReplicator);
75
19
  host.services.appLifecycle.onInitialise.addHandler(() => {
76
20
  host.services.API.registerWindow(viewType, factory);
77
- events.onEvent(EVENT_REQUEST_OPEN_P2P, () => {
78
- void openPane();
79
- });
21
+ events.onEvent(EVENT_REQUEST_OPEN_P2P, () => void openPane());
80
22
  host.services.API.addCommand({
81
23
  id: "open-p2p-replicator",
82
24
  name: "P2P Sync : Open P2P Replicator",
83
- callback: () => {
84
- void openPane();
85
- }
86
- });
87
- host.services.API.addCommand({
88
- id: "p2p-establish-connection",
89
- name: "P2P Sync : Connect to the Signalling Server",
90
- checkCallback: (isChecking) => {
91
- if (isChecking) return !(replicator.server?.isServing ?? false);
92
- void replicator.open();
93
- }
94
- });
95
- host.services.API.addCommand({
96
- id: "p2p-close-connection",
97
- name: "P2P Sync : Disconnect from the Signalling Server",
98
- checkCallback: (isChecking) => {
99
- if (isChecking) return replicator.server?.isServing ?? false;
100
- Logger("Closing P2P Connection", LOG_LEVEL_NOTICE);
101
- void replicator.close();
102
- }
25
+ callback: () => void openPane()
103
26
  });
104
27
  host.services.API.addCommand({
105
28
  id: "replicate-now-by-p2p",
106
29
  name: "Replicate now by P2P",
107
30
  checkCallback: (isChecking) => {
31
+ const replicator = activeReplicator.replicator;
108
32
  const settings = host.services.setting.currentSettings();
109
33
  if (isChecking) {
110
34
  if (settings.remoteType == REMOTE_P2P) return false;
@@ -119,7 +43,13 @@ function useP2PReplicator(host, viewTypeAndFactory) {
119
43
  return Promise.resolve(true);
120
44
  });
121
45
  }
122
- return { replicator, p2pLogCollector, storeP2PStatusLine };
46
+ return {
47
+ get replicator() {
48
+ return activeReplicator.replicator;
49
+ },
50
+ p2pLogCollector,
51
+ storeP2PStatusLine
52
+ };
123
53
  }
124
54
  export {
125
55
  useP2PReplicator
@@ -64,6 +64,7 @@ export declare class TrysteroReplicator {
64
64
  get confirm(): Confirm;
65
65
  get translate(): import("../../context.js").MessageTranslator;
66
66
  private runFiniteReplicationActivity;
67
+ private canStartOrdinaryReplication;
67
68
  constructor(env: ReplicatorHostEnv, server?: P2PHost);
68
69
  close(): Promise<void>;
69
70
  open(): Promise<void>;
@@ -123,7 +124,7 @@ export declare class TrysteroReplicator {
123
124
  onReplicationProgress(peerId: string, info?: ProgressInfo): boolean;
124
125
  onProgressAcknowledged(peerId: string, info?: ProgressInfo): boolean;
125
126
  acknowledgeProgress(remotePeerId: string, info?: ProgressInfo): void;
126
- replicateFrom(remotePeer: string, showNotice?: boolean, fromStart?: boolean): Promise<{
127
+ replicateFrom(remotePeer: string, showNotice?: boolean, fromStart?: boolean, skipOrdinaryReplicationPolicy?: boolean): Promise<{
127
128
  error: any;
128
129
  ok?: undefined;
129
130
  } | {