mindoodb 0.0.19 → 0.0.21

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 (105) hide show
  1. package/LICENSE +200 -0
  2. package/README-server.md +14 -14
  3. package/README.md +12 -10
  4. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts +6 -0
  5. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.d.ts.map +1 -1
  6. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js +519 -373
  7. package/dist/browser/appendonlystores/IndexedDBContentAddressedStore.js.map +1 -1
  8. package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts +12 -0
  9. package/dist/browser/cache/IndexedDBLocalCacheStore.d.ts.map +1 -1
  10. package/dist/browser/cache/IndexedDBLocalCacheStore.js +152 -65
  11. package/dist/browser/cache/IndexedDBLocalCacheStore.js.map +1 -1
  12. package/dist/browser/index.d.ts +1 -1
  13. package/dist/browser/index.d.ts.map +1 -1
  14. package/dist/browser/index.js +2 -1
  15. package/dist/browser/index.js.map +1 -1
  16. package/dist/browser/index.mjs +14 -0
  17. package/dist/core/BaseMindooDB.d.ts +594 -6
  18. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  19. package/dist/core/BaseMindooDB.js +2478 -191
  20. package/dist/core/BaseMindooDB.js.map +1 -1
  21. package/dist/core/BaseMindooTenant.d.ts +65 -1
  22. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  23. package/dist/core/BaseMindooTenant.js +250 -35
  24. package/dist/core/BaseMindooTenant.js.map +1 -1
  25. package/dist/core/BaseMindooTenantFactory.d.ts.map +1 -1
  26. package/dist/core/BaseMindooTenantFactory.js +47 -18
  27. package/dist/core/BaseMindooTenantFactory.js.map +1 -1
  28. package/dist/core/DocumentConflictAnalysis.d.ts +22 -0
  29. package/dist/core/DocumentConflictAnalysis.d.ts.map +1 -0
  30. package/dist/core/DocumentConflictAnalysis.js +101 -0
  31. package/dist/core/DocumentConflictAnalysis.js.map +1 -0
  32. package/dist/core/MindooDBServerAdmin.d.ts +26 -1
  33. package/dist/core/MindooDBServerAdmin.d.ts.map +1 -1
  34. package/dist/core/MindooDBServerAdmin.js +19 -3
  35. package/dist/core/MindooDBServerAdmin.js.map +1 -1
  36. package/dist/core/appendonlystores/types.d.ts +9 -0
  37. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  38. package/dist/core/automerge-adapter.d.ts.map +1 -1
  39. package/dist/core/automerge-adapter.js +26 -14
  40. package/dist/core/automerge-adapter.js.map +1 -1
  41. package/dist/core/cache/EncryptedLocalCacheStore.d.ts +51 -1
  42. package/dist/core/cache/EncryptedLocalCacheStore.d.ts.map +1 -1
  43. package/dist/core/cache/EncryptedLocalCacheStore.js +48 -9
  44. package/dist/core/cache/EncryptedLocalCacheStore.js.map +1 -1
  45. package/dist/core/cache/LocalCacheStore.d.ts +34 -0
  46. package/dist/core/cache/LocalCacheStore.d.ts.map +1 -1
  47. package/dist/core/cache/LocalCacheStore.js +16 -0
  48. package/dist/core/cache/LocalCacheStore.js.map +1 -1
  49. package/dist/core/databaseIdValidation.d.ts +13 -0
  50. package/dist/core/databaseIdValidation.d.ts.map +1 -0
  51. package/dist/core/databaseIdValidation.js +38 -0
  52. package/dist/core/databaseIdValidation.js.map +1 -0
  53. package/dist/core/index.d.ts +2 -0
  54. package/dist/core/index.d.ts.map +1 -1
  55. package/dist/core/index.js +2 -0
  56. package/dist/core/index.js.map +1 -1
  57. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.d.ts.map +1 -1
  58. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js +5 -1
  59. package/dist/core/indexing/virtualviews/MindooDBVirtualViewDataProvider.js.map +1 -1
  60. package/dist/core/indexing/virtualviews/VirtualView.d.ts +14 -0
  61. package/dist/core/indexing/virtualviews/VirtualView.d.ts.map +1 -1
  62. package/dist/core/indexing/virtualviews/VirtualView.js +38 -6
  63. package/dist/core/indexing/virtualviews/VirtualView.js.map +1 -1
  64. package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts +9 -2
  65. package/dist/core/indexing/virtualviews/VirtualViewDataChange.d.ts.map +1 -1
  66. package/dist/core/indexing/virtualviews/VirtualViewDataChange.js +10 -3
  67. package/dist/core/indexing/virtualviews/VirtualViewDataChange.js.map +1 -1
  68. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts +23 -1
  69. package/dist/core/indexing/virtualviews/VirtualViewEntryData.d.ts.map +1 -1
  70. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js +17 -1
  71. package/dist/core/indexing/virtualviews/VirtualViewEntryData.js.map +1 -1
  72. package/dist/core/indexing/virtualviews/types.d.ts +8 -0
  73. package/dist/core/indexing/virtualviews/types.d.ts.map +1 -1
  74. package/dist/core/indexing/virtualviews/types.js.map +1 -1
  75. package/dist/core/keys/KeyBag.d.ts +244 -7
  76. package/dist/core/keys/KeyBag.d.ts.map +1 -1
  77. package/dist/core/keys/KeyBag.js +299 -82
  78. package/dist/core/keys/KeyBag.js.map +1 -1
  79. package/dist/core/tenantIdValidation.d.ts +19 -0
  80. package/dist/core/tenantIdValidation.d.ts.map +1 -0
  81. package/dist/core/tenantIdValidation.js +43 -0
  82. package/dist/core/tenantIdValidation.js.map +1 -0
  83. package/dist/core/types.d.ts +703 -12
  84. package/dist/core/types.d.ts.map +1 -1
  85. package/dist/core/types.js.map +1 -1
  86. package/dist/node/cache/FileSystemLocalCacheStore.d.ts +2 -1
  87. package/dist/node/cache/FileSystemLocalCacheStore.d.ts.map +1 -1
  88. package/dist/node/cache/FileSystemLocalCacheStore.js +4 -0
  89. package/dist/node/cache/FileSystemLocalCacheStore.js.map +1 -1
  90. package/dist/node/server/ServerSync.d.ts.map +1 -1
  91. package/dist/node/server/ServerSync.js +3 -1
  92. package/dist/node/server/ServerSync.js.map +1 -1
  93. package/dist/node/server/TenantManager.d.ts +1 -0
  94. package/dist/node/server/TenantManager.d.ts.map +1 -1
  95. package/dist/node/server/TenantManager.js +11 -3
  96. package/dist/node/server/TenantManager.js.map +1 -1
  97. package/dist/node/server/validation.d.ts +2 -6
  98. package/dist/node/server/validation.d.ts.map +1 -1
  99. package/dist/node/server/validation.js +14 -24
  100. package/dist/node/server/validation.js.map +1 -1
  101. package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts +1 -0
  102. package/dist/reactnative/cache/MMKVLocalCacheStore.d.ts.map +1 -1
  103. package/dist/reactnative/cache/MMKVLocalCacheStore.js +14 -0
  104. package/dist/reactnative/cache/MMKVLocalCacheStore.js.map +1 -1
  105. package/package.json +25 -23
