mindoodb 0.0.85 → 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 (235) hide show
  1. package/README-server.md +1 -0
  2. package/README.md +6 -4
  3. package/dist/cloudflare/DurableObjectSql.d.ts +56 -0
  4. package/dist/cloudflare/DurableObjectSql.d.ts.map +1 -0
  5. package/dist/cloudflare/DurableObjectSql.js +180 -0
  6. package/dist/cloudflare/DurableObjectSql.js.map +1 -0
  7. package/dist/cloudflare/MindooDatabaseObject.d.ts +86 -0
  8. package/dist/cloudflare/MindooDatabaseObject.d.ts.map +1 -0
  9. package/dist/cloudflare/MindooDatabaseObject.js +614 -0
  10. package/dist/cloudflare/MindooDatabaseObject.js.map +1 -0
  11. package/dist/cloudflare/MindooServerObject.d.ts +63 -0
  12. package/dist/cloudflare/MindooServerObject.d.ts.map +1 -0
  13. package/dist/cloudflare/MindooServerObject.js +521 -0
  14. package/dist/cloudflare/MindooServerObject.js.map +1 -0
  15. package/dist/cloudflare/WorkersCryptoAdapter.d.ts +7 -0
  16. package/dist/cloudflare/WorkersCryptoAdapter.d.ts.map +1 -0
  17. package/dist/cloudflare/WorkersCryptoAdapter.js +14 -0
  18. package/dist/cloudflare/WorkersCryptoAdapter.js.map +1 -0
  19. package/dist/cloudflare/host.d.ts +49 -0
  20. package/dist/cloudflare/host.d.ts.map +1 -0
  21. package/dist/cloudflare/host.js +104 -0
  22. package/dist/cloudflare/host.js.map +1 -0
  23. package/dist/cloudflare/http.d.ts +91 -0
  24. package/dist/cloudflare/http.d.ts.map +1 -0
  25. package/dist/cloudflare/http.js +266 -0
  26. package/dist/cloudflare/http.js.map +1 -0
  27. package/dist/cloudflare/index.d.ts +12 -0
  28. package/dist/cloudflare/index.d.ts.map +1 -0
  29. package/dist/cloudflare/index.js +20 -0
  30. package/dist/cloudflare/index.js.map +1 -0
  31. package/dist/cloudflare/storeRpc.d.ts +22 -0
  32. package/dist/cloudflare/storeRpc.d.ts.map +1 -0
  33. package/dist/cloudflare/storeRpc.js +85 -0
  34. package/dist/cloudflare/storeRpc.js.map +1 -0
  35. package/dist/cloudflare/types.d.ts +101 -0
  36. package/dist/cloudflare/types.d.ts.map +1 -0
  37. package/dist/cloudflare/types.js +10 -0
  38. package/dist/cloudflare/types.js.map +1 -0
  39. package/dist/cloudflare/worker.d.ts +25 -0
  40. package/dist/cloudflare/worker.d.ts.map +1 -0
  41. package/dist/cloudflare/worker.js +170 -0
  42. package/dist/cloudflare/worker.js.map +1 -0
  43. package/dist/core/BaseMindooDB.d.ts +28 -1
  44. package/dist/core/BaseMindooDB.d.ts.map +1 -1
  45. package/dist/core/BaseMindooDB.js +180 -86
  46. package/dist/core/BaseMindooDB.js.map +1 -1
  47. package/dist/core/BaseMindooTenant.d.ts +14 -0
  48. package/dist/core/BaseMindooTenant.d.ts.map +1 -1
  49. package/dist/core/BaseMindooTenant.js +46 -0
  50. package/dist/core/BaseMindooTenant.js.map +1 -1
  51. package/dist/core/BaseMindooTenantDirectory.d.ts +10 -0
  52. package/dist/core/BaseMindooTenantDirectory.d.ts.map +1 -1
  53. package/dist/core/BaseMindooTenantDirectory.js +79 -8
  54. package/dist/core/BaseMindooTenantDirectory.js.map +1 -1
  55. package/dist/core/ChangeDocDraft.d.ts +133 -0
  56. package/dist/core/ChangeDocDraft.d.ts.map +1 -0
  57. package/dist/core/ChangeDocDraft.js +492 -0
  58. package/dist/core/ChangeDocDraft.js.map +1 -0
  59. package/dist/core/appendonlystores/network/AuthenticationService.d.ts +8 -0
  60. package/dist/core/appendonlystores/network/AuthenticationService.d.ts.map +1 -1
  61. package/dist/core/appendonlystores/network/AuthenticationService.js +19 -0
  62. package/dist/core/appendonlystores/network/AuthenticationService.js.map +1 -1
  63. package/dist/core/appendonlystores/network/IrohStreamIO.d.ts.map +1 -1
  64. package/dist/core/appendonlystores/network/IrohStreamIO.js +3 -23
  65. package/dist/core/appendonlystores/network/IrohStreamIO.js.map +1 -1
  66. package/dist/core/appendonlystores/rpc/storeRpcCodec.d.ts.map +1 -1
  67. package/dist/core/appendonlystores/rpc/storeRpcCodec.js +3 -23
  68. package/dist/core/appendonlystores/rpc/storeRpcCodec.js.map +1 -1
  69. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.d.ts.map +1 -1
  70. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js +21 -30
  71. package/dist/core/appendonlystores/sqlite/SqliteContentAddressedStore.js.map +1 -1
  72. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts +37 -1
  73. package/dist/core/appendonlystores/sqlite/SqliteDriver.d.ts.map +1 -1
  74. package/dist/core/appendonlystores/sqlite/SqliteDriver.js +99 -40
  75. package/dist/core/appendonlystores/sqlite/SqliteDriver.js.map +1 -1
  76. package/dist/core/expressions/evaluateExpression.d.ts +8 -0
  77. package/dist/core/expressions/evaluateExpression.d.ts.map +1 -1
  78. package/dist/core/expressions/evaluateExpression.js +29 -4
  79. package/dist/core/expressions/evaluateExpression.js.map +1 -1
  80. package/dist/core/expressions/metadata.js +4 -4
  81. package/dist/core/expressions/metadata.js.map +1 -1
  82. package/dist/core/types.d.ts +61 -2
  83. package/dist/core/types.d.ts.map +1 -1
  84. package/dist/core/types.js.map +1 -1
  85. package/dist/core/uri/MindooURI.d.ts.map +1 -1
  86. package/dist/core/uri/MindooURI.js +3 -24
  87. package/dist/core/uri/MindooURI.js.map +1 -1
  88. package/dist/core/utils/base64.d.ts +17 -0
  89. package/dist/core/utils/base64.d.ts.map +1 -0
  90. package/dist/core/utils/base64.js +54 -0
  91. package/dist/core/utils/base64.js.map +1 -0
  92. package/dist/core/utils/idGeneration.d.ts.map +1 -1
  93. package/dist/core/utils/idGeneration.js +14 -3
  94. package/dist/core/utils/idGeneration.js.map +1 -1
  95. package/dist/node/server/FsServerDataStore.d.ts +23 -0
  96. package/dist/node/server/FsServerDataStore.d.ts.map +1 -0
  97. package/dist/node/server/FsServerDataStore.js +59 -0
  98. package/dist/node/server/FsServerDataStore.js.map +1 -0
  99. package/dist/node/server/IrohServerEndpoint.d.ts +1 -1
  100. package/dist/node/server/IrohServerEndpoint.d.ts.map +1 -1
  101. package/dist/node/server/IrohServerRpc.d.ts +5 -5
  102. package/dist/node/server/IrohServerRpc.d.ts.map +1 -1
  103. package/dist/node/server/IrohServerRpc.js +1 -1
  104. package/dist/node/server/IrohServerRpc.js.map +1 -1
  105. package/dist/node/server/MindooDBServer.d.ts +29 -150
  106. package/dist/node/server/MindooDBServer.d.ts.map +1 -1
  107. package/dist/node/server/MindooDBServer.js +160 -1573
  108. package/dist/node/server/MindooDBServer.js.map +1 -1
  109. package/dist/node/server/PurgedDocRegistry.d.ts +4 -27
  110. package/dist/node/server/PurgedDocRegistry.d.ts.map +1 -1
  111. package/dist/node/server/PurgedDocRegistry.js +6 -83
  112. package/dist/node/server/PurgedDocRegistry.js.map +1 -1
  113. package/dist/node/server/StoreFactory.d.ts +1 -1
  114. package/dist/node/server/StoreFactory.d.ts.map +1 -1
  115. package/dist/node/server/TenantManager.d.ts +16 -284
  116. package/dist/node/server/TenantManager.d.ts.map +1 -1
  117. package/dist/node/server/TenantManager.js +35 -1168
  118. package/dist/node/server/TenantManager.js.map +1 -1
  119. package/dist/node/server/add-to-network.js +1 -1
  120. package/dist/node/server/add-to-network.js.map +1 -1
  121. package/dist/node/server/config.d.ts +11 -15
  122. package/dist/node/server/config.d.ts.map +1 -1
  123. package/dist/node/server/config.js +30 -245
  124. package/dist/node/server/config.js.map +1 -1
  125. package/dist/node/server/expressAdapter.d.ts +23 -0
  126. package/dist/node/server/expressAdapter.d.ts.map +1 -0
  127. package/dist/node/server/expressAdapter.js +108 -0
  128. package/dist/node/server/expressAdapter.js.map +1 -0
  129. package/dist/node/server/nativeIrohStreamIO.d.ts +1 -1
  130. package/dist/node/server/nativeIrohStreamIO.d.ts.map +1 -1
  131. package/dist/node/server/peer/FileClusterAuditStorage.d.ts +16 -0
  132. package/dist/node/server/peer/FileClusterAuditStorage.d.ts.map +1 -0
  133. package/dist/node/server/peer/FileClusterAuditStorage.js +39 -0
  134. package/dist/node/server/peer/FileClusterAuditStorage.js.map +1 -0
  135. package/dist/node/server/peer/IrohPeerLink.d.ts +1 -1
  136. package/dist/node/server/peer/IrohPeerLink.d.ts.map +1 -1
  137. package/dist/node/server/peer/routes.d.ts +10 -44
  138. package/dist/node/server/peer/routes.d.ts.map +1 -1
  139. package/dist/node/server/peer/routes.js +33 -275
  140. package/dist/node/server/peer/routes.js.map +1 -1
  141. package/dist/node/server/resolveServerPassword.js +1 -1
  142. package/dist/node/server/resolveServerPassword.js.map +1 -1
  143. package/dist/node/server/server.js +1 -1
  144. package/dist/node/server/server.js.map +1 -1
  145. package/dist/node/server/serverinit.js +1 -1
  146. package/dist/node/server/serverinit.js.map +1 -1
  147. package/dist/server/CapabilityMatcher.d.ts +60 -0
  148. package/dist/server/CapabilityMatcher.d.ts.map +1 -0
  149. package/dist/server/CapabilityMatcher.js +138 -0
  150. package/dist/server/CapabilityMatcher.js.map +1 -0
  151. package/dist/server/DeviceDiscoveryService.d.ts +36 -0
  152. package/dist/server/DeviceDiscoveryService.d.ts.map +1 -0
  153. package/dist/server/DeviceDiscoveryService.js +165 -0
  154. package/dist/server/DeviceDiscoveryService.js.map +1 -0
  155. package/dist/server/PurgedDocRegistry.d.ts +35 -0
  156. package/dist/server/PurgedDocRegistry.d.ts.map +1 -0
  157. package/dist/server/PurgedDocRegistry.js +90 -0
  158. package/dist/server/PurgedDocRegistry.js.map +1 -0
  159. package/dist/server/ServerDataStore.d.ts +67 -0
  160. package/dist/server/ServerDataStore.d.ts.map +1 -0
  161. package/dist/server/ServerDataStore.js +88 -0
  162. package/dist/server/ServerDataStore.js.map +1 -0
  163. package/dist/server/SyncEventBus.d.ts +39 -0
  164. package/dist/server/SyncEventBus.d.ts.map +1 -0
  165. package/dist/server/SyncEventBus.js +41 -0
  166. package/dist/server/SyncEventBus.js.map +1 -0
  167. package/dist/server/SystemAdminAuth.d.ts +82 -0
  168. package/dist/server/SystemAdminAuth.d.ts.map +1 -0
  169. package/dist/server/SystemAdminAuth.js +228 -0
  170. package/dist/server/SystemAdminAuth.js.map +1 -0
  171. package/dist/server/TenantManager.d.ts +330 -0
  172. package/dist/server/TenantManager.d.ts.map +1 -0
  173. package/dist/server/TenantManager.js +1186 -0
  174. package/dist/server/TenantManager.js.map +1 -0
  175. package/dist/server/http/MindooDBServerCore.d.ts +313 -0
  176. package/dist/server/http/MindooDBServerCore.d.ts.map +1 -0
  177. package/dist/server/http/MindooDBServerCore.js +1403 -0
  178. package/dist/server/http/MindooDBServerCore.js.map +1 -0
  179. package/dist/server/http/types.d.ts +89 -0
  180. package/dist/server/http/types.d.ts.map +1 -0
  181. package/dist/server/http/types.js +29 -0
  182. package/dist/server/http/types.js.map +1 -0
  183. package/dist/server/index.d.ts +32 -0
  184. package/dist/server/index.d.ts.map +1 -0
  185. package/dist/server/index.js +76 -0
  186. package/dist/server/index.js.map +1 -0
  187. package/dist/server/peer/ClusterAuditLog.d.ts +71 -0
  188. package/dist/server/peer/ClusterAuditLog.d.ts.map +1 -0
  189. package/dist/server/peer/ClusterAuditLog.js +81 -0
  190. package/dist/server/peer/ClusterAuditLog.js.map +1 -0
  191. package/dist/server/peer/ClusterJobRunner.d.ts +43 -0
  192. package/dist/server/peer/ClusterJobRunner.d.ts.map +1 -0
  193. package/dist/server/peer/ClusterJobRunner.js +119 -0
  194. package/dist/server/peer/ClusterJobRunner.js.map +1 -0
  195. package/dist/server/peer/ClusterManager.d.ts +149 -0
  196. package/dist/server/peer/ClusterManager.d.ts.map +1 -0
  197. package/dist/server/peer/ClusterManager.js +292 -0
  198. package/dist/server/peer/ClusterManager.js.map +1 -0
  199. package/dist/server/peer/PeerAuthService.d.ts +69 -0
  200. package/dist/server/peer/PeerAuthService.d.ts.map +1 -0
  201. package/dist/server/peer/PeerAuthService.js +208 -0
  202. package/dist/server/peer/PeerAuthService.js.map +1 -0
  203. package/dist/server/peer/PeerReplicator.d.ts +303 -0
  204. package/dist/server/peer/PeerReplicator.d.ts.map +1 -0
  205. package/dist/server/peer/PeerReplicator.js +1071 -0
  206. package/dist/server/peer/PeerReplicator.js.map +1 -0
  207. package/dist/server/peer/clusterHttp.d.ts +113 -0
  208. package/dist/server/peer/clusterHttp.d.ts.map +1 -0
  209. package/dist/server/peer/clusterHttp.js +290 -0
  210. package/dist/server/peer/clusterHttp.js.map +1 -0
  211. package/dist/server/peer/types.d.ts +244 -0
  212. package/dist/server/peer/types.d.ts.map +1 -0
  213. package/dist/server/peer/types.js +189 -0
  214. package/dist/server/peer/types.js.map +1 -0
  215. package/dist/server/rateLimits.d.ts +119 -0
  216. package/dist/server/rateLimits.d.ts.map +1 -0
  217. package/dist/server/rateLimits.js +159 -0
  218. package/dist/server/rateLimits.js.map +1 -0
  219. package/dist/server/serverConfig.d.ts +37 -0
  220. package/dist/server/serverConfig.d.ts.map +1 -0
  221. package/dist/server/serverConfig.js +251 -0
  222. package/dist/server/serverConfig.js.map +1 -0
  223. package/dist/server/timestampProxy.d.ts +129 -0
  224. package/dist/server/timestampProxy.d.ts.map +1 -0
  225. package/dist/server/timestampProxy.js +361 -0
  226. package/dist/server/timestampProxy.js.map +1 -0
  227. package/dist/server/types.d.ts +267 -0
  228. package/dist/server/types.d.ts.map +1 -0
  229. package/dist/server/types.js +56 -0
  230. package/dist/server/types.js.map +1 -0
  231. package/dist/server/validation.d.ts +43 -0
  232. package/dist/server/validation.d.ts.map +1 -0
  233. package/dist/server/validation.js +104 -0
  234. package/dist/server/validation.js.map +1 -0
  235. 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");
