@vrtmrz/livesync-commonlib 0.2.3 → 0.3.0

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 (46) hide show
  1. package/README.md +33 -8
  2. package/dist/API/DirectFileManipulatorV2.d.ts +7 -1
  3. package/dist/API/DirectFileManipulatorV2.js +46 -67
  4. package/dist/API/DirectFileWatchOwner.d.ts +24 -0
  5. package/dist/API/DirectFileWatchOwner.js +256 -0
  6. package/dist/API/processSetting.js +3 -3
  7. package/dist/common/models/setting.type.d.ts +1 -1
  8. package/dist/common/models/shared.definition.configNames.js +1 -2
  9. package/dist/common/models/tweak.definition.d.ts +1 -1
  10. package/dist/common/pathNormalization.d.ts +2 -0
  11. package/dist/common/pathNormalization.js +7 -2
  12. package/dist/managers/ChunkFetcher.d.ts +3 -0
  13. package/dist/managers/ChunkFetcher.js +20 -13
  14. package/dist/managers/LiveSyncManagers.d.ts +2 -0
  15. package/dist/managers/LiveSyncManagers.js +1 -0
  16. package/dist/managers/chunkFetchSource.d.ts +14 -0
  17. package/dist/managers/chunkFetchSource.js +12 -0
  18. package/dist/pouchdb/LiveSyncDBFunctions.js +5 -0
  19. package/dist/pouchdb/LiveSyncLocalDB.d.ts +7 -1
  20. package/dist/pouchdb/LiveSyncLocalDB.js +13 -10
  21. package/dist/pouchdb/pathIdentityCompatibility.d.ts +8 -0
  22. package/dist/pouchdb/pathIdentityCompatibility.js +19 -1
  23. package/dist/replication/RemoteResource.d.ts +3 -0
  24. package/dist/replication/index.d.ts +2 -0
  25. package/dist/replication/index.js +2 -0
  26. package/dist/replication/journal/JournalSyncCore.d.ts +1 -0
  27. package/dist/replication/journal/JournalSyncCore.js +35 -19
  28. package/dist/replication/journal/LiveSyncJournalReplicator.d.ts +11 -3
  29. package/dist/replication/journal/LiveSyncJournalReplicator.js +123 -45
  30. package/dist/replication/trystero/P2PRoomSessionOwner.js +14 -13
  31. package/dist/replication/trystero/TrysteroReplicator.d.ts +7 -5
  32. package/dist/replication/trystero/TrysteroReplicator.js +98 -70
  33. package/dist/replication/trystero/TrysteroReplicator.pathIdentity.d.ts +11 -0
  34. package/dist/replication/trystero/TrysteroReplicator.pathIdentity.js +72 -0
  35. package/dist/replication/trystero/p2pReplicatorConfigurationIdentity.js +2 -0
  36. package/dist/replication/trystero/useP2PReplicatorFeature.js +6 -2
  37. package/dist/serviceModules/Rebuilder.d.ts +1 -0
  38. package/dist/serviceModules/Rebuilder.js +33 -5
  39. package/dist/worker/bgWorker.js +1 -1
  40. package/docs/conflict-resolution.md +1 -1
  41. package/docs/development.md +4 -0
  42. package/docs/proven-in-use.md +30 -13
  43. package/docs/releasing.md +2 -0
  44. package/docs/service-feature-composition.md +7 -1
  45. package/docs/unicode-path-identity.md +64 -3
  46. package/package.json +2 -2