@@ -33,6 +33,24 @@ export declare class BaseMindooTenant implements MindooTenant {
33
33
  private databaseCache;
34
34
  private directoryCache;
35
35
  private remoteStoreCache;
36
+ /**
37
+ * Cursor into the local KeyBag changes feed. Tracks the last event
38
+ * consumed by {@link reconcileKeyBagChanges} so we can replay only the
39
+ * deltas and stay idempotent across both live notifications and
40
+ * explicit reconcile calls.
41
+ */
42
+ private keyBagChangeCursor;
43
+ /**
44
+ * Disposer returned by {@link KeyBag.onChanges} for live notifications.
45
+ * `null` once the tenant has been torn down via {@link disposeCacheManager}.
46
+ */
47
+ private unsubscribeKeyBagChanges;
48
+ /**
49
+ * Single-flight latch for {@link reconcileKeyBagChanges}. Concurrent calls
50
+ * coalesce onto the same in-flight reconcile so live listener firings and
51
+ * explicit calls cannot race against each other or duplicate work.
52
+ */
53
+ private keyBagReconcilePromise;
36
54
  private decryptedTenantKeyCache?;
37
55
  private decryptedUserSigningKeyCache?;
38
56
  private decryptedUserEncryptionKeyCache?;
@@ -49,7 +67,21 @@ export declare class BaseMindooTenant implements MindooTenant {
49
67
  * Map value: true if trusted, false if explicitly revoked
50
68
  */
51
69
  private additionalTrustedKeys?;
52
- constructor(factory: MindooTenantFactory, tenantId: string, administrationPublicKey: string, administrationEncryptionPublicKey: string, currentUser: PrivateUserId, currentUserPassword: string, keyBag: KeyBag, storeFactory: ContentAddressedStoreFactory, cryptoAdapter: CryptoAdapter, logger?: Logger, additionalTrustedKeys?: ReadonlyMap<string, boolean>, localCacheStore?: LocalCacheStore);
70
+ constructor(factory: MindooTenantFactory, tenantId: string, administrationPublicKey: string, administrationEncryptionPublicKey: string, currentUser: PrivateUserId, currentUserPassword: string, keyBag: KeyBag, storeFactory: ContentAddressedStoreFactory, cryptoAdapter: CryptoAdapter, logger?: Logger, additionalTrustedKeys?: ReadonlyMap<string, boolean>, localCacheStore?: LocalCacheStore,
71
+ /**
72
+ * Optional pre-derived crypto material for the current user. When the
73
+ * caller has already decrypted the user's signing/encryption private
74
+ * keys (e.g. once at login) and/or derived the cache encryption key,
75
+ * passing them here skips the per-instance PBKDF2 + decrypt path. The
76
+ * `currentUserPassword` argument may then be empty and is never used
77
+ * downstream (as long as the requested key is already present in the
78
+ * cache).
79
+ */
80
+ preDecryptedMaterial?: {
81
+ signingKey?: CryptoKey;
82
+ encryptionKey?: CryptoKey;
83
+ cacheEncryptionKey?: CryptoKey;
84
+ });
53
85
  /**
54
86
  * Initialize the tenant.
55
87
  */
@@ -64,6 +96,7 @@ export declare class BaseMindooTenant implements MindooTenant {
64
96
  */
65
97
  disposeCacheManager(): Promise<void>;
66
98
  getCryptoAdapter(): CryptoAdapter;
99
+ private normalizeTimeTravelDate;
67
100
  getFactory(): MindooTenantFactory;
68
101
  getId(): string;
69
102
  /**
@@ -95,7 +128,30 @@ export declare class BaseMindooTenant implements MindooTenant {
95
128
  openDirectory(): Promise<MindooTenantDirectory>;
96
129
  private assertCurrentUserCanOpenDB;
97
130
  openDB(id: string, options?: OpenDBOptions): Promise<MindooDB>;
131
+ /**
132
+ * Replay any pending KeyBag mutations for this tenant and refresh open
133
+ * live databases so document visibility matches the current key set.
134
+ *
135
+ * Designed as the single barrier callers can `await` to be certain that
136
+ * subsequent reads observe the latest add/remove of doc keys. Behaviour:
137
+ *
138
+ * - Idempotent and concurrency-safe. Concurrent callers share the same
139
+ * in-flight reconcile via {@link keyBagReconcilePromise}, and a second
140
+ * call after the cursor catches up is a fast no-op.
141
+ * - Cursor-based. Replays from the persisted cursor in
142
+ * {@link keyBagChangeCursor}, so missed live notifications are
143
+ * automatically recovered.
144
+ * - Tenant-scoped. Events for other tenants in a shared KeyBag are
145
+ * consumed (the cursor still advances) but ignored.
146
+ * - Invalidates the cached tenant default key when its add/remove was
147
+ * observed, so the next encrypt/decrypt call re-reads the bag.
148
+ * - Skips time-travel databases. Those views are immutable historical
149
+ * cuts and never participate in visibility reconciliation.
150
+ */
151
+ reconcileKeyBagChanges(): Promise<void>;
98
152
  createDocSignerFor(signKey: SigningKeyPair): MindooDocSigner;
153
+ private exportJoinResponseDocKeyVersions;
154
+ private resolveJoinResponseDocKeyIds;
99
155
  /**
100
156
  * Approve a join request and produce a join response.
101
157
  */
@@ -171,6 +227,14 @@ export declare class BaseMindooTenant implements MindooTenant {
171
227
  * @returns true if the key can be resolved, false otherwise
172
228
  */
173
229
  hasDecryptionKey(decryptionKeyId: string): Promise<boolean>;
230
+ /**
231
+ * Stable fingerprint of the doc-type keys this tenant currently has
232
+ * access to, delegating to {@link KeyBag.getDocKeyFingerprint} scoped
233
+ * to this tenant. Persisted by {@link BaseMindooDB} alongside its cache
234
+ * checkpoint so warm starts can skip a full metadata scan when the
235
+ * KeyBag composition has not changed.
236
+ */
237
+ getDocKeyFingerprint(): Promise<string>;
174
238
  /**
175
239
  * Get the symmetric key for a given key ID.
176
240
  * This is a protected helper method that can be used by subclasses.
@@ -1 +1 @@
1
- {"version":3,"file":"BaseMindooTenant.d.ts","sourceRoot":"","sources":["../../src/core/BaseMindooTenant.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"BaseMindooTenant.d.ts","sourceRoot":"","sources":["../../src/core/BaseMindooTenant.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,7 @@ const logging_1 = require("./logging");
12
12
  const MindooURI_1 = require("./uri/MindooURI");
13
13
  const EncryptedLocalCacheStore_1 = require("./cache/EncryptedLocalCacheStore");
14
14
  const CacheManager_1 = require("./cache/CacheManager");
15
+ const databaseIdValidation_1 = require("./databaseIdValidation");
15
16
  /**
16
17
  * BaseMindooTenant is a platform-agnostic implementation of MindooTenant
17
18
  * that works in both browser and server environments.
@@ -27,10 +28,38 @@ const CacheManager_1 = require("./cache/CacheManager");
27
28
  * - PBKDF2 key derivation (for password-based key derivation)
28
29
  */
29
30
  class BaseMindooTenant {
30
- constructor(factory, tenantId, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter, logger, additionalTrustedKeys, localCacheStore) {
31
+ constructor(factory, tenantId, administrationPublicKey, administrationEncryptionPublicKey, currentUser, currentUserPassword, keyBag, storeFactory, cryptoAdapter, logger, additionalTrustedKeys, localCacheStore,
32
+ /**
33
+ * Optional pre-derived crypto material for the current user. When the
34
+ * caller has already decrypted the user's signing/encryption private
35
+ * keys (e.g. once at login) and/or derived the cache encryption key,
36
+ * passing them here skips the per-instance PBKDF2 + decrypt path. The
37
+ * `currentUserPassword` argument may then be empty and is never used
38
+ * downstream (as long as the requested key is already present in the
39
+ * cache).
40
+ */
41
+ preDecryptedMaterial) {
31
42
  this.databaseCache = new Map();
32
43
  this.directoryCache = null;
33
44
  this.remoteStoreCache = new Map();
45
+ /**
46
+ * Cursor into the local KeyBag changes feed. Tracks the last event
47
+ * consumed by {@link reconcileKeyBagChanges} so we can replay only the
48
+ * deltas and stay idempotent across both live notifications and
49
+ * explicit reconcile calls.
50
+ */
51
+ this.keyBagChangeCursor = null;
52
+ /**
53
+ * Disposer returned by {@link KeyBag.onChanges} for live notifications.
54
+ * `null` once the tenant has been torn down via {@link disposeCacheManager}.
55
+ */
56
+ this.unsubscribeKeyBagChanges = null;
57
+ /**
58
+ * Single-flight latch for {@link reconcileKeyBagChanges}. Concurrent calls
59
+ * coalesce onto the same in-flight reconcile so live listener firings and
60
+ * explicit calls cannot race against each other or duplicate work.
61
+ */
62
+ this.keyBagReconcilePromise = null;
34
63
  // Local cache support
35
64
  this.cacheManager = null;
36
65
  this.factory = factory;
@@ -50,9 +79,44 @@ class BaseMindooTenant {
50
79
  this.logger =
51
80
  logger ||
52
81
  new logging_1.MindooLogger((0, logging_1.getDefaultLogLevel)(), `Tenant:${tenantId}`, true);
53
- // Set up cache if a local cache store is provided
82
+ // Hook the local KeyBag changes feed:
83
+ // - Snapshot the cursor so any pre-existing key state is treated as
84
+ // "already seen" and only future mutations trigger reconciliation.
85
+ // - Subscribe to live events scoped to this tenant; missed events
86
+ // are still recovered when callers invoke
87
+ // {@link reconcileKeyBagChanges} explicitly (it replays from the
88
+ // cursor regardless of whether the listener fired).
89
+ this.keyBagChangeCursor = this.keyBag.getLatestChangeCursor();
90
+ this.unsubscribeKeyBagChanges = this.keyBag.onChanges((event) => {
91
+ if (event.type !== "doc" || event.tenantId !== this.tenantId) {
92
+ return;
93
+ }
94
+ // Fire-and-forget: errors are surfaced via the reconcile path itself.
95
+ // We intentionally do not await here to keep the mutation call site
96
+ // synchronous from the listener's perspective.
97
+ void this.reconcileKeyBagChanges();
98
+ });
99
+ // Pre-warm the per-user CryptoKey caches so getDecryptedSigningKey()
100
+ // and getDecryptedEncryptionKey() never run PBKDF2 + decryptPrivateKey
101
+ // when the caller has already derived these keys at login.
102
+ if (preDecryptedMaterial?.signingKey) {
103
+ this.decryptedUserSigningKeyCache = preDecryptedMaterial.signingKey;
104
+ }
105
+ if (preDecryptedMaterial?.encryptionKey) {
106
+ this.decryptedUserEncryptionKeyCache = preDecryptedMaterial.encryptionKey;
107
+ }
108
+ // Set up cache if a local cache store is provided. When a pre-derived
109
+ // cache encryption key is available, prefer that to skip the password
110
+ // path and avoid retaining the plaintext password inside the encrypted
111
+ // cache wrapper.
54
112
  if (localCacheStore) {
55
- const encryptedStore = new EncryptedLocalCacheStore_1.EncryptedLocalCacheStore(localCacheStore, currentUserPassword, this.cryptoAdapter, this.logger.createChild("EncryptedCache"));
113
+ const encryptedStore = preDecryptedMaterial?.cacheEncryptionKey
114
+ ? new EncryptedLocalCacheStore_1.EncryptedLocalCacheStore(localCacheStore, {
115
+ cacheKey: preDecryptedMaterial.cacheEncryptionKey,
116
+ cryptoAdapter: this.cryptoAdapter,
117
+ logger: this.logger.createChild("EncryptedCache"),
118
+ })
119
+ : new EncryptedLocalCacheStore_1.EncryptedLocalCacheStore(localCacheStore, currentUserPassword, this.cryptoAdapter, this.logger.createChild("EncryptedCache"));
56
120
  this.cacheManager = new CacheManager_1.CacheManager(encryptedStore, undefined, this.logger.createChild("CacheManager"));
57
121
  }
58
122
  }
@@ -76,6 +140,12 @@ class BaseMindooTenant {
76
140
  * Should be called when the tenant is being closed.
77
141
  */
78
142
  async disposeCacheManager() {
143
+ // Unsubscribe from KeyBag changes alongside cache teardown. Callers
144
+ // already use `disposeCacheManager` as the "tenant is closing" signal,
145
+ // so this keeps lifecycle hooks in one place. Safe to call multiple
146
+ // times - the disposer is nulled out after the first call.
147
+ this.unsubscribeKeyBagChanges?.();
148
+ this.unsubscribeKeyBagChanges = null;
79
149
  if (this.cacheManager) {
80
150
  await this.cacheManager.dispose();
81
151
  this.cacheManager = null;
@@ -84,6 +154,16 @@ class BaseMindooTenant {
84
154
  getCryptoAdapter() {
85
155
  return this.cryptoAdapter;
86
156
  }
157
+ normalizeTimeTravelDate(value) {
158
+ if (value == null || value === "") {
159
+ return null;
160
+ }
161
+ const timestamp = value instanceof Date ? value.getTime() : typeof value === "number" ? value : Date.parse(value);
162
+ if (!Number.isFinite(timestamp)) {
163
+ throw new Error(`Invalid timeTravelDate: ${String(value)}`);
164
+ }
165
+ return timestamp;
166
+ }
87
167
  getFactory() {
88
168
  return this.factory;
89
169
  }
@@ -356,40 +436,171 @@ class BaseMindooTenant {
356
436
  throw new Error(`User "${currentUser.username}" does not have tenant access yet; the tenant admin must grant access first.`);
357
437
  }
358
438
  async openDB(id, options) {
439
+ const validDbId = (0, databaseIdValidation_1.validateDatabaseId)(id, "dbId");
359
440
  // Enforce admin-only mode for directory database - this is a security invariant
360
441
  // The directory database must only accept entries signed by the admin key
361
- const effectiveOptions = id === "directory"
442
+ const effectiveOptions = validDbId === "directory"
362
443
  ? { ...options, adminOnlyDb: true }
363
444
  : options ?? {};
364
- await this.assertCurrentUserCanOpenDB(id);
445
+ const normalizedTimeTravelDate = this.normalizeTimeTravelDate(effectiveOptions.timeTravelDate);
446
+ const databaseCacheKey = normalizedTimeTravelDate == null
447
+ ? validDbId
448
+ : `${validDbId}::tt:${normalizedTimeTravelDate}`;
449
+ await this.assertCurrentUserCanOpenDB(validDbId);
365
450
  // Return cached database if it exists
366
- const cached = this.databaseCache.get(id);
451
+ const cached = this.databaseCache.get(databaseCacheKey);
367
452
  if (cached) {
368
453
  // For directory DB, verify admin-only flag matches (defensive check)
369
- if (id === "directory" && !cached.isAdminOnlyDb()) {
454
+ if (validDbId === "directory" && !cached.isAdminOnlyDb()) {
370
455
  throw new Error("Directory database was cached without adminOnlyDb - this should never happen");
371
456
  }
457
+ // Live databases must reflect the current KeyBag state before they
458
+ // are handed back to callers. Time-travel snapshots are immutable
459
+ // historical views and intentionally do not participate in
460
+ // visibility reconciliation, so we skip the call for them.
461
+ if (normalizedTimeTravelDate == null) {
462
+ await this.reconcileKeyBagChanges();
463
+ }
372
464
  return cached;
373
465
  }
374
466
  // Extract store options and DB-specific options
375
- const { adminOnlyDb, attachmentConfig, documentCacheConfig, snapshotConfig, performanceCallback, ...storeOptions } = effectiveOptions;
376
- // Create the database stores using the factory
377
- const { docStore, attachmentStore } = this.storeFactory.createStore(id, storeOptions);
467
+ const { adminOnlyDb, timeTravelDate: _timeTravelDate, attachmentConfig, documentCacheConfig, snapshotConfig, performanceCallback, ...storeOptions } = effectiveOptions;
468
+ // Snapshot opens can share an already-open live store instance. This keeps
469
+ // in-memory factories usable while the snapshot DB maintains isolated state.
470
+ const liveDbForSnapshot = normalizedTimeTravelDate == null
471
+ ? null
472
+ : this.databaseCache.get(validDbId);
473
+ const { docStore, attachmentStore } = liveDbForSnapshot
474
+ ? {
475
+ docStore: liveDbForSnapshot.getStore(),
476
+ attachmentStore: liveDbForSnapshot.getAttachmentStore(),
477
+ }
478
+ : this.storeFactory.createStore(validDbId, storeOptions);
378
479
  const dbLogger = this.logger.createChild("BaseMindooDB");
379
- const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, documentCacheConfig, snapshotConfig, adminOnlyDb ?? false, dbLogger, performanceCallback);
380
- if (this.cacheManager) {
480
+ const db = new BaseMindooDB_1.BaseMindooDB(this, docStore, attachmentStore, attachmentConfig, documentCacheConfig, snapshotConfig, adminOnlyDb ?? false, dbLogger, performanceCallback, normalizedTimeTravelDate);
481
+ if (this.cacheManager && normalizedTimeTravelDate == null) {
381
482
  db.setCacheManager(this.cacheManager);
382
483
  }
383
484
  await db.initialize();
384
485
  // Cache the database for future use
385
- this.databaseCache.set(id, db);
486
+ this.databaseCache.set(databaseCacheKey, db);
386
487
  return db;
387
488
  }
489
+ /**
490
+ * Replay any pending KeyBag mutations for this tenant and refresh open
491
+ * live databases so document visibility matches the current key set.
492
+ *
493
+ * Designed as the single barrier callers can `await` to be certain that
494
+ * subsequent reads observe the latest add/remove of doc keys. Behaviour:
495
+ *
496
+ * - Idempotent and concurrency-safe. Concurrent callers share the same
497
+ * in-flight reconcile via {@link keyBagReconcilePromise}, and a second
498
+ * call after the cursor catches up is a fast no-op.
499
+ * - Cursor-based. Replays from the persisted cursor in
500
+ * {@link keyBagChangeCursor}, so missed live notifications are
501
+ * automatically recovered.
502
+ * - Tenant-scoped. Events for other tenants in a shared KeyBag are
503
+ * consumed (the cursor still advances) but ignored.
504
+ * - Invalidates the cached tenant default key when its add/remove was
505
+ * observed, so the next encrypt/decrypt call re-reads the bag.
506
+ * - Skips time-travel databases. Those views are immutable historical
507
+ * cuts and never participate in visibility reconciliation.
508
+ */
509
+ async reconcileKeyBagChanges() {
510
+ if (this.keyBagReconcilePromise) {
511
+ return this.keyBagReconcilePromise;
512
+ }
513
+ this.keyBagReconcilePromise = (async () => {
514
+ let sawTenantDocKeyChange = false;
515
+ let latestCursor = this.keyBagChangeCursor;
516
+ // Consume every pending event so the cursor advances even for
517
+ // unrelated tenants sharing the same bag. We only need to react to
518
+ // doc keys for our own tenant though.
519
+ for await (const event of this.keyBag.iterateChangesSince(this.keyBagChangeCursor)) {
520
+ latestCursor = { changeSeq: event.changeSeq };
521
+ if (event.type !== "doc" || event.tenantId !== this.tenantId) {
522
+ continue;
523
+ }
524
+ sawTenantDocKeyChange = true;
525
+ if (event.keyId === types_1.DEFAULT_TENANT_KEY_ID) {
526
+ // The cached plaintext default key may now refer to a rotated
527
+ // or removed version; drop it so the next access re-resolves.
528
+ this.decryptedTenantKeyCache = undefined;
529
+ }
530
+ }
531
+ this.keyBagChangeCursor = latestCursor;
532
+ if (!sawTenantDocKeyChange) {
533
+ return;
534
+ }
535
+ // Notify every open live database; time-travel views are read-only
536
+ // historical snapshots and intentionally skip reconciliation.
537
+ for (const db of this.databaseCache.values()) {
538
+ if (db.isTimeTravelMode()) {
539
+ continue;
540
+ }
541
+ const reconcile = db.reconcileKeyVisibility;
542
+ if (typeof reconcile === "function") {
543
+ await reconcile.call(db);
544
+ }
545
+ }
546
+ })();
547
+ try {
548
+ await this.keyBagReconcilePromise;
549
+ }
550
+ finally {
551
+ this.keyBagReconcilePromise = null;
552
+ }
553
+ }
388
554
  createDocSignerFor(signKey) {
389
555
  this.logger.debug(`Creating MindooDocSigner for signing key pair`);
390
556
  const signerLogger = this.logger.createChild("MindooDocSigner");
391
557
  return new MindooDocSigner_1.MindooDocSigner(this, signKey, signerLogger);
392
558
  }
559
+ async exportJoinResponseDocKeyVersions(keyId, sharePassword) {
560
+ const scopedKeyId = `doc:${this.tenantId}:${keyId}`;
561
+ const details = (await this.keyBag.listKeyDetails())
562
+ .filter((detail) => detail.scopedKeyId === scopedKeyId)
563
+ .sort((a, b) => a.versionIndex - b.versionIndex);
564
+ if (details.length === 0) {
565
+ throw new Error(`Failed to export document key "${keyId}" for tenant "${this.tenantId}"`);
566
+ }
567
+ const versions = [];
568
+ for (const detail of details) {
569
+ const encryptedKey = await this.keyBag.encryptAndExportKeyVersion("doc", this.tenantId, keyId, detail.versionIndex, sharePassword);
570
+ if (!encryptedKey) {
571
+ throw new Error(`Failed to export document key "${keyId}" version ${detail.versionIndex} for tenant "${this.tenantId}"`);
572
+ }
573
+ const { createdAt: encryptedKeyCreatedAt, ...encryptedKeyPayload } = encryptedKey;
574
+ versions.push({
575
+ createdAt: detail.createdAt ?? encryptedKeyCreatedAt,
576
+ encryptedKey: encryptedKeyPayload,
577
+ });
578
+ }
579
+ return { keyId, versions };
580
+ }
581
+ resolveJoinResponseDocKeyIds(options) {
582
+ const requestedKeyIds = options.sharedDocKeyIds ?? [types_1.PUBLIC_INFOS_KEY_ID, types_1.DEFAULT_TENANT_KEY_ID];
583
+ const keyIds = new Set();
584
+ keyIds.add(types_1.PUBLIC_INFOS_KEY_ID);
585
+ for (const keyId of requestedKeyIds) {
586
+ const normalizedKeyId = keyId.trim();
587
+ if (normalizedKeyId) {
588
+ keyIds.add(normalizedKeyId);
589
+ }
590
+ }
591
+ return [
592
+ types_1.PUBLIC_INFOS_KEY_ID,
593
+ ...Array.from(keyIds)
594
+ .filter((keyId) => keyId !== types_1.PUBLIC_INFOS_KEY_ID)
595
+ .sort((a, b) => {
596
+ if (a === types_1.DEFAULT_TENANT_KEY_ID)
597
+ return -1;
598
+ if (b === types_1.DEFAULT_TENANT_KEY_ID)
599
+ return 1;
600
+ return a.localeCompare(b);
601
+ }),
602
+ ];
603
+ }
393
604
  async approveJoinRequest(joinRequest, options) {
394
605
  // Parse the join request if it's a URI string
395
606
  let request;
@@ -416,24 +627,17 @@ class BaseMindooTenant {
416
627
  userEncryptionPublicKey: request.encryptionPublicKey,
417
628
  };
418
629
  await directory.registerUser(publicUserId, options.adminSigningKey, options.adminPassword);
419
- // 2. Export the tenant key encrypted with the share password
420
- const encryptedTenantKey = await this.keyBag.encryptAndExportKey("doc", this.tenantId, types_1.DEFAULT_TENANT_KEY_ID, options.sharePassword);
421
- if (!encryptedTenantKey) {
422
- throw new Error(`Failed to export tenant key for tenant "${this.tenantId}"`);
423
- }
424
- // 3. Export the $publicinfos key encrypted with the share password
425
- const encryptedPublicInfosKey = await this.keyBag.encryptAndExportKey("doc", this.tenantId, types_1.PUBLIC_INFOS_KEY_ID, options.sharePassword);
426
- if (!encryptedPublicInfosKey) {
427
- throw new Error(`Failed to export $publicinfos key`);
428
- }
429
- // 4. Build the join response
630
+ // 2. Export selected document keys encrypted with the share password.
631
+ // `$publicinfos` is mandatory because the joining user needs it for
632
+ // directory access; all selected key ids include every rotated version.
633
+ const encryptedDocKeys = await Promise.all(this.resolveJoinResponseDocKeyIds(options).map((keyId) => this.exportJoinResponseDocKeyVersions(keyId, options.sharePassword)));
634
+ // 3. Build the join response
430
635
  const joinResponse = {
431
- v: 1,
636
+ v: 2,
432
637
  tenantId: this.tenantId,
433
638
  adminSigningPublicKey: this.administrationPublicKey,
434
639
  adminEncryptionPublicKey: this.administrationEncryptionPublicKey,
435
- encryptedTenantKey,
436
- encryptedPublicInfosKey,
640
+ encryptedDocKeys,
437
641
  };
438
642
  if (options.serverUrl) {
439
643
  joinResponse.serverUrl = options.serverUrl;
@@ -580,13 +784,14 @@ class BaseMindooTenant {
580
784
  * Create a remote store connected to a MindooDB server, ready for sync.
581
785
  */
582
786
  async connectToServer(serverUrl, dbId, storeKind = types_1.StoreKind.docs) {
583
- console.log(`[connectToServer] Connecting to server: ${serverUrl}, db: ${dbId}, storeKind: ${storeKind}`);
584
- this.logger.info(`Connecting to server: ${serverUrl}, db: ${dbId}, storeKind: ${storeKind}`);
787
+ const validDbId = (0, databaseIdValidation_1.validateDatabaseId)(dbId, "dbId");
788
+ console.log(`[connectToServer] Connecting to server: ${serverUrl}, db: ${validDbId}, storeKind: ${storeKind}`);
789
+ this.logger.info(`Connecting to server: ${serverUrl}, db: ${validDbId}, storeKind: ${storeKind}`);
585
790
  const normalizedServerUrl = serverUrl.replace(/\/$/, "");
586
- const cacheKey = `${normalizedServerUrl}::${dbId}::${storeKind}`;
791
+ const cacheKey = `${normalizedServerUrl}::${validDbId}::${storeKind}`;
587
792
  const cachedStore = this.remoteStoreCache.get(cacheKey);
588
793
  if (cachedStore) {
589
- this.logger.debug(`Reusing cached remote store for ${normalizedServerUrl}, db: ${dbId}`);
794
+ this.logger.debug(`Reusing cached remote store for ${normalizedServerUrl}, db: ${validDbId}`);
590
795
  return cachedStore;
591
796
  }
592
797
  const remoteStorePromise = (async () => {
@@ -598,7 +803,7 @@ class BaseMindooTenant {
598
803
  const transport = new HttpTransport({
599
804
  baseUrl,
600
805
  tenantId: this.tenantId,
601
- dbId,
806
+ dbId: validDbId,
602
807
  storeKind,
603
808
  }, this.logger.createChild("HttpTransport"));
604
809
  // Get the current user's decrypted signing key
@@ -606,9 +811,9 @@ class BaseMindooTenant {
606
811
  // Get the current user's decrypted RSA encryption private key (for decrypting entries)
607
812
  const decryptedEncryptionKey = await this.getDecryptedEncryptionKey();
608
813
  // Create the client network store
609
- const store = new ClientNetworkContentAddressedStore(dbId, storeKind, transport, this.cryptoAdapter, this.currentUser.username, signingKey, decryptedEncryptionKey, this.logger.createChild(`ClientNetworkStore:${dbId}:${storeKind}`));
610
- console.log(`[connectToServer] ✓ Connected to server for db "${dbId}"`);
611
- this.logger.info(`Connected to server for db "${dbId}"`);
814
+ const store = new ClientNetworkContentAddressedStore(validDbId, storeKind, transport, this.cryptoAdapter, this.currentUser.username, signingKey, decryptedEncryptionKey, this.logger.createChild(`ClientNetworkStore:${validDbId}:${storeKind}`));
815
+ console.log(`[connectToServer] ✓ Connected to server for db "${validDbId}"`);
816
+ this.logger.info(`Connected to server for db "${validDbId}"`);
612
817
  return store;
613
818
  })();
614
819
  this.remoteStoreCache.set(cacheKey, remoteStorePromise);
@@ -752,6 +957,16 @@ class BaseMindooTenant {
752
957
  throw error;
753
958
  }
754
959
  }
960
+ /**
961
+ * Stable fingerprint of the doc-type keys this tenant currently has
962
+ * access to, delegating to {@link KeyBag.getDocKeyFingerprint} scoped
963
+ * to this tenant. Persisted by {@link BaseMindooDB} alongside its cache
964
+ * checkpoint so warm starts can skip a full metadata scan when the
965
+ * KeyBag composition has not changed.
966
+ */
967
+ async getDocKeyFingerprint() {
968
+ return this.keyBag.getDocKeyFingerprint(this.tenantId);
969
+ }
755
970
  /**
756
971
  * Get the symmetric key for a given key ID.
757
972
  * This is a protected helper method that can be used by subclasses.