mindoodb 0.0.84 → 0.0.86

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 (264) hide show
  1. package/README-server.md +1 -0
  2. package/README.md +6 -4
  3. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts +15 -0
  4. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.d.ts.map +1 -1
  5. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js +75 -1
  6. package/dist/appendonlystores/network/ClientNetworkContentAddressedStore.js.map +1 -1
  7. package/dist/appendonlystores/network/HttpTransport.d.ts +9 -1
  8. package/dist/appendonlystores/network/HttpTransport.d.ts.map +1 -1
  9. package/dist/appendonlystores/network/HttpTransport.js +38 -10
  10. package/dist/appendonlystores/network/HttpTransport.js.map +1 -1
  11. package/dist/cloudflare/DurableObjectSql.d.ts +56 -0
  12. package/dist/cloudflare/DurableObjectSql.d.ts.map +1 -0
  13. package/dist/cloudflare/DurableObjectSql.js +180 -0
  14. package/dist/cloudflare/DurableObjectSql.js.map +1 -0
  15. package/dist/cloudflare/MindooDatabaseObject.d.ts +86 -0
  16. package/dist/cloudflare/MindooDatabaseObject.d.ts.map +1 -0
  17. package/dist/cloudflare/MindooDatabaseObject.js +614 -0
  18. package/dist/cloudflare/MindooDatabaseObject.js.map +1 -0
  19. package/dist/cloudflare/MindooServerObject.d.ts +63 -0
  20. package/dist/cloudflare/MindooServerObject.d.ts.map +1 -0
  21. package/dist/cloudflare/MindooServerObject.js +521 -0
  22. package/dist/cloudflare/MindooServerObject.js.map +1 -0
  23. package/dist/cloudflare/WorkersCryptoAdapter.d.ts +7 -0
  24. package/dist/cloudflare/WorkersCryptoAdapter.d.ts.map +1 -0
  25. package/dist/cloudflare/WorkersCryptoAdapter.js +14 -0
  26. package/dist/cloudflare/WorkersCryptoAdapter.js.map +1 -0
  27. package/dist/cloudflare/host.d.ts +49 -0
  28. package/dist/cloudflare/host.d.ts.map +1 -0
  29. package/dist/cloudflare/host.js +104 -0
  30. package/dist/cloudflare/host.js.map +1 -0
  31. package/dist/cloudflare/http.d.ts +91 -0
  32. package/dist/cloudflare/http.d.ts.map +1 -0
  33. package/dist/cloudflare/http.js +266 -0
  34. package/dist/cloudflare/http.js.map +1 -0
  35. package/dist/cloudflare/index.d.ts +12 -0
  36. package/dist/cloudflare/index.d.ts.map +1 -0
  37. package/dist/cloudflare/index.js +20 -0
  38. package/dist/cloudflare/index.js.map +1 -0
  39. package/dist/cloudflare/storeRpc.d.ts +22 -0
  40. package/dist/cloudflare/storeRpc.d.ts.map +1 -0
  41. package/dist/cloudflare/storeRpc.js +85 -0
  42. package/dist/cloudflare/storeRpc.js.map +1 -0
  43. package/dist/cloudflare/types.d.ts +101 -0
  44. package/dist/cloudflare/types.d.ts.map +1 -0
  45. package/dist/cloudflare/types.js +10 -0
  46. package/dist/cloudflare/types.js.map +1 -0
  47. package/dist/cloudflare/worker.d.ts +25 -0
  48. package/dist/cloudflare/worker.d.ts.map +1 -0
  49. package/dist/cloudflare/worker.js +170 -0
  50. package/dist/cloudflare/worker.js.map +1 -0
  51. package/dist/core/BaseMindooDB.d.ts +43 -1
  52. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  53. package/dist/core/BaseMindooDB.js +209 -87
  54. package/dist/core/BaseMindooDB.js.map +1 -1
  55. package/dist/core/BaseMindooTenant.d.ts +14 -0
  56. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  57. package/dist/core/BaseMindooTenant.js +46 -0
  58. package/dist/core/BaseMindooTenant.js.map +1 -1
  59. package/dist/core/BaseMindooTenantDirectory.d.ts +10 -0
  60. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  61. package/dist/core/BaseMindooTenantDirectory.js +79 -8
  62. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  63. package/dist/core/ChangeDocDraft.d.ts +133 -0
  64. package/dist/core/ChangeDocDraft.d.ts.map +1 -0
  65. package/dist/core/ChangeDocDraft.js +492 -0
  66. package/dist/core/ChangeDocDraft.js.map +1 -0
  67. package/dist/core/appendonlystores/InMemoryContentAddressedStore.d.ts.map +1 -1
  68. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js +1 -0
  69. package/dist/core/appendonlystores/InMemoryContentAddressedStore.js.map +1 -1
  70. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +8 -0
  71. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  72. package/dist/core/appendonlystores/network/AuthenticationService.js +19 -0
  73. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  74. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
  75. package/dist/core/appendonlystores/network/IrohStreamIO.js +3 -23
  76. package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
  77. package/dist/core/appendonlystores/network/NetworkTransport.d.ts +7 -1
  78. package/dist/core/appendonlystores/network/NetworkTransport.d.ts.map +1 -1
  79. package/dist/core/appendonlystores/network/types.d.ts +23 -0
  80. package/dist/core/appendonlystores/network/types.d.ts.map +1 -1
  81. package/dist/core/appendonlystores/network/types.js.map +1 -1
  82. package/dist/core/appendonlystores/rpc/storeRpcCodec.d.ts.map +1 -1
  83. package/dist/core/appendonlystores/rpc/storeRpcCodec.js +3 -23
  84. package/dist/core/appendonlystores/rpc/storeRpcCodec.js.map +1 -1
  85. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts.map +1 -1
  86. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js +21 -30
  87. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js.map +1 -1
  88. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts +37 -1
  89. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts.map +1 -1
  90. package/dist/core/appendonlystores/sqlite/SqliteDriver.js +99 -40
  91. package/dist/core/appendonlystores/sqlite/SqliteDriver.js.map +1 -1
  92. package/dist/core/appendonlystores/syncStores.d.ts.map +1 -1
  93. package/dist/core/appendonlystores/syncStores.js +253 -168
  94. package/dist/core/appendonlystores/syncStores.js.map +1 -1
  95. package/dist/core/appendonlystores/types.d.ts +8 -0
  96. package/dist/core/appendonlystores/types.d.ts.map +1 -1
  97. package/dist/core/appendonlystores/types.js.map +1 -1
  98. package/dist/core/expressions/evaluateExpression.d.ts +8 -0
  99. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
  100. package/dist/core/expressions/evaluateExpression.js +29 -4
  101. package/dist/core/expressions/evaluateExpression.js.map +1 -1
  102. package/dist/core/expressions/metadata.js +4 -4
  103. package/dist/core/expressions/metadata.js.map +1 -1
  104. package/dist/core/types.d.ts +61 -2
  105. package/dist/core/types.d.ts.map +1 -1
  106. package/dist/core/types.js.map +1 -1
  107. package/dist/core/uri/MindooURI.d.ts.map +1 -1
  108. package/dist/core/uri/MindooURI.js +3 -24
  109. package/dist/core/uri/MindooURI.js.map +1 -1
  110. package/dist/core/utils/base64.d.ts +17 -0
  111. package/dist/core/utils/base64.d.ts.map +1 -0
  112. package/dist/core/utils/base64.js +54 -0
  113. package/dist/core/utils/base64.js.map +1 -0
  114. package/dist/core/utils/idGeneration.d.ts.map +1 -1
  115. package/dist/core/utils/idGeneration.js +14 -3
  116. package/dist/core/utils/idGeneration.js.map +1 -1
  117. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.d.ts.map +1 -1
  118. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js +1 -0
  119. package/dist/node/appendonlystores/BasicOnDiskContentAddressedStore.js.map +1 -1
  120. package/dist/node/server/FsServerDataStore.d.ts +23 -0
  121. package/dist/node/server/FsServerDataStore.d.ts.map +1 -0
  122. package/dist/node/server/FsServerDataStore.js +59 -0
  123. package/dist/node/server/FsServerDataStore.js.map +1 -0
  124. package/dist/node/server/IrohServerEndpoint.d.ts +1 -1
  125. package/dist/node/server/IrohServerEndpoint.d.ts.map +1 -1
  126. package/dist/node/server/IrohServerRpc.d.ts +5 -5
  127. package/dist/node/server/IrohServerRpc.d.ts.map +1 -1
  128. package/dist/node/server/IrohServerRpc.js +1 -1
  129. package/dist/node/server/IrohServerRpc.js.map +1 -1
  130. package/dist/node/server/MindooDBServer.d.ts +29 -150
  131. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  132. package/dist/node/server/MindooDBServer.js +160 -1568
  133. package/dist/node/server/MindooDBServer.js.map +1 -1
  134. package/dist/node/server/PurgedDocRegistry.d.ts +4 -27
  135. package/dist/node/server/PurgedDocRegistry.d.ts.map +1 -1
  136. package/dist/node/server/PurgedDocRegistry.js +6 -83
  137. package/dist/node/server/PurgedDocRegistry.js.map +1 -1
  138. package/dist/node/server/StoreFactory.d.ts +1 -1
  139. package/dist/node/server/StoreFactory.d.ts.map +1 -1
  140. package/dist/node/server/TenantManager.d.ts +16 -284
  141. package/dist/node/server/TenantManager.d.ts.map +1 -1
  142. package/dist/node/server/TenantManager.js +35 -1168
  143. package/dist/node/server/TenantManager.js.map +1 -1
  144. package/dist/node/server/add-to-network.js +1 -1
  145. package/dist/node/server/add-to-network.js.map +1 -1
  146. package/dist/node/server/compressionMiddleware.d.ts +14 -0
  147. package/dist/node/server/compressionMiddleware.d.ts.map +1 -1
  148. package/dist/node/server/compressionMiddleware.js +46 -3
  149. package/dist/node/server/compressionMiddleware.js.map +1 -1
  150. package/dist/node/server/config.d.ts +11 -15
  151. package/dist/node/server/config.d.ts.map +1 -1
  152. package/dist/node/server/config.js +30 -245
  153. package/dist/node/server/config.js.map +1 -1
  154. package/dist/node/server/expressAdapter.d.ts +23 -0
  155. package/dist/node/server/expressAdapter.d.ts.map +1 -0
  156. package/dist/node/server/expressAdapter.js +108 -0
  157. package/dist/node/server/expressAdapter.js.map +1 -0
  158. package/dist/node/server/nativeIrohStreamIO.d.ts +1 -1
  159. package/dist/node/server/nativeIrohStreamIO.d.ts.map +1 -1
  160. package/dist/node/server/peer/FileClusterAuditStorage.d.ts +16 -0
  161. package/dist/node/server/peer/FileClusterAuditStorage.d.ts.map +1 -0
  162. package/dist/node/server/peer/FileClusterAuditStorage.js +39 -0
  163. package/dist/node/server/peer/FileClusterAuditStorage.js.map +1 -0
  164. package/dist/node/server/peer/IrohPeerLink.d.ts +1 -1
  165. package/dist/node/server/peer/IrohPeerLink.d.ts.map +1 -1
  166. package/dist/node/server/peer/routes.d.ts +10 -44
  167. package/dist/node/server/peer/routes.d.ts.map +1 -1
  168. package/dist/node/server/peer/routes.js +33 -275
  169. package/dist/node/server/peer/routes.js.map +1 -1
  170. package/dist/node/server/resolveServerPassword.js +1 -1
  171. package/dist/node/server/resolveServerPassword.js.map +1 -1
  172. package/dist/node/server/server.js +1 -1
  173. package/dist/node/server/server.js.map +1 -1
  174. package/dist/node/server/serverinit.js +1 -1
  175. package/dist/node/server/serverinit.js.map +1 -1
  176. package/dist/server/CapabilityMatcher.d.ts +60 -0
  177. package/dist/server/CapabilityMatcher.d.ts.map +1 -0
  178. package/dist/server/CapabilityMatcher.js +138 -0
  179. package/dist/server/CapabilityMatcher.js.map +1 -0
  180. package/dist/server/DeviceDiscoveryService.d.ts +36 -0
  181. package/dist/server/DeviceDiscoveryService.d.ts.map +1 -0
  182. package/dist/server/DeviceDiscoveryService.js +165 -0
  183. package/dist/server/DeviceDiscoveryService.js.map +1 -0
  184. package/dist/server/PurgedDocRegistry.d.ts +35 -0
  185. package/dist/server/PurgedDocRegistry.d.ts.map +1 -0
  186. package/dist/server/PurgedDocRegistry.js +90 -0
  187. package/dist/server/PurgedDocRegistry.js.map +1 -0
  188. package/dist/server/ServerDataStore.d.ts +67 -0
  189. package/dist/server/ServerDataStore.d.ts.map +1 -0
  190. package/dist/server/ServerDataStore.js +88 -0
  191. package/dist/server/ServerDataStore.js.map +1 -0
  192. package/dist/server/SyncEventBus.d.ts +39 -0
  193. package/dist/server/SyncEventBus.d.ts.map +1 -0
  194. package/dist/server/SyncEventBus.js +41 -0
  195. package/dist/server/SyncEventBus.js.map +1 -0
  196. package/dist/server/SystemAdminAuth.d.ts +82 -0
  197. package/dist/server/SystemAdminAuth.d.ts.map +1 -0
  198. package/dist/server/SystemAdminAuth.js +228 -0
  199. package/dist/server/SystemAdminAuth.js.map +1 -0
  200. package/dist/server/TenantManager.d.ts +330 -0
  201. package/dist/server/TenantManager.d.ts.map +1 -0
  202. package/dist/server/TenantManager.js +1186 -0
  203. package/dist/server/TenantManager.js.map +1 -0
  204. package/dist/server/http/MindooDBServerCore.d.ts +313 -0
  205. package/dist/server/http/MindooDBServerCore.d.ts.map +1 -0
  206. package/dist/server/http/MindooDBServerCore.js +1403 -0
  207. package/dist/server/http/MindooDBServerCore.js.map +1 -0
  208. package/dist/server/http/types.d.ts +89 -0
  209. package/dist/server/http/types.d.ts.map +1 -0
  210. package/dist/server/http/types.js +29 -0
  211. package/dist/server/http/types.js.map +1 -0
  212. package/dist/server/index.d.ts +32 -0
  213. package/dist/server/index.d.ts.map +1 -0
  214. package/dist/server/index.js +76 -0
  215. package/dist/server/index.js.map +1 -0
  216. package/dist/server/peer/ClusterAuditLog.d.ts +71 -0
  217. package/dist/server/peer/ClusterAuditLog.d.ts.map +1 -0
  218. package/dist/server/peer/ClusterAuditLog.js +81 -0
  219. package/dist/server/peer/ClusterAuditLog.js.map +1 -0
  220. package/dist/server/peer/ClusterJobRunner.d.ts +43 -0
  221. package/dist/server/peer/ClusterJobRunner.d.ts.map +1 -0
  222. package/dist/server/peer/ClusterJobRunner.js +119 -0
  223. package/dist/server/peer/ClusterJobRunner.js.map +1 -0
  224. package/dist/server/peer/ClusterManager.d.ts +149 -0
  225. package/dist/server/peer/ClusterManager.d.ts.map +1 -0
  226. package/dist/server/peer/ClusterManager.js +292 -0
  227. package/dist/server/peer/ClusterManager.js.map +1 -0
  228. package/dist/server/peer/PeerAuthService.d.ts +69 -0
  229. package/dist/server/peer/PeerAuthService.d.ts.map +1 -0
  230. package/dist/server/peer/PeerAuthService.js +208 -0
  231. package/dist/server/peer/PeerAuthService.js.map +1 -0
  232. package/dist/server/peer/PeerReplicator.d.ts +303 -0
  233. package/dist/server/peer/PeerReplicator.d.ts.map +1 -0
  234. package/dist/server/peer/PeerReplicator.js +1071 -0
  235. package/dist/server/peer/PeerReplicator.js.map +1 -0
  236. package/dist/server/peer/clusterHttp.d.ts +113 -0
  237. package/dist/server/peer/clusterHttp.d.ts.map +1 -0
  238. package/dist/server/peer/clusterHttp.js +290 -0
  239. package/dist/server/peer/clusterHttp.js.map +1 -0
  240. package/dist/server/peer/types.d.ts +244 -0
  241. package/dist/server/peer/types.d.ts.map +1 -0
  242. package/dist/server/peer/types.js +189 -0
  243. package/dist/server/peer/types.js.map +1 -0
  244. package/dist/server/rateLimits.d.ts +119 -0
  245. package/dist/server/rateLimits.d.ts.map +1 -0
  246. package/dist/server/rateLimits.js +159 -0
  247. package/dist/server/rateLimits.js.map +1 -0
  248. package/dist/server/serverConfig.d.ts +37 -0
  249. package/dist/server/serverConfig.d.ts.map +1 -0
  250. package/dist/server/serverConfig.js +251 -0
  251. package/dist/server/serverConfig.js.map +1 -0
  252. package/dist/server/timestampProxy.d.ts +129 -0
  253. package/dist/server/timestampProxy.d.ts.map +1 -0
  254. package/dist/server/timestampProxy.js +361 -0
  255. package/dist/server/timestampProxy.js.map +1 -0
  256. package/dist/server/types.d.ts +267 -0
  257. package/dist/server/types.d.ts.map +1 -0
  258. package/dist/server/types.js +56 -0
  259. package/dist/server/types.js.map +1 -0
  260. package/dist/server/validation.d.ts +43 -0
  261. package/dist/server/validation.d.ts.map +1 -0
  262. package/dist/server/validation.js +104 -0
  263. package/dist/server/validation.js.map +1 -0
  264. package/package.json +9 -1