@@ -327,20 +327,25 @@ class ChunkFetcher {
327
327
  await this.resolveLocallyAvailableRetries(requestClaims);
328
328
  let pendingIDs = await this.waitBeforeRemoteRequest(requestIDs, requestClaims, localCheckCompletionVersion);
329
329
  if (pendingIDs.length === 0) return;
330
- const replicator = this.options.replicatorService.getActiveReplicator();
331
- if (!replicator) {
332
- Logger("No active replicator was found to request missing chunks.");
333
- return;
334
- }
335
- if (!canFetchRemoteChunks(replicator)) {
336
- Logger("The active replicator does not support fetching individual remote chunks.");
337
- for (const chunkID of pendingIDs) {
338
- this.chunkManager.emitEvent(EVENT_MISSING_CHUNK_REMOTE, chunkID);
330
+ const requestCompletionVersion = this.finiteCompletionVersion;
331
+ let fetched;
332
+ if (this.options.source) {
333
+ fetched = await this.options.source.fetchChunks(pendingIDs);
334
+ } else {
335
+ const replicator = this.options.replicatorService.getActiveReplicator();
336
+ if (!replicator) {
337
+ Logger("No active replicator was found to request missing chunks.");
338
+ return;
339
339
  }
340
- return;
340
+ if (!canFetchRemoteChunks(replicator)) {
341
+ Logger("The active replicator does not support fetching individual remote chunks.");
342
+ for (const chunkID of pendingIDs) {
343
+ this.chunkManager.emitEvent(EVENT_MISSING_CHUNK_REMOTE, chunkID);
344
+ }
345
+ return;
346
+ }
347
+ fetched = await replicator.fetchRemoteChunks(pendingIDs, false);
341
348
  }
342
- const requestCompletionVersion = this.finiteCompletionVersion;
343
- const fetched = await replicator.fetchRemoteChunks(pendingIDs, false);
344
349
  pendingIDs = this.getActiveClaimIds(pendingIDs, requestClaims);
345
350
  this.touchClaims(pendingIDs, requestClaims);
346
351
  if (!fetched) {
@@ -354,7 +359,9 @@ class ChunkFetcher {
354
359
  this.logFetchedChunks(pendingIDs, classification);
355
360
  const { chunks, missingIds, invalidIds } = classification;
356
361
  try {
357
- if (chunks.length > 0) {
362
+ if (chunks.length > 0 && this.options.source) {
363
+ await this.options.source.storeChunks(chunks);
364
+ } else if (chunks.length > 0) {
358
365
  Logger(`Writing fetched chunks (${chunks.length}) to the database...`);
359
366
  const result = await this.chunkManager.write(
360
367
  chunks,
@@ -2,6 +2,7 @@ import { type EntryDoc } from "../common/types.js";
2
2
  import { ContentSplitter } from "../ContentSplitter/ContentSplitters.js";
3
3
  import { ChangeManager } from "./ChangeManager.js";
4
4
  import { ChunkFetcher } from "./ChunkFetcher.js";
5
+ import type { ChunkFetchSource } from "./chunkFetchSource.js";
5
6
  import { ChunkManager } from "./ChunkManager.js";
6
7
  import { ConflictManager } from "./ConflictManager.js";
7
8
  import { EntryManager } from "./EntryManager/EntryManager.js";
@@ -15,6 +16,7 @@ export interface LiveSyncManagersOptions<TSettingService extends ISettingService
15
16
  pathService: IPathService;
16
17
  replicatorService: IReplicatorService;
17
18
  APIService: IAPIService;
19
+ chunkFetchSource?: ChunkFetchSource;
18
20
  }
19
21
  export declare class LiveSyncManagers {
20
22
  protected _pathService: IPathService;
@@ -76,6 +76,7 @@ class LiveSyncManagers {
76
76
  });
77
77
  const chunkFetcher = new ChunkFetcher({
78
78
  chunkManager,
79
+ source: this.options.chunkFetchSource,
79
80
  replicatorService: this.options.replicatorService,
80
81
  settingService: this.options.settingService
81
82
  });
@@ -0,0 +1,14 @@
1
+ import type { DocumentID, EntryDoc, EntryLeaf } from "../common/types.js";
2
+ /** IO supplied by a host whose chunk source does not use the active replicator. */
3
+ export interface ChunkFetchSource {
4
+ /** Missing identifiers are omitted; false means that this request could not retrieve chunks. */
5
+ fetchChunks(ids: DocumentID[]): Promise<EntryLeaf[] | false>;
6
+ /** Accept fetched chunks before notifying waiting readers. */
7
+ storeChunks(chunks: EntryLeaf[]): Promise<void>;
8
+ }
9
+ /**
10
+ * Read through the prepared direct database, including its decryption transforms.
11
+ * The source already owns these documents, so accepting them performs no write.
12
+ * Construct a new source for each database lifetime.
13
+ */
14
+ export declare function createDirectChunkFetchSource(database: PouchDB.Database<EntryDoc>): ChunkFetchSource;
@@ -0,0 +1,12 @@
1
+ function createDirectChunkFetchSource(database) {
2
+ return {
3
+ async fetchChunks(ids) {
4
+ const result = await database.allDocs({ keys: ids, include_docs: true });
5
+ return result.rows.flatMap((row) => "doc" in row && row.doc ? [row.doc] : []);
6
+ },
7
+ storeChunks: () => Promise.resolve()
8
+ };
9
+ }
10
+ export {
11
+ createDirectChunkFetchSource
12
+ };
@@ -6,6 +6,7 @@ import {
6
6
  } from "../common/types.js";
7
7
  import { extractObject, isObjectDifferent, resolveWithIgnoreKnownError } from "../common/utils.js";
8
8
  import { assessTweakCompatibility } from "../common/models/tweak.compatibility.js";
9
+ import { getPathNormalizationVersion } from "../common/pathNormalization.js";
9
10
  import { usesEncryptedInternalMetadata } from "./remoteFeatureCompatibility.js";
10
11
  function getEffectiveTweakValues(setting) {
11
12
  return {
@@ -56,6 +57,10 @@ async function ensureRemoteIsCompatible(infoSrc, setting, deviceNodeID, currentV
56
57
  if (tweakAssessment.entries.some(({ key, relation }) => key === "idDerivationVersion" && relation === "different")) {
57
58
  return "ID_KEY_MISMATCH";
58
59
  }
60
+ if (getPathNormalizationVersion(currentTweakValues) !== getPathNormalizationVersion(preferred_tweak)) {
61
+ recordTweakAssessment?.(tweakAssessment);
62
+ return ["MISMATCHED", preferred_tweak];
63
+ }
59
64
  if (!setting.disableCheckingConfigMismatch) {
60
65
  recordTweakAssessment?.(tweakAssessment);
61
66
  if (tweakAssessment.alignment === "mismatched") {
@@ -1,6 +1,7 @@
1
1
  import { type EntryDoc, type EntryLeaf, type Credential, type RemoteDBSettings, type DocumentID, type FilePathWithPrefix, type FilePath, type DatabaseEntry, type LoadedEntry, type MetaEntry, type SavingEntry, type diff_result_leaf } from "../common/types.js";
2
2
  import type { LiveSyncEventHub } from "../hub/hub.js";
3
3
  import { LiveSyncManagers } from "../managers/LiveSyncManagers.js";
4
+ import type { ChunkFetchSource } from "../managers/chunkFetchSource.js";
4
5
  import type { AutoMergeResult } from "../managers/ConflictManager.js";
5
6
  import type { IServiceHub } from "../services/base/IService.js";
6
7
  import type { ServiceContext } from "../services/base/ServiceBase.js";
@@ -33,7 +34,12 @@ export type GeneratedChunk = {
33
34
  id: DocumentID;
34
35
  piece: string;
35
36
  };
37
+ /** Optional host IO bound afresh to each prepared database instance. */
38
+ export interface LiveSyncLocalDBRuntimeOptions {
39
+ createChunkFetchSource?: (database: PouchDB.Database<EntryDoc>) => ChunkFetchSource;
40
+ }
36
41
  export declare class LiveSyncLocalDB {
42
+ private readonly runtimeOptions;
37
43
  auth: Credential;
38
44
  dbname: string;
39
45
  settings: RemoteDBSettings;
@@ -52,7 +58,7 @@ export declare class LiveSyncLocalDB {
52
58
  /** Share owner retirement across every close path for one physical database lifetime. */
53
59
  private activeReplicationClose?;
54
60
  private databaseUnloadNotification?;
55
- constructor(dbname: string, env: LiveSyncLocalDBEnv);
61
+ constructor(dbname: string, env: LiveSyncLocalDBEnv, runtimeOptions?: LiveSyncLocalDBRuntimeOptions);
56
62
  /** Notify dependent services at most once for each physical database lifecycle. */
57
63
  private notifyDatabaseUnload;
58
64
  onunload(): void;
@@ -16,6 +16,18 @@ function getNoFromRev(rev) {
16
16
  return parseInt(rev.split("-")[0]);
17
17
  }
18
18
  class LiveSyncLocalDB {
19
+ constructor(dbname, env, runtimeOptions = {}) {
20
+ this.runtimeOptions = runtimeOptions;
21
+ this.auth = {
22
+ username: "",
23
+ password: ""
24
+ };
25
+ this.dbname = dbname;
26
+ this.env = env;
27
+ this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
28
+ this.refreshSettings();
29
+ }
30
+ runtimeOptions;
19
31
  auth;
20
32
  dbname;
21
33
  settings;
@@ -47,16 +59,6 @@ class LiveSyncLocalDB {
47
59
  /** Share owner retirement across every close path for one physical database lifetime. */
48
60
  activeReplicationClose;
49
61
  databaseUnloadNotification;
50
- constructor(dbname, env) {
51
- this.auth = {
52
- username: "",
53
- password: ""
54
- };
55
- this.dbname = dbname;
56
- this.env = env;
57
- this._log = createInstanceLogFunction("LiveSyncLocalDB", this.env.services.API);
58
- this.refreshSettings();
59
- }
60
62
  /** Notify dependent services at most once for each physical database lifecycle. */
61
63
  notifyDatabaseUnload() {
62
64
  if (!this.databaseUnloadNotification) {
@@ -170,6 +172,7 @@ class LiveSyncLocalDB {
170
172
  this.databaseUnloadNotification = void 0;
171
173
  const manager = new LiveSyncManagers({
172
174
  database: this.localDatabase,
175
+ chunkFetchSource: this.runtimeOptions?.createChunkFetchSource?.(this.localDatabase),
173
176
  APIService: this.env.services.API,
174
177
  pathService: this.env.services.path,
175
178
  replicatorService: this.env.services.replicator,
@@ -2,6 +2,14 @@ import { type EntryDoc } from "../common/types.js";
2
2
  type PathIdentitySettings = {
3
3
  pathNormalizationVersion?: number;
4
4
  };
5
+ export type PathIdentityInspection = {
6
+ readonly status: "empty";
7
+ } | {
8
+ readonly status: "configured";
9
+ readonly pathNormalizationVersion: 0 | 1;
10
+ };
11
+ /** Read the existing declaration without changing a remote or declaring a new identity. */
12
+ export declare function inspectDatabasePathIdentity(database: PouchDB.Database<EntryDoc>): Promise<PathIdentityInspection>;
5
13
  export declare function assertMatchingPathIdentityFeature(document: unknown, settings: PathIdentitySettings): void;
6
14
  /** Refuse an implicit migration. A fresh database may declare its identity before its first Metadata write. */
7
15
  export declare function assertDatabasePathIdentity(database: PouchDB.Database<EntryDoc>, settings: PathIdentitySettings, declare?: boolean): Promise<void>;
@@ -2,11 +2,28 @@ import { VERSIONING_DOCID } from "../common/types.js";
2
2
  import { getPathNormalizationVersion } from "../common/pathNormalization.js";
3
3
  import {
4
4
  assessRemoteFeatureDocument,
5
+ describeRemoteFeatureRejection,
5
6
  NFC_PATH_IDENTITY_FEATURE,
6
7
  REMOTE_FEATURE_GENERATION
7
8
  } from "./remoteFeatureCompatibility.js";
8
9
  import { isErrorOfMissingDoc } from "./utils_couchdb.js";
9
10
  import { bumpRemoteVersion } from "./negotiation.js";
11
+ async function inspectDatabasePathIdentity(database) {
12
+ let document;
13
+ try {
14
+ document = await database.get(VERSIONING_DOCID);
15
+ } catch (error) {
16
+ if (!isErrorOfMissingDoc(error)) throw error;
17
+ return (await database.info()).doc_count === 0 ? { status: "empty" } : { status: "configured", pathNormalizationVersion: 0 };
18
+ }
19
+ const assessment = assessRemoteFeatureDocument(document);
20
+ if (assessment.status === "older-generation") return { status: "configured", pathNormalizationVersion: 0 };
21
+ if (assessment.status !== "supported") throw new Error(describeRemoteFeatureRejection(assessment));
22
+ return {
23
+ status: "configured",
24
+ pathNormalizationVersion: assessment.usedFeatures.includes(NFC_PATH_IDENTITY_FEATURE) ? 1 : 0
25
+ };
26
+ }
10
27
  function assertMatchingPathIdentityFeature(document, settings) {
11
28
  const assessment = assessRemoteFeatureDocument(document);
12
29
  const declared = assessment.status === "supported" && assessment.usedFeatures.includes(NFC_PATH_IDENTITY_FEATURE);
@@ -60,5 +77,6 @@ async function assertDatabasePathIdentity(database, settings, declare = false) {
60
77
  }
61
78
  export {
62
79
  assertDatabasePathIdentity,
63
- assertMatchingPathIdentityFeature
80
+ assertMatchingPathIdentityFeature,
81
+ inspectDatabasePathIdentity
64
82
  };
@@ -1,3 +1,4 @@
1
+ import type { PathIdentityInspection } from "../pouchdb/pathIdentityCompatibility.js";
1
2
  import type { RemoteDBSettings, RemotePreferredTweakResult } from "../common/types.js";
2
3
  import type { JoinedDevicesSnapshot, ForgetJoinedDeviceResult } from "./joinedDevices.js";
3
4
  import type { RemoteDBStatus } from "./LiveSyncAbstractReplicator.js";
@@ -36,6 +37,8 @@ export type RemoteConnectionProbeResult = {
36
37
  export interface RemoteConnectionProbe {
37
38
  check(options?: RemoteConnectionProbeOptions): Promise<RemoteConnectionProbeResult>;
38
39
  getStatus(): Promise<false | RemoteDBStatus>;
40
+ /** Inspect a central remote before choosing initial host settings. */
41
+ inspectPathIdentity?(): Promise<PathIdentityInspection>;
39
42
  dispose(): Promise<void>;
40
43
  }
41
44
  /** A finite preferred-tweak reader bound to one trial configuration. */
@@ -13,3 +13,5 @@ export { assessRemoteFeatureDocument, describeRemoteFeatureRejection, ENCRYPTED_
13
13
  export type { RemoteFeatureAssessment } from "../pouchdb/remoteFeatureCompatibility.js";
14
14
  export type { JoinedDevice, JoinedDevicesSnapshot, ForgetJoinedDeviceResult } from "./joinedDevices.js";
15
15
  export type { ReplicationStatics, CouchDBReplicationPhase, CouchDBReplicationProgress, ReplicationCheckpointProgress, } from "../common/models/shared.definition.js";
16
+ export { inspectDatabasePathIdentity } from "../pouchdb/pathIdentityCompatibility.js";
17
+ export type { PathIdentityInspection } from "../pouchdb/pathIdentityCompatibility.js";
@@ -43,6 +43,7 @@ import {
43
43
  REMOTE_FEATURE_GENERATION,
44
44
  usesEncryptedInternalMetadata
45
45
  } from "../pouchdb/remoteFeatureCompatibility.js";
46
+ import { inspectDatabasePathIdentity } from "../pouchdb/pathIdentityCompatibility.js";
46
47
  export {
47
48
  CAPABILITY_NOT_APPLICABLE,
48
49
  CAPABILITY_NOT_IMPLEMENTED,
@@ -72,6 +73,7 @@ export {
72
73
  centralRemoteAdministrationVerified,
73
74
  defineReplicatorProviderDefinitions,
74
75
  describeRemoteFeatureRejection,
76
+ inspectDatabasePathIdentity,
75
77
  isCentralRemoteAdministrationVerified,
76
78
  isReplicationCompleted,
77
79
  milestoneSatisfiesCentralRemoteAdministration,
@@ -62,6 +62,7 @@ export declare class JournalSyncCore {
62
62
  private _createSendCompressTransformStream;
63
63
  private _createSendUploadWritableStream;
64
64
  sendLocalJournal(showMessage?: boolean): Promise<boolean>;
65
+ hasRemoteJournals(): Promise<boolean>;
65
66
  _getRemoteJournals(): Promise<string[]>;
66
67
  processDocuments(allDocs: ProcessingEntry[]): Promise<boolean>;
67
68
  private _createReceiveReadableStream;
@@ -33,6 +33,7 @@ import {
33
33
  encryptBinary as encryptBinaryHKDF,
34
34
  decryptBinary as decryptBinaryHKDF
35
35
  } from "octagonal-wheels/encryption/hkdf";
36
+ import { assertDatabasePathIdentity } from "../../pouchdb/pathIdentityCompatibility.js";
36
37
  const RECORD_SPLIT = `
37
38
  `;
38
39
  const UNIT_SPLIT = ``;
@@ -45,6 +46,9 @@ function serializeDoc(doc) {
45
46
  }
46
47
  return te.encode(JSON.stringify(doc) + RECORD_SPLIT);
47
48
  }
49
+ function isJournalKey(key) {
50
+ return !key.startsWith("_");
51
+ }
48
52
  class JournalSyncCore {
49
53
  _settings;
50
54
  storage;
@@ -217,7 +221,13 @@ class JournalSyncCore {
217
221
  this.syncParamsHandler = void 0;
218
222
  }
219
223
  async resetCheckpointInfo() {
220
- await this.updateCheckPointInfo((info) => ({ ...CheckPointInfoDefault }));
224
+ await this.updateCheckPointInfo(() => ({
225
+ ...CheckPointInfoDefault,
226
+ knownIDs: /* @__PURE__ */ new Set(),
227
+ sentIDs: /* @__PURE__ */ new Set(),
228
+ receivedFiles: /* @__PURE__ */ new Set(),
229
+ sentFiles: /* @__PURE__ */ new Set()
230
+ }));
221
231
  }
222
232
  getJournalEpochFromSyncParams(params) {
223
233
  return `${params.protocolVersion}:${params.pbkdf2salt}`;
@@ -262,32 +272,33 @@ class JournalSyncCore {
262
272
  }
263
273
  async resetBucket() {
264
274
  this.resetAllCaches();
265
- let files = [];
266
275
  try {
276
+ const skippedFiles = [];
277
+ let from = "";
278
+ let files;
267
279
  do {
268
- files = await this.storage.listFiles("", 100);
269
- if (files.length == 0) {
270
- break;
271
- }
272
- if (!await this.storage.deleteFiles(files)) {
273
- return false;
280
+ files = await this.storage.listFiles(from, 100);
281
+ if (files.length > 0) {
282
+ const nextFrom = files[files.length - 1];
283
+ if (nextFrom <= from) {
284
+ throw new Error("Journal reset listing did not advance. Control documents have been retained.");
285
+ }
286
+ skippedFiles.push(...files.filter((key) => !isJournalKey(key)));
287
+ const journals = files.filter(isJournalKey);
288
+ if (journals.length > 0 && !await this.storage.deleteFiles(journals)) return false;
289
+ from = nextFrom;
274
290
  }
275
- } while (files.length != 0);
291
+ } while (files.length > 0);
292
+ while (skippedFiles.length > 0) {
293
+ if (!await this.storage.deleteFiles(skippedFiles.splice(0, 100))) return false;
294
+ }
276
295
  } catch (ex) {
277
296
  Logger(`WARNING! Could not delete files.`, LOG_LEVEL_NOTICE, "reset-bucket");
278
297
  Logger(ex, LOG_LEVEL_VERBOSE);
279
298
  return false;
280
299
  }
281
- const journals = await this._getRemoteJournals();
282
- if (journals.length == 0) {
283
- Logger("Nothing to delete!", LOG_LEVEL_NOTICE);
284
- return true;
285
- }
286
- if (!await this.storage.deleteFiles(journals)) {
287
- return false;
288
- }
289
- Logger(`${journals.length} items has been deleted!`, LOG_LEVEL_NOTICE);
290
300
  await this.resetCheckpointInfo();
301
+ Logger("Remote bucket cleared", LOG_LEVEL_NOTICE);
291
302
  return true;
292
303
  }
293
304
  getRemoteKey() {
@@ -502,6 +513,7 @@ class JournalSyncCore {
502
513
  });
503
514
  }
504
515
  async sendLocalJournal(showMessage = false) {
516
+ await assertDatabasePathIdentity(this.db, this.currentSettings);
505
517
  this.updateInfo({ syncStatus: "JOURNAL_SEND" });
506
518
  return await shareRunningResult("send_journal_stream", async () => {
507
519
  this.requestedStop = false;
@@ -544,12 +556,15 @@ class JournalSyncCore {
544
556
  }
545
557
  });
546
558
  }
559
+ async hasRemoteJournals() {
560
+ return (await this.storage.listFiles("", 1)).some(isJournalKey);
561
+ }
547
562
  async _getRemoteJournals() {
548
563
  const checkPointInfo = await this.getCheckpointInfo();
549
564
  const StartAfter = [...checkPointInfo.receivedFiles.keys()].sort(
550
565
  (a, b) => b.localeCompare(a, void 0, { numeric: true })
551
566
  )[0];
552
- const files = (await this.storage.listFiles(StartAfter)).filter((e) => !e.startsWith("_"));
567
+ const files = (await this.storage.listFiles(StartAfter)).filter(isJournalKey);
553
568
  if (!files) return [];
554
569
  return files.sort((a, b) => a.localeCompare(b, void 0, { numeric: true }));
555
570
  }
@@ -723,6 +738,7 @@ class JournalSyncCore {
723
738
  });
724
739
  }
725
740
  async receiveRemoteJournal(showMessage = false) {
741
+ await assertDatabasePathIdentity(this.db, this.currentSettings);
726
742
  this.updateInfo({ syncStatus: "JOURNAL_RECEIVE" });
727
743
  return await shareRunningResult("receive_journal_stream", async () => {
728
744
  this.requestedStop = false;
@@ -1,3 +1,4 @@
1
+ import type { PathIdentityInspection } from "../../pouchdb/pathIdentityCompatibility.js";
1
2
  import { type RemoteDBSettings, type ChunkVersionRange, type NodeData, type RemotePreferredTweakResult, type TweakAssessment } from "../../common/types.js";
2
3
  import { JournalSyncCore } from "./JournalSyncCore.js";
3
4
  import { LiveSyncAbstractReplicator, type RemoteDBStatus } from "../LiveSyncAbstractReplicator.js";
@@ -23,7 +24,16 @@ export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicato
23
24
  private journalTransferStopGeneration;
24
25
  getReplicationPBKDF2Salt(setting: RemoteDBSettings, refresh?: boolean): Promise<Uint8Array<ArrayBuffer>>;
25
26
  setupJournalSyncClient(settings?: RemoteDBSettings): JournalSyncCore;
26
- ensureBucketIsCompatible(deviceNodeID: string, currentVersionRange: ChunkVersionRange, setting?: RemoteDBSettings, client?: Pick<JournalSyncCore, "downloadJsonWithResult" | "getCheckpointInfo" | "uploadJson">, recordTweakAssessment?: (assessment: TweakAssessment) => void): Promise<ENSURE_DB_RESULT>;
27
+ private isBucketPathIdentityCompatible;
28
+ private isBucketIdDerivationCompatible;
29
+ private assertBucketIdentity;
30
+ /** Inspect identity without publishing this node, preparing a seed, or changing checkpoints. */
31
+ assertRemotePathIdentity(setting: RemoteDBSettings): Promise<void>;
32
+ /** Read path identity for initial setup without publishing a node or preparing a seed. */
33
+ inspectRemotePathIdentity(setting: RemoteDBSettings): Promise<PathIdentityInspection>;
34
+ /** Lock existing journals for replacement without declaring the replacement's identity. */
35
+ markRemoteLockedForRebuild(setting: RemoteDBSettings): Promise<void>;
36
+ ensureBucketIsCompatible(deviceNodeID: string, currentVersionRange: ChunkVersionRange, setting?: RemoteDBSettings, client?: Pick<JournalSyncCore, "downloadJsonWithResult" | "getCheckpointInfo" | "uploadJson" | "hasRemoteJournals">, recordTweakAssessment?: (assessment: TweakAssessment) => void): Promise<ENSURE_DB_RESULT>;
27
37
  constructor(env: LiveSyncJournalReplicatorEnv);
28
38
  migrate(from: number, to: number): Promise<boolean>;
29
39
  /**
@@ -62,8 +72,6 @@ export declare class LiveSyncJournalReplicator extends LiveSyncAbstractReplicato
62
72
  setPreferredRemoteTweakSettings(setting: RemoteDBSettings): Promise<void>;
63
73
  getRemotePreferredTweakValues(setting: RemoteDBSettings): Promise<RemotePreferredTweakResult>;
64
74
  getRemoteStatus(setting: RemoteDBSettings): Promise<false | RemoteDBStatus>;
65
- private supportsLegacyPathIdentity;
66
- private assertLegacyPathIdentity;
67
75
  private getOwnJoinedDeviceId;
68
76
  getJoinedDevices(setting: RemoteDBSettings): Promise<JoinedDevicesSnapshot>;
69
77
  forgetJoinedDevice(setting: RemoteDBSettings, deviceId: string): Promise<ForgetJoinedDeviceResult>;