@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,4 +1,4 @@
1
- import { $t } from "./i18n.js";
1
+ import { englishMessageTranslator } from "../services/base/MessageTranslator.js";
2
2
  import {
3
3
  DEFAULT_SETTINGS,
4
4
  configurationNames
@@ -324,12 +324,12 @@ const SettingInformation = {
324
324
  desc: "If this enabled, All files are handled as case-Sensitive (Previous behaviour)."
325
325
  },
326
326
  doNotUseFixedRevisionForChunks: {
327
- name: "Compute revisions for chunks",
328
- desc: "If this enabled, all chunks will be stored with the revision made from its content."
327
+ name: "Content-derived chunk revisions (obsolete setting)",
328
+ desc: "Chunk revisions are always derived from their content. This stored key is retained only for compatibility."
329
329
  },
330
330
  sendChunksBulkMaxSize: {
331
- name: "Maximum size of chunks to send in one request",
332
- desc: "MB"
331
+ name: "Maximum request size for manually resending chunks",
332
+ desc: "MB per request"
333
333
  },
334
334
  useAdvancedMode: {
335
335
  name: "Enable advanced features"
@@ -399,7 +399,7 @@ const SettingInformation = {
399
399
  desc: "Files with modification times greater than this value (in seconds since the Unix epoch) will not have their events reflected. Set to 0 to disable this limit."
400
400
  }
401
401
  };
402
- function translateInfo(infoSrc, translate = (message) => $t(message)) {
402
+ function translateInfo(infoSrc, translate = englishMessageTranslator) {
403
403
  if (!infoSrc) return false;
404
404
  const info = { ...infoSrc };
405
405
  info.name = translate(info.name);
@@ -10,7 +10,8 @@ import { IDPrefixes, LEAF_WAIT_ONLY_REMOTE, LEAF_WAIT_TIMEOUT, LEAF_WAIT_TIMEOUT
10
10
  import { AutoAccepting, type BucketSyncSetting, type ChunkSplitterVersion, type ConfigPassphraseStore, type CouchDBConnection, type E2EEAlgorithm, type EncryptionSettings, type HashAlgorithm, type HasSettings, type LocalDBSettings, type ObsidianLiveSyncSettings, type P2PConnectionInfo, type P2PSyncSetting, type PluginSyncSettingEntry, type RemoteDBSettings, type RemoteType, type RemoteTypeSettings, type SYNC_MODE } from "./models/setting.type.js";
11
11
  import { ChunkAlgorithmNames, ChunkAlgorithms, CURRENT_SETTING_VERSION, E2EEAlgorithmNames, E2EEAlgorithms, HashAlgorithms, REMOTE_COUCHDB, REMOTE_MINIO, REMOTE_P2P, RemoteTypes, SETTING_VERSION_INITIAL, SETTING_VERSION_SUPPORT_CASE_INSENSITIVE, MODE_AUTOMATIC, MODE_PAUSED, MODE_SELECTIVE, MODE_SHINY } from "./models/setting.const.js";
12
12
  import { PREFERRED_BASE, PREFERRED_JOURNAL_SYNC, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED } from "./models/setting.const.preferred.js";
13
- import { P2P_DEFAULT_SETTINGS, DEFAULT_SETTINGS } from "./models/setting.const.defaults.js";
13
+ import { P2P_DEFAULT_SETTINGS, DEFAULT_SETTINGS, NEW_VAULT_SETTINGS, SETTINGS_SCHEMA_DEFAULTS, createNewVaultSettings } from "./models/setting.const.defaults.js";
14
+ import { prepareSettingsForLoad, SettingsMigrationReviewCodes, type PreparedSettings, type SettingsMigrationReviewCode, type SettingsMigrationReviewReason, type SettingsMigrationState } from "./models/setting.lifecycle.js";
14
15
  import { KeyIndexOfSettings } from "./models/setting.const.qr.js";
15
16
  import type { DeviceInfo, EntryBody, EntryDoc, EntryDocResponse, EntryMilestoneInfo, EntryNodeInfo, NodeData, NodeKey } from "./models/db.definition.js";
16
17
  import { isMetaEntry } from "./models/db.definition.js";
@@ -42,7 +43,8 @@ export type { EncryptionSettings };
42
43
  export { HashAlgorithms, type HashAlgorithm, ChunkAlgorithmNames, ChunkAlgorithms, type ChunkSplitterVersion };
43
44
  export type { RemoteDBSettings };
44
45
  export type { ObsidianLiveSyncSettings };
45
- export { DEFAULT_SETTINGS };
46
+ export { DEFAULT_SETTINGS, NEW_VAULT_SETTINGS, SETTINGS_SCHEMA_DEFAULTS, createNewVaultSettings };
47
+ export { prepareSettingsForLoad, SettingsMigrationReviewCodes, type PreparedSettings, type SettingsMigrationReviewCode, type SettingsMigrationReviewReason, type SettingsMigrationState, };
46
48
  export { KeyIndexOfSettings };
47
49
  export { type HasSettings };
48
50
  export { PREFERRED_BASE, PREFERRED_SETTING_CLOUDANT, PREFERRED_SETTING_SELF_HOSTED, PREFERRED_JOURNAL_SYNC };
@@ -67,7 +67,17 @@ import {
67
67
  PREFERRED_SETTING_CLOUDANT,
68
68
  PREFERRED_SETTING_SELF_HOSTED
69
69
  } from "./models/setting.const.preferred.js";
70
- import { P2P_DEFAULT_SETTINGS, DEFAULT_SETTINGS } from "./models/setting.const.defaults.js";
70
+ import {
71
+ P2P_DEFAULT_SETTINGS,
72
+ DEFAULT_SETTINGS,
73
+ NEW_VAULT_SETTINGS,
74
+ SETTINGS_SCHEMA_DEFAULTS,
75
+ createNewVaultSettings
76
+ } from "./models/setting.const.defaults.js";
77
+ import {
78
+ prepareSettingsForLoad,
79
+ SettingsMigrationReviewCodes
80
+ } from "./models/setting.lifecycle.js";
71
81
  import { KeyIndexOfSettings } from "./models/setting.const.qr.js";
72
82
  import { isMetaEntry } from "./models/db.definition.js";
73
83
  import {
@@ -165,6 +175,7 @@ export {
165
175
  MODE_PAUSED,
166
176
  MODE_SELECTIVE,
167
177
  MODE_SHINY,
178
+ NEW_VAULT_SETTINGS,
168
179
  NODEINFO_DOCID,
169
180
  NOT_CONFLICTED,
170
181
  NoteTypes,
@@ -190,10 +201,12 @@ export {
190
201
  SALT_OF_ID,
191
202
  SALT_OF_PASSPHRASE,
192
203
  SEED_MURMURHASH,
204
+ SETTINGS_SCHEMA_DEFAULTS,
193
205
  SETTING_KEY_P2P_DEVICE_NAME,
194
206
  SETTING_VERSION_INITIAL,
195
207
  SETTING_VERSION_SUPPORT_CASE_INSENSITIVE,
196
208
  SYNCINFO_ID,
209
+ SettingsMigrationReviewCodes,
197
210
  SuffixDatabaseName,
198
211
  TIME_ARGUMENT_INFINITY,
199
212
  TweakValuesDefault,
@@ -207,6 +220,8 @@ export {
207
220
  configURIBase,
208
221
  configURIBaseQR,
209
222
  configurationNames,
223
+ createNewVaultSettings,
210
224
  isMetaEntry,
225
+ prepareSettingsForLoad,
211
226
  statusDisplay
212
227
  };
package/dist/context.d.ts CHANGED
@@ -5,4 +5,6 @@
5
5
  */
6
6
  export { ServiceContext, createServiceContext, type ServiceContextContract, type ServiceContextOptions, } from "./services/base/ServiceBase.js";
7
7
  export { createLiveSyncEventHub, type LiveSyncEventHub } from "./hub/hub.js";
8
- export { passthroughMessageTranslator, type MessageTranslator, type TranslationParameters, } from "./services/base/MessageTranslator.js";
8
+ export { englishMessageTranslator, passthroughMessageTranslator, type MessageTranslator, type TranslationParameters, } from "./services/base/MessageTranslator.js";
9
+ export { commonlibEnglishMessages, type CommonlibMessageKey } from "./services/base/CommonlibMessages.js";
10
+ export type { StandardIo, StandardIoChunk } from "./platform/standardIo.js";
package/dist/context.js CHANGED
@@ -4,11 +4,15 @@ import {
4
4
  } from "./services/base/ServiceBase.js";
5
5
  import { createLiveSyncEventHub } from "./hub/hub.js";
6
6
  import {
7
+ englishMessageTranslator,
7
8
  passthroughMessageTranslator
8
9
  } from "./services/base/MessageTranslator.js";
10
+ import { commonlibEnglishMessages } from "./services/base/CommonlibMessages.js";
9
11
  export {
10
12
  ServiceContext,
13
+ commonlibEnglishMessages,
11
14
  createLiveSyncEventHub,
12
15
  createServiceContext,
16
+ englishMessageTranslator,
13
17
  passthroughMessageTranslator
14
18
  };
@@ -1,3 +1,4 @@
1
+ export type ConfirmActionLayout = "auto" | "vertical";
1
2
  export interface Confirm {
2
3
  askYesNo(message: string): Promise<"yes" | "no">;
3
4
  askString(title: string, key: string, placeholder: string, isPassword?: boolean): Promise<string | false>;
@@ -12,6 +13,13 @@ export interface Confirm {
12
13
  defaultAction: T[number];
13
14
  timeout?: number;
14
15
  }): Promise<T[number] | false>;
15
- askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void): void;
16
- confirmWithMessage(title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout?: number): Promise<(typeof buttons)[number] | false>;
16
+ /**
17
+ * Shows a non-blocking message containing one host-rendered action link.
18
+ *
19
+ * Hosts which can display transient UI should keep the message visible for
20
+ * `durationMs`. Headless hosts may report the message without presenting an
21
+ * interactive action.
22
+ */
23
+ askInPopup(key: string, dialogText: string, anchorCallback: (anchor: HTMLAnchorElement) => void, durationMs?: number): void;
24
+ confirmWithMessage(title: string, contentMd: string, buttons: string[], defaultAction: (typeof buttons)[number], timeout?: number, actionLayout?: ConfirmActionLayout): Promise<(typeof buttons)[number] | false>;
17
25
  }
@@ -2,10 +2,18 @@ import type { FilePathWithPrefix, LoadedEntry, MetaEntry, UXFileInfo, UXFileInfo
2
2
  export interface DatabaseFileAccess {
3
3
  delete: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string) => Promise<boolean>;
4
4
  store: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
5
+ /** Store a file as a child of an exact revision and return the created revision. */
6
+ storeWithBaseRevision: (file: UXFileInfo, baseRevision: string | undefined, skipCheck?: boolean) => Promise<string | false>;
5
7
  storeAsConflictedRevision: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<boolean>;
8
+ /** Preserve unknown storage content as a conflict and return its exact revision. */
9
+ storeAsConflictedRevisionWithResult: (file: UXFileInfo, currentRev: string, skipCheck?: boolean) => Promise<string | false>;
10
+ /** Store a user deletion as a visible logical-deletion child of an exact revision. */
11
+ storeDeletionWithBaseRevision: (file: UXFileInfoStub | FilePathWithPrefix, baseRevision: string) => Promise<string | false>;
6
12
  storeContent(path: FilePathWithPrefix, content: string): Promise<boolean>;
7
13
  createChunks: (file: UXFileInfo, force?: boolean, skipCheck?: boolean) => Promise<boolean>;
8
14
  hasContentInRevisionHistory: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string) => Promise<boolean>;
15
+ /** Return every available revision whose content exactly matches the supplied bytes. */
16
+ findContentRevisions: (file: UXFileInfoStub | FilePathWithPrefix, content: string | string[] | Blob | ArrayBuffer, currentRev?: string) => Promise<string[]>;
9
17
  fetch: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, waitForReady?: boolean, skipCheck?: boolean) => Promise<UXFileInfo | false>;
10
18
  fetchEntryFromMeta: (meta: MetaEntry, waitForReady?: boolean, skipCheck?: boolean) => Promise<LoadedEntry | false>;
11
19
  fetchEntryMeta: (file: UXFileInfoStub | FilePathWithPrefix, rev?: string, skipCheck?: boolean) => Promise<MetaEntry | false>;
@@ -4,8 +4,18 @@ export interface Rebuilder {
4
4
  $rebuildEverything(): Promise<void>;
5
5
  $fetchLocal(makeLocalChunkBeforeSync?: boolean, preventMakeLocalFilesBeforeSync?: boolean): Promise<void>;
6
6
  $fetchLocalDBFast(autoResume: boolean): Promise<void>;
7
- scheduleRebuild(): Promise<void>;
8
- scheduleFetch(): Promise<void>;
7
+ /**
8
+ * Writes the Rebuild flag, suspends the current runtime, optionally prepares
9
+ * persisted state, and requests a restart, in that order.
10
+ *
11
+ * The preparation callback runs only after the flag exists. If it fails,
12
+ * the flag is removed, the current runtime is resumed, and the error is
13
+ * rethrown. A false result means that the flag could not be written and no
14
+ * restart was requested.
15
+ */
16
+ scheduleRebuild(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
17
+ /** See {@link scheduleRebuild}; this variant writes the Fetch flag. */
18
+ scheduleFetch(prepareBeforeRestart?: () => Promise<void>): Promise<boolean>;
9
19
  /**
10
20
  * Declares the finish of the rebuild process and unlock remote, resume reflecting the changes.
11
21
  */
@@ -0,0 +1,32 @@
1
+ import type { FilePathWithPrefix } from "../common/types.js";
2
+ import type { SimpleStore } from "../common/utils.js";
3
+ export type FileReflectionProvenanceRecord = {
4
+ /** Exact database revision which most recently produced the storage state. */
5
+ revision: string;
6
+ /** Raw modification time observed from this device's storage after reflection. */
7
+ observedStorageMtime?: number;
8
+ };
9
+ export interface FileReflectionProvenance {
10
+ get(path: FilePathWithPrefix): Promise<FileReflectionProvenanceRecord | undefined>;
11
+ set(path: FilePathWithPrefix, record: FileReflectionProvenanceRecord): Promise<void>;
12
+ delete(path: FilePathWithPrefix): Promise<void>;
13
+ move(from: FilePathWithPrefix, to: FilePathWithPrefix): Promise<void>;
14
+ }
15
+ /**
16
+ * Device-local provenance backed by an existing host-owned key-value store.
17
+ *
18
+ * The revision is authoritative. The raw storage mtime is diagnostic and may
19
+ * be used as a fast change hint, but it never proves content or branch identity.
20
+ * The host may construct this object before opening its store, but it must not
21
+ * invoke provenance operations until its normal storage lifecycle is ready.
22
+ * Store failures are reported to the caller rather than hidden by readiness
23
+ * waits, so lifecycle violations and reset races cannot hang file processing.
24
+ */
25
+ export declare class StoredFileReflectionProvenance implements FileReflectionProvenance {
26
+ private readonly store;
27
+ constructor(store: SimpleStore<FileReflectionProvenanceRecord>);
28
+ get(path: FilePathWithPrefix): Promise<FileReflectionProvenanceRecord | undefined>;
29
+ set(path: FilePathWithPrefix, record: FileReflectionProvenanceRecord): Promise<void>;
30
+ delete(path: FilePathWithPrefix): Promise<void>;
31
+ move(from: FilePathWithPrefix, to: FilePathWithPrefix): Promise<void>;
32
+ }
@@ -0,0 +1,25 @@
1
+ class StoredFileReflectionProvenance {
2
+ constructor(store) {
3
+ this.store = store;
4
+ }
5
+ store;
6
+ async get(path) {
7
+ return await this.store.get(path) ?? void 0;
8
+ }
9
+ async set(path, record) {
10
+ await this.store.set(path, record);
11
+ }
12
+ async delete(path) {
13
+ await this.store.delete(path);
14
+ }
15
+ async move(from, to) {
16
+ const record = await this.get(from);
17
+ if (record) {
18
+ await this.set(to, record);
19
+ }
20
+ await this.delete(from);
21
+ }
22
+ }
23
+ export {
24
+ StoredFileReflectionProvenance
25
+ };
@@ -258,13 +258,22 @@ class ConflictManager {
258
258
  }
259
259
  async tryAutoMergeSensibly(path, test, conflicts) {
260
260
  const conflictedRev = conflicts[0];
261
- const conflictedRevNo = Number(conflictedRev.split("-")[0]);
262
- const revFrom = await this.database.get(await this.options.pathService.path2id(path), {
263
- revs_info: true
264
- });
265
- const commonBase = (revFrom._revs_info || []).filter(
266
- (e) => e.status == "available" && Number(e.rev.split("-")[0]) < conflictedRevNo
267
- )?.[0]?.rev ?? "";
261
+ let commonBase = "";
262
+ try {
263
+ const documentId = await this.options.pathService.path2id(path);
264
+ const [currentBranch, conflictedBranch] = await Promise.all([
265
+ this.database.get(documentId, { rev: test._rev, revs_info: true }),
266
+ this.database.get(documentId, { rev: conflictedRev, revs_info: true })
267
+ ]);
268
+ const currentAvailable = new Set(
269
+ (currentBranch._revs_info || []).filter((revision) => revision.status === "available").map((revision) => revision.rev)
270
+ );
271
+ commonBase = (conflictedBranch._revs_info || []).filter((revision) => revision.status === "available" && currentAvailable.has(revision.rev)).sort((left, right) => Number(right.rev.split("-")[0]) - Number(left.rev.split("-")[0]))[0]?.rev ?? "";
272
+ } catch (ex) {
273
+ Logger(`Could not determine the common revision for ${path}`, LOG_LEVEL_VERBOSE);
274
+ Logger(ex, LOG_LEVEL_VERBOSE);
275
+ return false;
276
+ }
268
277
  let p = void 0;
269
278
  if (commonBase) {
270
279
  if (isSensibleMargeApplicable(path)) {
@@ -33,5 +33,6 @@ export declare class EntryManager {
33
33
  getDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions, dump?: boolean, waitForReady?: boolean, includeDeleted?: boolean): Promise<false | LoadedEntry>;
34
34
  getDBEntryFromMeta(meta: LoadedEntry | MetaEntry, dump?: boolean, waitForReady?: boolean): Promise<false | LoadedEntry>;
35
35
  deleteDBEntry(path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
36
+ storeDeletionAtRevision(path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<false | PouchDB.Core.Response>;
36
37
  putDBEntry(note: SavingEntry, onlyChunks?: boolean, conflictBaseRev?: string): Promise<false | PouchDB.Core.Response>;
37
38
  }
@@ -8,7 +8,8 @@ import {
8
8
  getDBEntryMetaByPath,
9
9
  isTargetFile,
10
10
  prepareChunk,
11
- putDBEntry
11
+ putDBEntry,
12
+ storeDeletionByPathAtRevision
12
13
  } from "./EntryManagerImpls.js";
13
14
  class EntryManager {
14
15
  options;
@@ -64,6 +65,9 @@ class EntryManager {
64
65
  async deleteDBEntry(path, opt) {
65
66
  return await deleteDBEntryByPath(this.serviceHost, this, path, opt);
66
67
  }
68
+ async storeDeletionAtRevision(path, baseRevision) {
69
+ return await storeDeletionByPathAtRevision(this.serviceHost, this, path, baseRevision);
70
+ }
67
71
  async putDBEntry(note, onlyChunks, conflictBaseRev) {
68
72
  return await putDBEntry(this.serviceHost, this, note, onlyChunks, conflictBaseRev);
69
73
  }
@@ -71,4 +71,5 @@ export declare function getDBEntryByPath(host: NecessaryServicesInterfaces<"path
71
71
  datatype: import("../../common/types.js").EntryTypeNotes;
72
72
  } & PouchDB.Core.IdMeta)>;
73
73
  export declare function deleteDBEntryByPath(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase }: NecessaryManagers<"localDatabase">, path: FilePathWithPrefix | FilePath, opt?: PouchDB.Core.GetOptions): Promise<boolean>;
74
+ export declare function storeDeletionByPathAtRevision(host: NecessaryServicesInterfaces<"path" | "setting", never>, { localDatabase }: NecessaryManagers<"localDatabase">, path: FilePathWithPrefix | FilePath, baseRevision: string): Promise<PouchDB.Core.Response | false>;
74
75
  export {};
@@ -484,6 +484,33 @@ async function deleteDBEntryByPath(host, { localDatabase }, path, opt) {
484
484
  throw ex;
485
485
  }
486
486
  }
487
+ async function storeDeletionByPathAtRevision(host, { localDatabase }, path, baseRevision) {
488
+ if (!isTargetFile(host, path)) {
489
+ return false;
490
+ }
491
+ const id = await host.services.path.path2id(path);
492
+ try {
493
+ return await serialized("file:" + path, async () => {
494
+ const obj = await localDatabase.get(id, {
495
+ rev: baseRevision
496
+ });
497
+ if (obj.type === "leaf") {
498
+ return false;
499
+ }
500
+ delete obj._deleted;
501
+ obj.deleted = true;
502
+ obj.mtime = Date.now();
503
+ const result = await localDatabase.put(obj, { force: true });
504
+ Logger(`Entry soft-deleted from revision: ${path} (${obj._id.substring(0, 8)}-${result.rev})`);
505
+ return result;
506
+ }) ?? false;
507
+ } catch (ex) {
508
+ if (isErrorOfMissingDoc(ex)) {
509
+ return false;
510
+ }
511
+ throw ex;
512
+ }
513
+ }
487
514
  export {
488
515
  canUseOnDemandChunking,
489
516
  computeChunkRetrievalMethod,
@@ -495,5 +522,6 @@ export {
495
522
  isLegacyNote,
496
523
  isTargetFile,
497
524
  prepareChunk,
498
- putDBEntry
525
+ putDBEntry,
526
+ storeDeletionByPathAtRevision
499
527
  };
@@ -21,3 +21,4 @@ export declare const builtinModules: readonly string[];
21
21
  /** Returns whether a module specifier refers to a Node built-in. */
22
22
  export declare const isBuiltin: typeof nodeIsBuiltin;
23
23
  export { NodeStorageAdapter, createNodeStorage, validateStoragePath, type CreateNodeStorageOptions, type NodeStorageStat, } from "./storage.js";
24
+ export { createNodeStandardIo, type CreateNodeStandardIoOptions, type NodeStandardInput, type NodeStandardOutput, } from "./standardIo.js";
@@ -17,9 +17,13 @@ import {
17
17
  createNodeStorage,
18
18
  validateStoragePath
19
19
  } from "./storage.js";
20
+ import {
21
+ createNodeStandardIo
22
+ } from "./standardIo.js";
20
23
  export {
21
24
  NodeStorageAdapter,
22
25
  builtinModules,
26
+ createNodeStandardIo,
23
27
  createNodeStorage,
24
28
  fileURLToPath,
25
29
  fs,
@@ -0,0 +1,24 @@
1
+ import type { StandardIo, StandardIoChunk } from "../standardIo.js";
2
+ /** Minimal readable stream shape accepted by the Node standard-I/O adapter. */
3
+ export type NodeStandardInput = AsyncIterable<unknown> & NodeJS.ReadableStream;
4
+ /** Minimal writable stream shape accepted by the Node standard-I/O adapter. */
5
+ export interface NodeStandardOutput extends NodeJS.WritableStream {
6
+ write(chunk: StandardIoChunk): boolean;
7
+ }
8
+ /** Streams used by {@link createNodeStandardIo}. */
9
+ export interface CreateNodeStandardIoOptions {
10
+ /** Defaults to `process.stdin`. */
11
+ stdin?: NodeStandardInput;
12
+ /** Defaults to `process.stdout`. */
13
+ stdout?: NodeStandardOutput;
14
+ /** Defaults to `process.stderr`. */
15
+ stderr?: NodeStandardOutput;
16
+ }
17
+ /**
18
+ * Bind the host-neutral standard-I/O contract to Node streams.
19
+ *
20
+ * Supplying streams is useful for embedded command-line hosts and tests. The
21
+ * caller owns stream lifecycle; this adapter only closes the temporary
22
+ * readline interface created by {@link StandardIo.prompt}.
23
+ */
24
+ export declare function createNodeStandardIo(options?: CreateNodeStandardIoOptions): StandardIo;
@@ -0,0 +1,39 @@
1
+ import * as nodeReadlinePromises from "node:readline/promises";
2
+ function createNodeStandardIo(options = {}) {
3
+ const stdin = options.stdin ?? process.stdin;
4
+ const stdout = options.stdout ?? process.stdout;
5
+ const stderr = options.stderr ?? process.stderr;
6
+ return {
7
+ async readStdin() {
8
+ const decoder = new TextDecoder();
9
+ let content = "";
10
+ for await (const chunk of stdin) {
11
+ if (typeof chunk === "string") {
12
+ content += chunk;
13
+ } else if (chunk instanceof Uint8Array) {
14
+ content += decoder.decode(chunk, { stream: true });
15
+ } else {
16
+ throw new TypeError("Standard input yielded a value which is neither text nor binary data.");
17
+ }
18
+ }
19
+ return content + decoder.decode();
20
+ },
21
+ async prompt(question) {
22
+ const readline = nodeReadlinePromises.createInterface({ input: stdin, output: stdout });
23
+ try {
24
+ return await readline.question(question);
25
+ } finally {
26
+ readline.close();
27
+ }
28
+ },
29
+ writeStdout(chunk) {
30
+ stdout.write(chunk);
31
+ },
32
+ writeStderr(chunk) {
33
+ stderr.write(chunk);
34
+ }
35
+ };
36
+ }
37
+ export {
38
+ createNodeStandardIo
39
+ };
@@ -2,7 +2,12 @@ import type { UXDataWriteOptions, UXStat } from "../../common/types.js";
2
2
  import type { IStorageAdapter } from "../../serviceModules/adapters/IStorageAdapter.js";
3
3
  export type NodeStorageStat = UXStat;
4
4
  export interface CreateNodeStorageOptions {
5
- /** Absolute or process-relative directory which bounds every adapter operation. */
5
+ /**
6
+ * Host-selected root directory. Adapter paths are resolved relative to this root.
7
+ *
8
+ * The host owns this trust boundary. The adapter is not a filesystem sandbox against
9
+ * an untrusted local process which can replace path components concurrently.
10
+ */
6
11
  rootPath: string;
7
12
  }
8
13
  /** Rooted storage adapter backed by Node's file-system APIs. */
@@ -10,6 +15,7 @@ export declare class NodeStorageAdapter implements IStorageAdapter<NodeStorageSt
10
15
  private readonly rootPath;
11
16
  constructor(rootPath: string);
12
17
  private resolvePath;
18
+ private assertNoSymbolicLinks;
13
19
  exists(path: string): Promise<boolean>;
14
20
  trystat(path: string): Promise<NodeStorageStat | null>;
15
21
  stat(path: string): Promise<NodeStorageStat | null>;
@@ -20,10 +26,13 @@ export declare class NodeStorageAdapter implements IStorageAdapter<NodeStorageSt
20
26
  write(path: string, data: string, options?: UXDataWriteOptions): Promise<void>;
21
27
  writeBinary(path: string, data: ArrayBuffer, options?: UXDataWriteOptions): Promise<void>;
22
28
  append(path: string, data: string, options?: UXDataWriteOptions): Promise<void>;
29
+ /** Atomically rename or move an entry without leaving the injected root. */
30
+ rename(sourcePath: string, targetPath: string): Promise<void>;
23
31
  list(basePath: string): Promise<{
24
32
  files: string[];
25
33
  folders: string[];
26
34
  }>;
35
+ private writeFile;
27
36
  private applyTimes;
28
37
  }
29
38
  /** Bind a Node storage adapter to an explicitly selected root directory. */