@@ -5,6 +5,7 @@ exports.BaseMindooDB = exports.CUSTOM_DOC_ID_INITIAL_CHANGE_BYTES = void 0;
5
5
  // React Native: native Rust (react-native-automerge-generated)
6
6
  // Browser/Node.js: WASM (@automerge/automerge/slim)
7
7
  const automerge_adapter_1 = require("./automerge-adapter");
8
+ const ChangeDocDraft_1 = require("./ChangeDocDraft");
8
9
  const values_1 = require("./values");
9
10
  const storeEntryTime_1 = require("./storeEntryTime");
10
11
  const changeAttribution_1 = require("./changeAttribution");
@@ -293,6 +294,8 @@ class BaseMindooDB {
293
294
  this.nextChangeSeq = 1;
294
295
  // Cache of loaded documents: Map<docId, InternalDoc>
295
296
  this.docCache = new Map();
297
+ /** Document loads in progress; see {@link loadDocumentInternal}. */
298
+ this.documentLoadsInFlight = new Map();
296
299
  this.wrappedInternalDocs = new WeakMap();
297
300
  /**
298
301
  * Per-document count of local writes since the last snapshot eligibility
@@ -362,6 +365,25 @@ class BaseMindooDB {
362
365
  */
363
366
  this.revisionFeedDocCache = new Map();
364
367
  this.revisionFeedCacheLimit = 256;
368
+ /**
369
+ * Revisions of documents whose fold suffix holds un-witnessed entries, keyed
370
+ * by the exact fold (entry order, witness state and prefix). The feed
371
+ * re-discovers such entries on every resume, since their trusted time is the
372
+ * provisional `now`; while nothing about the document changed, only that
373
+ * trusted time differs, so the revisions are re-emitted from here instead
374
+ * of being loaded, verified, decrypted and folded again. Without it, every
375
+ * pass re-folded every un-witnessed document: registering N users offline
376
+ * cost O(N) per registration. An entry is replaced on any change and
377
+ * dropped once the document has no un-witnessed entries left.
378
+ *
379
+ * Each revision holds its own document state, so the cache is bounded: a
380
+ * document with a longer suffix, or one arriving when the cache is full, is
381
+ * folded every pass as before.
382
+ */
383
+ this.provisionalRevisionCache = new Map();
384
+ this.provisionalRevisionCacheSize = 0;
385
+ this.provisionalRevisionCacheLimit = 4096;
386
+ this.provisionalRevisionCacheDocLimit = 32;
365
387
  /**
366
388
  * Window size for batching encrypted-payload loads while folding a document's
367
389
  * revision suffix ({@link foldDocRevisions}): one {@link ContentAddressedStore.getEntries}
@@ -5174,6 +5196,27 @@ class BaseMindooDB {
5174
5196
  if (firstChangedIdx < 0) {
5175
5197
  return;
5176
5198
  }
5199
+ // Unchanged document whose suffix is still provisional: re-emit the
5200
+ // previous fold with the current trusted times (see provisionalRevisionCache).
5201
+ const suffix = ttSorted.slice(firstChangedIdx);
5202
+ const hasProvisional = suffix.some((r) => !r.witnessed);
5203
+ const foldKey = [
5204
+ ttSorted.slice(0, firstChangedIdx).map((r) => r.meta.id).join(","),
5205
+ suffix.map((r) => `${r.meta.id}:${r.meta.receivedAt ?? "-"}`).join(","),
5206
+ ].join("|");
5207
+ const cachedFold = this.provisionalRevisionCache.get(docId);
5208
+ if (cachedFold && cachedFold.key === foldKey) {
5209
+ for (let i = 0; i < cachedFold.revisions.length; i++) {
5210
+ yield { ...cachedFold.revisions[i], trustedTime: suffix[i].tt, cursor: resumeCursor };
5211
+ }
5212
+ return;
5213
+ }
5214
+ if (cachedFold) {
5215
+ this.provisionalRevisionCache.delete(docId);
5216
+ this.provisionalRevisionCacheSize -= cachedFold.revisions.length;
5217
+ }
5218
+ const foldedRevisions = [];
5219
+ let foldComplete = true;
5177
5220
  // Origin time and key id come from the document's create entry, independent
5178
5221
  // of fold order (under the trusted-time rule un-witnessed entries can sort
5179
5222
  // before the create, so we must not rely on seeing the create first).
@@ -5223,6 +5266,7 @@ class BaseMindooDB {
5223
5266
  // Absent from the loaded window means missing in the store or it failed
5224
5267
  // signature verification (already logged during the parallel prepare).
5225
5268
  this.logger.warn(`Revision entry ${meta.id} unavailable (missing or invalid), skipping`);
5269
+ foldComplete = false;
5226
5270
  continue;
5227
5271
  }
5228
5272
  const entryData = ready.entry;
@@ -5247,16 +5291,24 @@ class BaseMindooDB {
5247
5291
  // this document's replay entries seen up to and including this one.
5248
5292
  isDeleted: this.computeIsDeletedFromMetadata([...seenMeta]),
5249
5293
  };
5250
- yield {
5294
+ const revision = {
5251
5295
  docId,
5252
5296
  entryId: meta.id,
5253
5297
  doc: this.wrapDocument(internalDoc),
5254
5298
  entryType: meta.entryType,
5255
- trustedTime: rev.tt,
5256
5299
  witnessed: rev.witnessed,
5257
5300
  createdByPublicKey: meta.createdByPublicKey,
5258
- cursor: resumeCursor,
5259
5301
  };
5302
+ foldedRevisions.push(revision);
5303
+ yield { ...revision, trustedTime: rev.tt, cursor: resumeCursor };
5304
+ }
5305
+ // Reached only when the consumer took every revision of this document.
5306
+ if (hasProvisional &&
5307
+ foldComplete &&
5308
+ foldedRevisions.length <= this.provisionalRevisionCacheDocLimit &&
5309
+ this.provisionalRevisionCacheSize + foldedRevisions.length <= this.provisionalRevisionCacheLimit) {
5310
+ this.provisionalRevisionCache.set(docId, { key: foldKey, revisions: foldedRevisions });
5311
+ this.provisionalRevisionCacheSize += foldedRevisions.length;
5260
5312
  }
5261
5313
  // Cache the doc's full merged state for the next incremental advance.
5262
5314
  this.putRevisionFeedCache(docId, {
@@ -8135,8 +8187,6 @@ class BaseMindooDB {
8135
8187
  //
8136
8188
  // We use a two-phase approach: the callback can do async work and collect
8137
8189
  // what needs to be changed, then we apply those changes in Automerge.change()
8138
- const pendingChanges = new Map();
8139
- const pendingDeletions = new Set();
8140
8190
  // Track pending attachment operations
8141
8191
  const pendingAttachmentAdditions = [];
8142
8192
  const pendingAttachmentRemovals = new Set();
@@ -8144,19 +8194,6 @@ class BaseMindooDB {
8144
8194
  const pendingAttachmentAppends = new Map();
8145
8195
  // Map of attachmentId -> extraction result for setAttachmentExtractedText
8146
8196
  const pendingAttachmentTextUpdates = new Map();
8147
- // incrementCounter() calls, applied in call order after the field
8148
- // assignments so they can target counters assigned in the same callback.
8149
- const pendingCounterIncrements = [];
8150
- // Assignments and deletions are applied before the increments, so an
8151
- // assignment or deletion of a field supersedes the increments queued on it
8152
- // (or below it) earlier in the callback; later increments still apply.
8153
- const dropPendingCounterIncrements = (field) => {
8154
- for (let i = pendingCounterIncrements.length - 1; i >= 0; i--) {
8155
- if (pendingCounterIncrements[i].path[0] === field) {
8156
- pendingCounterIncrements.splice(i, 1);
8157
- }
8158
- }
8159
- };
8160
8197
  // Reference to db for closures
8161
8198
  const db = this;
8162
8199
  // Guard flag to prevent operations after callback completes
@@ -8167,6 +8204,15 @@ class BaseMindooDB {
8167
8204
  throw new Error(`${methodName}() cannot be called after changeDoc() callback has completed. Document changes can only be made within the callback.`);
8168
8205
  }
8169
8206
  };
8207
+ // getData() edits a lazily built shadow copy and logs every edit, at any
8208
+ // depth, for replay inside Automerge.change() below.
8209
+ const draft = new ChangeDocDraft_1.ChangeDocDraft(internalDoc.doc, {
8210
+ scalarKind: (value) => db.automergeScalarKind(value),
8211
+ readScalar: (value) => db.readAutomergeScalar(value),
8212
+ makeCounter: (value) => new automerge_adapter_1.Automerge.Counter(value),
8213
+ isRichTextInBase: (path) => db.isRichTextAt(internalDoc.doc, path),
8214
+ assertActive: throwIfCallbackInactive,
8215
+ });
8170
8216
  // Create a document wrapper that collects changes
8171
8217
  const collectingDoc = {
8172
8218
  getDatabase: () => this,
@@ -8184,62 +8230,7 @@ class BaseMindooDB {
8184
8230
  isEncryptedFor: (users) => (0, sealedTypes_1.isSealedKeyId)(internalDoc.decryptionKeyId) &&
8185
8231
  (0, recipients_1.isPayloadEncryptedFor)(internalDoc.doc, internalDoc.sealedRecipients, Array.isArray(users) ? users : [users]),
8186
8232
  getHeads: () => automerge_adapter_1.Automerge.getHeads(internalDoc.doc),
8187
- getData: () => {
8188
- // Return a proxy that collects property assignments and deletions
8189
- const currentData = internalDoc.doc;
8190
- return new Proxy(currentData, {
8191
- set: (target, prop, value) => {
8192
- throwIfCallbackInactive("set property");
8193
- if (typeof prop === "string") {
8194
- dropPendingCounterIncrements(prop);
8195
- // If this property was marked for deletion, remove it from deletions
8196
- pendingDeletions.delete(prop);
8197
- // Track the change
8198
- pendingChanges.set(prop, value);
8199
- // NOTE: Don't set on target immediately - Automerge requires changes
8200
- // to be made inside Automerge.change(). Pending changes are applied later.
8201
- }
8202
- return true;
8203
- },
8204
- deleteProperty: (target, prop) => {
8205
- throwIfCallbackInactive("delete property");
8206
- if (typeof prop === "string") {
8207
- dropPendingCounterIncrements(prop);
8208
- // Mark for deletion
8209
- pendingDeletions.add(prop);
8210
- // Remove from pending changes if it was there
8211
- pendingChanges.delete(prop);
8212
- // NOTE: Don't delete from target immediately - Automerge requires changes
8213
- // to be made inside Automerge.change(). Pending deletions are applied later.
8214
- }
8215
- return true;
8216
- },
8217
- get: (target, prop) => {
8218
- // If marked for deletion, return undefined
8219
- if (typeof prop === "string" && pendingDeletions.has(prop)) {
8220
- return undefined;
8221
- }
8222
- // Check pending changes first, then target
8223
- if (typeof prop === "string" && pendingChanges.has(prop)) {
8224
- return pendingChanges.get(prop);
8225
- }
8226
- // Counters read as numbers and immutable strings as strings here
8227
- // too; change counters with incrementCounter().
8228
- return db.readAutomergeScalar(target[prop]);
8229
- },
8230
- has: (target, prop) => {
8231
- // If marked for deletion, it doesn't exist
8232
- if (typeof prop === "string" && pendingDeletions.has(prop)) {
8233
- return false;
8234
- }
8235
- // Check pending changes first, then target
8236
- if (typeof prop === "string" && pendingChanges.has(prop)) {
8237
- return true;
8238
- }
8239
- return prop in target;
8240
- },
8241
- });
8242
- },
8233
+ getData: () => draft.getRoot(),
8243
8234
  // ========== Attachment Write Methods (work in changeDoc context) ==========
8244
8235
  addAttachment: async (fileData, fileName, mimeType, keyId) => {
8245
8236
  throwIfCallbackInactive("addAttachment");
@@ -8354,12 +8345,25 @@ class BaseMindooDB {
8354
8345
  }
8355
8346
  },
8356
8347
  // ========== Counter Write Methods ==========
8348
+ updateText: (path, value) => {
8349
+ const segments = typeof path === "string" ? [path] : [...path];
8350
+ db.validateJsonPath(segments, "updateText");
8351
+ if (draft.getFieldType(segments) === "richText") {
8352
+ throw new Error(`updateText: '${segments.map(String).join(".")}' is rich text (marks or blocks); use applyRichTextPatch() or applyRichTextStepsPatch()`);
8353
+ }
8354
+ draft.updateText(segments, value);
8355
+ },
8356
+ getFieldType: (path) => {
8357
+ const segments = typeof path === "string" ? [path] : [...path];
8358
+ db.validateJsonPath(segments, "getFieldType");
8359
+ return draft.getFieldType(segments);
8360
+ },
8357
8361
  incrementCounter: (path, delta) => {
8358
8362
  throwIfCallbackInactive("incrementCounter");
8359
8363
  const segments = typeof path === "string" ? [path] : [...path];
8360
8364
  db.validateJsonPath(segments, "incrementCounter");
8361
8365
  (0, values_1.assertCounterAmount)(delta, "incrementCounter delta");
8362
- pendingCounterIncrements.push({ path: segments, delta });
8366
+ draft.incrementCounter(segments, delta);
8363
8367
  },
8364
8368
  // ========== Attachment Read Methods (also work in changeDoc context) ==========
8365
8369
  getAttachments: () => {
@@ -8394,20 +8398,14 @@ class BaseMindooDB {
8394
8398
  let newDoc;
8395
8399
  try {
8396
8400
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, (automergeDoc) => {
8397
- // Apply all pending changes (sets/updates)
8398
- for (const [key, value] of pendingChanges) {
8399
- this.assignHydrated(automergeDoc, key, this.hydrateTypedValues(value));
8400
- }
8401
- // Apply all pending deletions
8402
- for (const key of pendingDeletions) {
8403
- delete automergeDoc[key];
8404
- }
8401
+ // Replay the callback's field edits and counter increments in the
8402
+ // order they were made: an assignment after an increment replaces
8403
+ // it, an increment after an assignment adds to the new counter, and
8404
+ // a list index means the element it meant at that moment.
8405
+ this.applyChangeDocDraftOps(automergeDoc, draft.ops);
8405
8406
  // Engine-internal edits that need the raw Automerge document, e.g.
8406
8407
  // restoreDocumentToEntry's rich-text spans.
8407
8408
  internalMutation?.(automergeDoc);
8408
- for (const { path, delta } of pendingCounterIncrements) {
8409
- this.incrementCounterAtPath(automergeDoc, path, delta);
8410
- }
8411
8409
  // Apply pending attachment changes
8412
8410
  if (pendingAttachmentAdditions.length > 0 ||
8413
8411
  pendingAttachmentRemovals.size > 0 ||
@@ -8512,6 +8510,96 @@ class BaseMindooDB {
8512
8510
  }
8513
8511
  await Promise.all(pendingAttachmentAdditions.map((ref) => this.clearPendingAttachmentUploadLedger(ref.attachmentId)));
8514
8512
  }
8513
+ /** "counter" / "atomic" for the CRDT scalars that read as plain values, otherwise null. */
8514
+ automergeScalarKind(value) {
8515
+ if (this.isAutomergeCounter(value))
8516
+ return "counter";
8517
+ if (this.isAutomergeImmutableString(value))
8518
+ return "atomic";
8519
+ return null;
8520
+ }
8521
+ /** Whether the text at `path` carries marks or block markers (rich text). */
8522
+ isRichTextAt(automergeDoc, path) {
8523
+ if (typeof this.readValueAtPath(automergeDoc, path) !== "string")
8524
+ return false;
8525
+ const spans = automerge_adapter_1.Automerge.spans(automergeDoc, path);
8526
+ return spans.some((span) => span.type === "block" || (span.marks !== undefined && Object.keys(span.marks).length > 0));
8527
+ }
8528
+ fieldTypeAt(automergeDoc, path) {
8529
+ if (path.length === 0)
8530
+ return "map";
8531
+ let parent = automergeDoc;
8532
+ for (const segment of path.slice(0, -1)) {
8533
+ parent = parent?.[segment];
8534
+ if (parent === null || typeof parent !== "object" || this.automergeScalarKind(parent) !== null)
8535
+ return undefined;
8536
+ }
8537
+ const key = path[path.length - 1];
8538
+ if (!Object.prototype.hasOwnProperty.call(parent, key))
8539
+ return undefined;
8540
+ const value = parent[key];
8541
+ if (typeof value === "string")
8542
+ return this.isRichTextAt(automergeDoc, path) ? "richText" : "text";
8543
+ return (0, ChangeDocDraft_1.fieldTypeOf)(value, { scalarKind: (v) => this.automergeScalarKind(v) });
8544
+ }
8545
+ async getFieldType(doc, path) {
8546
+ const segments = typeof path === "string" ? [path] : [...path];
8547
+ this.validateJsonPath(segments, "getFieldType");
8548
+ const docId = doc.getId();
8549
+ let internalDoc = this.wrappedInternalDocs.get(doc) ?? this.getCachedDocument(docId);
8550
+ if (!internalDoc) {
8551
+ const loadedDoc = await this.loadDocumentInternal(docId);
8552
+ if (!loadedDoc) {
8553
+ throw new errors_1.DocumentNotFoundError(docId);
8554
+ }
8555
+ internalDoc = loadedDoc;
8556
+ }
8557
+ return this.fieldTypeAt(internalDoc.doc, segments);
8558
+ }
8559
+ /** Replays the edits a changeDoc() callback made through getData(), see ChangeDocDraft. */
8560
+ applyChangeDocDraftOps(automergeDoc, ops) {
8561
+ const containerAt = (path) => {
8562
+ let current = automergeDoc;
8563
+ for (const segment of path)
8564
+ current = current[segment];
8565
+ return current;
8566
+ };
8567
+ for (const op of ops) {
8568
+ if (op.kind === "counter") {
8569
+ this.incrementCounterAtPath(automergeDoc, op.path, op.delta);
8570
+ continue;
8571
+ }
8572
+ if (op.kind === "splice") {
8573
+ const list = containerAt(op.path);
8574
+ const values = this.hydrateTypedValues(op.items);
8575
+ list.splice(op.index, op.deleteCount, ...this.withoutNestedFloats(values));
8576
+ values.forEach((value, offset) => {
8577
+ if (value instanceof automerge_adapter_1.Automerge.Float64)
8578
+ list[op.index + offset] = new automerge_adapter_1.Automerge.Float64(value.value);
8579
+ else
8580
+ this.restoreNestedFloats(list[op.index + offset], value);
8581
+ });
8582
+ continue;
8583
+ }
8584
+ const parent = containerAt(op.path.slice(0, -1));
8585
+ const key = op.path[op.path.length - 1];
8586
+ if (op.kind === "delete") {
8587
+ delete parent[key];
8588
+ continue;
8589
+ }
8590
+ const current = parent[key];
8591
+ if (op.kind === "text" && typeof current === "string") {
8592
+ // Splice only the changed middle, so the edit merges with concurrent
8593
+ // typing instead of replacing the whole text.
8594
+ const splice = (0, ChangeDocDraft_1.diffTextSplice)(current, op.value);
8595
+ if (splice) {
8596
+ automerge_adapter_1.Automerge.splice(automergeDoc, op.path, splice.index, splice.deleteCount, splice.insert);
8597
+ }
8598
+ continue;
8599
+ }
8600
+ this.assignHydrated(parent, key, this.hydrateTypedValues(op.value));
8601
+ }
8602
+ }
8515
8603
  validateTextPatch(patch) {
8516
8604
  if (!Array.isArray(patch.path) || patch.path.length === 0) {
8517
8605
  throw new Error("Text patch path must contain at least one segment");
@@ -11751,7 +11839,33 @@ class BaseMindooDB {
11751
11839
  this.docCache.delete(docId);
11752
11840
  this.dirtyDocIds.delete(docId);
11753
11841
  }
11754
- async loadDocumentInternal(docId) {
11842
+ /**
11843
+ * Materialize a document, sharing a load that is already running for it.
11844
+ *
11845
+ * Several readers often want the same document at the same moment: the
11846
+ * summary buffer and the full-text index follow the same changefeed after
11847
+ * every sync page, each with its own prefetch window. Without sharing, both
11848
+ * windows miss the L1 cache for documents the other is still loading and
11849
+ * each materializes them, so a first sync with both indexes paid every
11850
+ * document twice. A load is only shared while no newer entries for the
11851
+ * document were indexed since it started, so no caller gets a state older
11852
+ * than its call.
11853
+ */
11854
+ loadDocumentInternal(docId) {
11855
+ const generation = this.docEntryGeneration.get(docId) ?? 0;
11856
+ const inFlight = this.documentLoadsInFlight.get(docId);
11857
+ if (inFlight && inFlight.generation === generation) {
11858
+ return inFlight.promise;
11859
+ }
11860
+ const promise = this.loadDocumentUncoalesced(docId).finally(() => {
11861
+ if (this.documentLoadsInFlight.get(docId)?.promise === promise) {
11862
+ this.documentLoadsInFlight.delete(docId);
11863
+ }
11864
+ });
11865
+ this.documentLoadsInFlight.set(docId, { generation, promise });
11866
+ return promise;
11867
+ }
11868
+ async loadDocumentUncoalesced(docId) {
11755
11869
  const startedAt = Date.now();
11756
11870
  const cacheCheckStartedAt = Date.now();
11757
11871
  // Short-circuit on the visibility layer: a doc marked
@@ -12339,6 +12453,14 @@ class BaseMindooDB {
12339
12453
  setAttachmentExtractedText: () => {
12340
12454
  throw new Error("setAttachmentExtractedText() can only be called within changeDoc() callback");
12341
12455
  },
12456
+ getFieldType: (path) => {
12457
+ const segments = typeof path === "string" ? [path] : [...path];
12458
+ this.validateJsonPath(segments, "getFieldType");
12459
+ return this.fieldTypeAt(internalDoc.doc, segments);
12460
+ },
12461
+ updateText: () => {
12462
+ throw new Error("updateText() can only be called within changeDoc() callback; use applyJsonPatch() with textSplice outside of it");
12463
+ },
12342
12464
  incrementCounter: () => {
12343
12465
  throw new Error("incrementCounter() can only be called within changeDoc() callback; use applyJsonPatch() with counterIncrement outside of it");
12344
12466
  },