@@ -364,6 +365,25 @@ class BaseMindooDB {
364
365
  */
365
366
  this.revisionFeedDocCache = new Map();
366
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;
367
387
  /**
368
388
  * Window size for batching encrypted-payload loads while folding a document's
369
389
  * revision suffix ({@link foldDocRevisions}): one {@link ContentAddressedStore.getEntries}
@@ -5176,6 +5196,27 @@ class BaseMindooDB {
5176
5196
  if (firstChangedIdx < 0) {
5177
5197
  return;
5178
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;
5179
5220
  // Origin time and key id come from the document's create entry, independent
5180
5221
  // of fold order (under the trusted-time rule un-witnessed entries can sort
5181
5222
  // before the create, so we must not rely on seeing the create first).
@@ -5225,6 +5266,7 @@ class BaseMindooDB {
5225
5266
  // Absent from the loaded window means missing in the store or it failed
5226
5267
  // signature verification (already logged during the parallel prepare).
5227
5268
  this.logger.warn(`Revision entry ${meta.id} unavailable (missing or invalid), skipping`);
5269
+ foldComplete = false;
5228
5270
  continue;
5229
5271
  }
5230
5272
  const entryData = ready.entry;
@@ -5249,16 +5291,24 @@ class BaseMindooDB {
5249
5291
  // this document's replay entries seen up to and including this one.
5250
5292
  isDeleted: this.computeIsDeletedFromMetadata([...seenMeta]),
5251
5293
  };
5252
- yield {
5294
+ const revision = {
5253
5295
  docId,
5254
5296
  entryId: meta.id,
5255
5297
  doc: this.wrapDocument(internalDoc),
5256
5298
  entryType: meta.entryType,
5257
- trustedTime: rev.tt,
5258
5299
  witnessed: rev.witnessed,
5259
5300
  createdByPublicKey: meta.createdByPublicKey,
5260
- cursor: resumeCursor,
5261
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;
5262
5312
  }
5263
5313
  // Cache the doc's full merged state for the next incremental advance.
5264
5314
  this.putRevisionFeedCache(docId, {
@@ -8137,8 +8187,6 @@ class BaseMindooDB {
8137
8187
  //
8138
8188
  // We use a two-phase approach: the callback can do async work and collect
8139
8189
  // what needs to be changed, then we apply those changes in Automerge.change()
8140
- const pendingChanges = new Map();
8141
- const pendingDeletions = new Set();
8142
8190
  // Track pending attachment operations
8143
8191
  const pendingAttachmentAdditions = [];
8144
8192
  const pendingAttachmentRemovals = new Set();
@@ -8146,19 +8194,6 @@ class BaseMindooDB {
8146
8194
  const pendingAttachmentAppends = new Map();
8147
8195
  // Map of attachmentId -> extraction result for setAttachmentExtractedText
8148
8196
  const pendingAttachmentTextUpdates = new Map();
8149
- // incrementCounter() calls, applied in call order after the field
8150
- // assignments so they can target counters assigned in the same callback.
8151
- const pendingCounterIncrements = [];
8152
- // Assignments and deletions are applied before the increments, so an
8153
- // assignment or deletion of a field supersedes the increments queued on it
8154
- // (or below it) earlier in the callback; later increments still apply.
8155
- const dropPendingCounterIncrements = (field) => {
8156
- for (let i = pendingCounterIncrements.length - 1; i >= 0; i--) {
8157
- if (pendingCounterIncrements[i].path[0] === field) {
8158
- pendingCounterIncrements.splice(i, 1);
8159
- }
8160
- }
8161
- };
8162
8197
  // Reference to db for closures
8163
8198
  const db = this;
8164
8199
  // Guard flag to prevent operations after callback completes
@@ -8169,6 +8204,15 @@ class BaseMindooDB {
8169
8204
  throw new Error(`${methodName}() cannot be called after changeDoc() callback has completed. Document changes can only be made within the callback.`);
8170
8205
  }
8171
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
+ });
8172
8216
  // Create a document wrapper that collects changes
8173
8217
  const collectingDoc = {
8174
8218
  getDatabase: () => this,
@@ -8186,62 +8230,7 @@ class BaseMindooDB {
8186
8230
  isEncryptedFor: (users) => (0, sealedTypes_1.isSealedKeyId)(internalDoc.decryptionKeyId) &&
8187
8231
  (0, recipients_1.isPayloadEncryptedFor)(internalDoc.doc, internalDoc.sealedRecipients, Array.isArray(users) ? users : [users]),
8188
8232
  getHeads: () => automerge_adapter_1.Automerge.getHeads(internalDoc.doc),
8189
- getData: () => {
8190
- // Return a proxy that collects property assignments and deletions
8191
- const currentData = internalDoc.doc;
8192
- return new Proxy(currentData, {
8193
- set: (target, prop, value) => {
8194
- throwIfCallbackInactive("set property");
8195
- if (typeof prop === "string") {
8196
- dropPendingCounterIncrements(prop);
8197
- // If this property was marked for deletion, remove it from deletions
8198
- pendingDeletions.delete(prop);
8199
- // Track the change
8200
- pendingChanges.set(prop, value);
8201
- // NOTE: Don't set on target immediately - Automerge requires changes
8202
- // to be made inside Automerge.change(). Pending changes are applied later.
8203
- }
8204
- return true;
8205
- },
8206
- deleteProperty: (target, prop) => {
8207
- throwIfCallbackInactive("delete property");
8208
- if (typeof prop === "string") {
8209
- dropPendingCounterIncrements(prop);
8210
- // Mark for deletion
8211
- pendingDeletions.add(prop);
8212
- // Remove from pending changes if it was there
8213
- pendingChanges.delete(prop);
8214
- // NOTE: Don't delete from target immediately - Automerge requires changes
8215
- // to be made inside Automerge.change(). Pending deletions are applied later.
8216
- }
8217
- return true;
8218
- },
8219
- get: (target, prop) => {
8220
- // If marked for deletion, return undefined
8221
- if (typeof prop === "string" && pendingDeletions.has(prop)) {
8222
- return undefined;
8223
- }
8224
- // Check pending changes first, then target
8225
- if (typeof prop === "string" && pendingChanges.has(prop)) {
8226
- return pendingChanges.get(prop);
8227
- }
8228
- // Counters read as numbers and immutable strings as strings here
8229
- // too; change counters with incrementCounter().
8230
- return db.readAutomergeScalar(target[prop]);
8231
- },
8232
- has: (target, prop) => {
8233
- // If marked for deletion, it doesn't exist
8234
- if (typeof prop === "string" && pendingDeletions.has(prop)) {
8235
- return false;
8236
- }
8237
- // Check pending changes first, then target
8238
- if (typeof prop === "string" && pendingChanges.has(prop)) {
8239
- return true;
8240
- }
8241
- return prop in target;
8242
- },
8243
- });
8244
- },
8233
+ getData: () => draft.getRoot(),
8245
8234
  // ========== Attachment Write Methods (work in changeDoc context) ==========
8246
8235
  addAttachment: async (fileData, fileName, mimeType, keyId) => {
8247
8236
  throwIfCallbackInactive("addAttachment");
@@ -8356,12 +8345,25 @@ class BaseMindooDB {
8356
8345
  }
8357
8346
  },
8358
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
+ },
8359
8361
  incrementCounter: (path, delta) => {
8360
8362
  throwIfCallbackInactive("incrementCounter");
8361
8363
  const segments = typeof path === "string" ? [path] : [...path];
8362
8364
  db.validateJsonPath(segments, "incrementCounter");
8363
8365
  (0, values_1.assertCounterAmount)(delta, "incrementCounter delta");
8364
- pendingCounterIncrements.push({ path: segments, delta });
8366
+ draft.incrementCounter(segments, delta);
8365
8367
  },
8366
8368
  // ========== Attachment Read Methods (also work in changeDoc context) ==========
8367
8369
  getAttachments: () => {
@@ -8396,20 +8398,14 @@ class BaseMindooDB {
8396
8398
  let newDoc;
8397
8399
  try {
8398
8400
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, (automergeDoc) => {
8399
- // Apply all pending changes (sets/updates)
8400
- for (const [key, value] of pendingChanges) {
8401
- this.assignHydrated(automergeDoc, key, this.hydrateTypedValues(value));
8402
- }
8403
- // Apply all pending deletions
8404
- for (const key of pendingDeletions) {
8405
- delete automergeDoc[key];
8406
- }
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);
8407
8406
  // Engine-internal edits that need the raw Automerge document, e.g.
8408
8407
  // restoreDocumentToEntry's rich-text spans.
8409
8408
  internalMutation?.(automergeDoc);
8410
- for (const { path, delta } of pendingCounterIncrements) {
8411
- this.incrementCounterAtPath(automergeDoc, path, delta);
8412
- }
8413
8409
  // Apply pending attachment changes
8414
8410
  if (pendingAttachmentAdditions.length > 0 ||
8415
8411
  pendingAttachmentRemovals.size > 0 ||
@@ -8514,6 +8510,96 @@ class BaseMindooDB {
8514
8510
  }
8515
8511
  await Promise.all(pendingAttachmentAdditions.map((ref) => this.clearPendingAttachmentUploadLedger(ref.attachmentId)));
8516
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
+ }
8517
8603
  validateTextPatch(patch) {
8518
8604
  if (!Array.isArray(patch.path) || patch.path.length === 0) {
8519
8605
  throw new Error("Text patch path must contain at least one segment");
@@ -12367,6 +12453,14 @@ class BaseMindooDB {
12367
12453
  setAttachmentExtractedText: () => {
12368
12454
  throw new Error("setAttachmentExtractedText() can only be called within changeDoc() callback");
12369
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
+ },
12370
12464
  incrementCounter: () => {
12371
12465
  throw new Error("incrementCounter() can only be called within changeDoc() callback; use applyJsonPatch() with counterIncrement outside of it");
12372
12466
  },