mindoodb 0.0.79 → 0.0.81

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.
@@ -7,6 +7,7 @@ exports.BaseMindooDB = exports.CUSTOM_DOC_ID_INITIAL_CHANGE_BYTES = void 0;
7
7
  const automerge_adapter_1 = require("./automerge-adapter");
8
8
  const values_1 = require("./values");
9
9
  const storeEntryTime_1 = require("./storeEntryTime");
10
+ const changeAttribution_1 = require("./changeAttribution");
10
11
  const types_1 = require("./types");
11
12
  const types_2 = require("./types");
12
13
  const types_3 = require("./appendonlystores/types");
@@ -4054,6 +4055,7 @@ class BaseMindooDB {
4054
4055
  }
4055
4056
  this.validateIdPrefixOption("createDocuments", options);
4056
4057
  this.validateAssumeUniqueIdOption("createDocuments", options);
4058
+ (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
4057
4059
  }
4058
4060
  const results = new Array(inputs.length).fill(null);
4059
4061
  const prepared = [];
@@ -4080,7 +4082,7 @@ class BaseMindooDB {
4080
4082
  for (const [key, value] of valueEntries) {
4081
4083
  data[key] = value;
4082
4084
  }
4083
- }, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck);
4085
+ }, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck, undefined, undefined, options.attribution);
4084
4086
  }
4085
4087
  results[index] = existingDoc;
4086
4088
  continue;
@@ -4197,6 +4199,9 @@ class BaseMindooDB {
4197
4199
  // of concurrent same-id creates is then not a concern and the create can
4198
4200
  // follow the generated-ID path (initialValues baked into doc_create).
4199
4201
  const useDeterministicSeed = useCustomDocId && !options.assumeUniqueId;
4202
+ // Validated before anything is written. The deterministic seed change of a
4203
+ // convergent custom id is fixed bytes and cannot carry it.
4204
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
4200
4205
  // Sanitize caller-provided initial values: internal/reserved fields (those
4201
4206
  // starting with "_", e.g. "_attachments") are managed by MindooDB and must
4202
4207
  // not be seeded by callers. `undefined` values are treated as "not set"
@@ -4244,7 +4249,7 @@ class BaseMindooDB {
4244
4249
  if (existing) {
4245
4250
  if (existing.isDeleted) {
4246
4251
  this.logger.debug(`Document ${docId} exists as a tombstone; undeleting existing custom-id document`);
4247
- await this.undeleteDocInternal(docId, signingKeyPair, signingKeyPassword);
4252
+ await this.undeleteDocInternal(docId, signingKeyPair, signingKeyPassword, undefined, options.attribution);
4248
4253
  const undeleted = await this.loadDocumentInternal(docId);
4249
4254
  if (!undeleted || undeleted.isDeleted) {
4250
4255
  throw new Error(`Document ${docId} could not be undeleted`);
@@ -4287,7 +4292,7 @@ class BaseMindooDB {
4287
4292
  this.logger.debug(`Creating initial Automerge change for document ${docId}`);
4288
4293
  const initialDoc = automerge_adapter_1.Automerge.init();
4289
4294
  try {
4290
- newDoc = automerge_adapter_1.Automerge.change(initialDoc, (doc) => {
4295
+ newDoc = automerge_adapter_1.Automerge.change(initialDoc, changeMessage ? { message: changeMessage } : {}, (doc) => {
4291
4296
  // Store metadata in the document payload
4292
4297
  // We need to modify the document to ensure a change is created
4293
4298
  doc._attachments = [];
@@ -4463,6 +4468,7 @@ class BaseMindooDB {
4463
4468
  // out of the deterministic seed, so initialValues fold into doc_create
4464
4469
  // and the document costs a single store entry.
4465
4470
  const useDeterministicSeed = useCustomDocId && !options.assumeUniqueId;
4471
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
4466
4472
  if (useDeterministicSeed && options.recipients !== undefined) {
4467
4473
  throw new Error("createDocuments: recipients is not supported together with a convergent custom id; pass assumeUniqueId");
4468
4474
  }
@@ -4497,7 +4503,7 @@ class BaseMindooDB {
4497
4503
  }
4498
4504
  else {
4499
4505
  const initialDoc = automerge_adapter_1.Automerge.init();
4500
- seedDoc = automerge_adapter_1.Automerge.change(initialDoc, (doc) => {
4506
+ seedDoc = automerge_adapter_1.Automerge.change(initialDoc, changeMessage ? { message: changeMessage } : {}, (doc) => {
4501
4507
  doc._attachments = [];
4502
4508
  if (sealedCreate) {
4503
4509
  (0, recipients_1.applyEncryptForCreate)(doc, sealedCreate.encryptFor);
@@ -4521,7 +4527,7 @@ class BaseMindooDB {
4521
4527
  let finalDoc = seedDoc;
4522
4528
  let valueChangeBytes = null;
4523
4529
  if (useDeterministicSeed && initialValueEntries.length > 0) {
4524
- finalDoc = automerge_adapter_1.Automerge.change(seedDoc, { time: now }, (doc) => {
4530
+ finalDoc = automerge_adapter_1.Automerge.change(seedDoc, { time: now, message: changeMessage }, (doc) => {
4525
4531
  for (const [key, value] of initialValueEntries) {
4526
4532
  this.assignHydrated(doc, key, value);
4527
4533
  }
@@ -4957,12 +4963,14 @@ class BaseMindooDB {
4957
4963
  };
4958
4964
  // Wrap and yield (including lifecycle terminal entries)
4959
4965
  const wrappedDoc = this.wrapDocument(internalDoc);
4966
+ const attribution = this.attributionFromChangePayload(entryMetadata.entryType, decryptedPayload);
4960
4967
  yield {
4961
4968
  changeEntryId: entryMetadata.id,
4962
4969
  doc: wrappedDoc,
4963
4970
  changeCreatedAt: entryMetadata.createdAt,
4964
4971
  changeCreatedByPublicKey: entryMetadata.createdByPublicKey,
4965
4972
  changeDependencyIds: [...entryMetadata.dependencyIds],
4973
+ ...(attribution ? { attribution } : {}),
4966
4974
  };
4967
4975
  }
4968
4976
  }
@@ -5427,6 +5435,40 @@ class BaseMindooDB {
5427
5435
  this.revisionFeedDocCache.delete(oldest);
5428
5436
  }
5429
5437
  }
5438
+ /**
5439
+ * The agent attribution carried by a document change payload (the Automerge
5440
+ * change message), if any. Snapshots and undecodable payloads yield none.
5441
+ */
5442
+ attributionFromChangePayload(entryType, payload) {
5443
+ if (entryType !== "doc_create" &&
5444
+ entryType !== "doc_change" &&
5445
+ entryType !== "doc_delete" &&
5446
+ entryType !== "doc_undelete") {
5447
+ return undefined;
5448
+ }
5449
+ try {
5450
+ return (0, changeAttribution_1.decodeChangeAttribution)(automerge_adapter_1.Automerge.decodeChange(payload).message);
5451
+ }
5452
+ catch {
5453
+ return undefined;
5454
+ }
5455
+ }
5456
+ /**
5457
+ * Verify, decrypt and read the attribution of one stored entry. Entries with
5458
+ * an invalid signature or a payload this replica cannot decrypt yield none.
5459
+ */
5460
+ async readStoreEntryAttribution(entry) {
5461
+ try {
5462
+ if (!(await this.tenant.verifyEntrySignature(entry, entry.encryptedData))) {
5463
+ return undefined;
5464
+ }
5465
+ const payload = await this.tenant.decryptPayload(entry.encryptedData, entry.decryptionKeyId);
5466
+ return this.attributionFromChangePayload(entry.entryType, payload);
5467
+ }
5468
+ catch {
5469
+ return undefined;
5470
+ }
5471
+ }
5430
5472
  async getDocumentHistoryPage(docId, options) {
5431
5473
  const startedAt = Date.now();
5432
5474
  const limit = Math.max(1, Math.floor(options?.limit ?? 100));
@@ -5456,6 +5498,21 @@ class BaseMindooDB {
5456
5498
  // order for same-millisecond entries.
5457
5499
  isDeleted: this.computeIsDeletedFromMetadata(relevantEntries.slice(0, offset + sliceIndex + 1)),
5458
5500
  }));
5501
+ if (options?.includeAttribution && slice.length > 0) {
5502
+ // Opt-in: costs one load + signature check + decrypt per returned entry,
5503
+ // still bounded by the page size.
5504
+ await this.ingestSealedKeyFromEntries(slice);
5505
+ const loaded = await this.store.getEntries(slice.map((entry) => entry.id));
5506
+ const loadedById = new Map(loaded.map((entry) => [entry.id, entry]));
5507
+ for (const pageEntry of entries) {
5508
+ const stored = loadedById.get(pageEntry.entryId);
5509
+ const attribution = stored
5510
+ ? await this.readStoreEntryAttribution(stored)
5511
+ : undefined;
5512
+ if (attribution)
5513
+ pageEntry.attribution = attribution;
5514
+ }
5515
+ }
5459
5516
  const nextOffset = offset + entries.length;
5460
5517
  const hasMore = nextOffset < relevantEntries.length;
5461
5518
  const result = {
@@ -7146,7 +7203,7 @@ class BaseMindooDB {
7146
7203
  return docIds;
7147
7204
  }
7148
7205
  async deleteDocument(docId, options = {}) {
7149
- return this.deleteDocInternal(docId, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck);
7206
+ return this.deleteDocInternal(docId, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck, options.attribution);
7150
7207
  }
7151
7208
  /**
7152
7209
  * Bulk-delete documents in a single append-only store write.
@@ -7167,6 +7224,7 @@ class BaseMindooDB {
7167
7224
  if (docIds.length === 0) {
7168
7225
  return;
7169
7226
  }
7227
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
7170
7228
  // One gate call per document id: inside `userdirectory` the applicable rule
7171
7229
  // depends on the id (personal documents are owned by their creator), so a
7172
7230
  // single batch-wide check would deny an owner deleting their own data.
@@ -7203,7 +7261,7 @@ class BaseMindooDB {
7203
7261
  this.logger.debug(`Skipping bulk delete for ${docId} — not found or already deleted`);
7204
7262
  continue;
7205
7263
  }
7206
- preparedByIndex[index] = await this.prepareLifecycleEntry(internalDoc, "doc_delete", options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck);
7264
+ preparedByIndex[index] = await this.prepareLifecycleEntry(internalDoc, "doc_delete", options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck, changeMessage);
7207
7265
  }
7208
7266
  }));
7209
7267
  const prepared = preparedByIndex.filter((p) => p !== null);
@@ -7223,7 +7281,7 @@ class BaseMindooDB {
7223
7281
  return this.deleteDocument(docId, { signingKeyPair, signingKeyPassword });
7224
7282
  }
7225
7283
  async undeleteDocument(docId, options = {}) {
7226
- return this.undeleteDocInternal(docId, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck);
7284
+ return this.undeleteDocInternal(docId, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck, options.attribution);
7227
7285
  }
7228
7286
  async assertLifecycleMutationAllowed(op, signingKeyPair, signingKeyPassword, documentUsernameHash, docId) {
7229
7287
  this.assertWritable("document lifecycle mutation");
@@ -7241,8 +7299,8 @@ class BaseMindooDB {
7241
7299
  * change that advances `_lastModified`, wraps it in the requested lifecycle
7242
7300
  * StoreEntry type, signs/encrypts it, and updates local caches/indexes.
7243
7301
  */
7244
- async writeLifecycleEntry(internalDoc, entryType, signingKeyPair, signingKeyPassword, bypassPrecheck) {
7245
- const prepared = await this.prepareLifecycleEntry(internalDoc, entryType, signingKeyPair, signingKeyPassword, bypassPrecheck);
7302
+ async writeLifecycleEntry(internalDoc, entryType, signingKeyPair, signingKeyPassword, bypassPrecheck, attribution) {
7303
+ const prepared = await this.prepareLifecycleEntry(internalDoc, entryType, signingKeyPair, signingKeyPassword, bypassPrecheck, (0, changeAttribution_1.encodeChangeAttribution)(attribution));
7246
7304
  await this.putLocalEntries([prepared.fullEntry]);
7247
7305
  await this.applyLifecycleEntryLocally(prepared, { markDirty: true });
7248
7306
  }
@@ -7252,7 +7310,7 @@ class BaseMindooDB {
7252
7310
  * returned `fullEntry` (single or batched `putEntries`) and then call
7253
7311
  * {@link applyLifecycleEntryLocally} to update caches and indexes.
7254
7312
  */
7255
- async prepareLifecycleEntry(internalDoc, entryType, signingKeyPair, signingKeyPassword, bypassPrecheck) {
7313
+ async prepareLifecycleEntry(internalDoc, entryType, signingKeyPair, signingKeyPassword, bypassPrecheck, changeMessage) {
7256
7314
  // Delete and undelete are encoded as normal Automerge changes so the DAG
7257
7315
  // keeps causal ancestry, while the StoreEntry type carries lifecycle intent.
7258
7316
  const useCustomSigningKey = signingKeyPair !== undefined && signingKeyPassword !== undefined;
@@ -7272,7 +7330,7 @@ class BaseMindooDB {
7272
7330
  // `emptyChange`, which always advances the heads with a fresh hash while
7273
7331
  // keeping causal ancestry intact.
7274
7332
  const headsBefore = automerge_adapter_1.Automerge.getHeads(internalDoc.doc);
7275
- let newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => automerge_adapter_1.Automerge.change(doc, { time: now }, (mutableDoc) => {
7333
+ let newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => automerge_adapter_1.Automerge.change(doc, { time: now, message: changeMessage }, (mutableDoc) => {
7276
7334
  if (!Array.isArray(mutableDoc._attachments)) {
7277
7335
  mutableDoc._attachments = [];
7278
7336
  }
@@ -7281,6 +7339,7 @@ class BaseMindooDB {
7281
7339
  if (automerge_adapter_1.Automerge.getHeads(newDoc).every((head) => headsBefore.includes(head))) {
7282
7340
  newDoc = automerge_adapter_1.Automerge.emptyChange(newDoc, {
7283
7341
  time: now,
7342
+ message: changeMessage,
7284
7343
  });
7285
7344
  }
7286
7345
  const changeBytes = automerge_adapter_1.Automerge.getLastLocalChange(newDoc);
@@ -7407,7 +7466,8 @@ class BaseMindooDB {
7407
7466
  * Handles both regular deletion (signed by current user) and
7408
7467
  * deletion with a custom signing key (e.g., for directory operations).
7409
7468
  */
7410
- async deleteDocInternal(docId, signingKeyPair, signingKeyPassword, bypassPrecheck) {
7469
+ async deleteDocInternal(docId, signingKeyPair, signingKeyPassword, bypassPrecheck, attribution) {
7470
+ (0, changeAttribution_1.encodeChangeAttribution)(attribution);
7411
7471
  const useCustomSigningKey = signingKeyPair !== undefined && signingKeyPassword !== undefined;
7412
7472
  this.logger.debug(`Deleting document ${docId}${useCustomSigningKey ? " using custom signing key" : ""}`);
7413
7473
  await this.assertLifecycleMutationAllowed("doc_delete", signingKeyPair, signingKeyPassword, undefined, docId);
@@ -7416,10 +7476,11 @@ class BaseMindooDB {
7416
7476
  if (!internalDoc || internalDoc.isDeleted) {
7417
7477
  throw new Error(`Document ${docId} not found or already deleted`);
7418
7478
  }
7419
- await this.writeLifecycleEntry(internalDoc, "doc_delete", signingKeyPair, signingKeyPassword, bypassPrecheck);
7479
+ await this.writeLifecycleEntry(internalDoc, "doc_delete", signingKeyPair, signingKeyPassword, bypassPrecheck, attribution);
7420
7480
  this.logger.info(`Document ${docId} deleted successfully`);
7421
7481
  }
7422
- async undeleteDocInternal(docId, signingKeyPair, signingKeyPassword, bypassPrecheck) {
7482
+ async undeleteDocInternal(docId, signingKeyPair, signingKeyPassword, bypassPrecheck, attribution) {
7483
+ (0, changeAttribution_1.encodeChangeAttribution)(attribution);
7423
7484
  const useCustomSigningKey = signingKeyPair !== undefined && signingKeyPassword !== undefined;
7424
7485
  this.logger.debug(`Undeleting document ${docId}${useCustomSigningKey ? " using custom signing key" : ""}`);
7425
7486
  const internalDoc = await this.loadDocumentInternal(docId);
@@ -7431,11 +7492,11 @@ class BaseMindooDB {
7431
7492
  this.logger.debug(`Document ${docId} is already alive; undelete is a no-op`);
7432
7493
  return;
7433
7494
  }
7434
- await this.writeLifecycleEntry(internalDoc, "doc_undelete", signingKeyPair, signingKeyPassword, bypassPrecheck);
7495
+ await this.writeLifecycleEntry(internalDoc, "doc_undelete", signingKeyPair, signingKeyPassword, bypassPrecheck, attribution);
7435
7496
  this.logger.info(`Document ${docId} undeleted successfully`);
7436
7497
  }
7437
7498
  async changeDoc(doc, changeFunc, options = {}) {
7438
- return this.changeDocInternal(doc, changeFunc, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck);
7499
+ return this.changeDocInternal(doc, changeFunc, options.signingKeyPair, options.signingKeyPassword, options.bypassAccessControlPrecheck, undefined, undefined, options.attribution);
7439
7500
  }
7440
7501
  async changeDocWithSigningKey(doc, changeFunc, signingKeyPair, signingKeyPassword) {
7441
7502
  return this.changeDoc(doc, changeFunc, {
@@ -7443,8 +7504,9 @@ class BaseMindooDB {
7443
7504
  signingKeyPassword,
7444
7505
  });
7445
7506
  }
7446
- async applyTextPatch(doc, patch) {
7507
+ async applyTextPatch(doc, patch, options = {}) {
7447
7508
  this.assertWritable("applyTextPatch");
7509
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
7448
7510
  const docId = doc.getId();
7449
7511
  this.logger.debug(`===== applyTextPatch called for document ${docId} =====`);
7450
7512
  let internalDoc = this.getCachedDocument(docId);
@@ -7473,10 +7535,10 @@ class BaseMindooDB {
7473
7535
  try {
7474
7536
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => {
7475
7537
  if (baseHeads && baseHeads.length > 0) {
7476
- const result = automerge_adapter_1.Automerge.changeAt(doc, baseHeads, applyEdits);
7538
+ const result = automerge_adapter_1.Automerge.changeAt(doc, baseHeads, { message: changeMessage }, applyEdits);
7477
7539
  return result.newDoc;
7478
7540
  }
7479
- return automerge_adapter_1.Automerge.change(doc, applyEdits);
7541
+ return automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, applyEdits);
7480
7542
  });
7481
7543
  if (this.isDebugEnabled())
7482
7544
  this.logger.debug(`Successfully applied text patch, new document heads: ${JSON.stringify(automerge_adapter_1.Automerge.getHeads(newDoc))}`);
@@ -7500,8 +7562,9 @@ class BaseMindooDB {
7500
7562
  data: wrapped.getData(),
7501
7563
  };
7502
7564
  }
7503
- async applyRichTextPatch(doc, patch) {
7565
+ async applyRichTextPatch(doc, patch, options = {}) {
7504
7566
  this.assertWritable("applyRichTextPatch");
7567
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
7505
7568
  const docId = doc.getId();
7506
7569
  this.logger.debug(`===== applyRichTextPatch called for document ${docId} =====`);
7507
7570
  let internalDoc = this.getCachedDocument(docId);
@@ -7541,9 +7604,9 @@ class BaseMindooDB {
7541
7604
  try {
7542
7605
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => {
7543
7606
  if (patch.baseHeads && patch.baseHeads.length > 0) {
7544
- return this.changeOnForkAt(doc, patch.baseHeads, applySpans);
7607
+ return this.changeOnForkAt(doc, patch.baseHeads, applySpans, changeMessage);
7545
7608
  }
7546
- return automerge_adapter_1.Automerge.change(doc, applySpans);
7609
+ return automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, applySpans);
7547
7610
  });
7548
7611
  if (this.isDebugEnabled())
7549
7612
  this.logger.debug(`Successfully applied rich-text patch, new document heads: ${JSON.stringify(automerge_adapter_1.Automerge.getHeads(newDoc))}`);
@@ -7567,8 +7630,9 @@ class BaseMindooDB {
7567
7630
  data: wrapped.getData(),
7568
7631
  };
7569
7632
  }
7570
- async applyDocumentUpdate(doc, update) {
7633
+ async applyDocumentUpdate(doc, update, options = {}) {
7571
7634
  this.assertWritable("applyDocumentUpdate");
7635
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
7572
7636
  const docId = doc.getId();
7573
7637
  let internalDoc = this.getCachedDocument(docId);
7574
7638
  if (!internalDoc) {
@@ -7621,8 +7685,8 @@ class BaseMindooDB {
7621
7685
  let newDoc;
7622
7686
  try {
7623
7687
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (current) => baseHeads
7624
- ? this.changeOnForkAt(current, baseHeads, applyAll)
7625
- : automerge_adapter_1.Automerge.change(current, applyAll));
7688
+ ? this.changeOnForkAt(current, baseHeads, applyAll, changeMessage)
7689
+ : automerge_adapter_1.Automerge.change(current, { message: changeMessage }, applyAll));
7626
7690
  }
7627
7691
  catch (error) {
7628
7692
  this.logger.error(`Error applying document update for document ${docId}:`, error);
@@ -7681,8 +7745,9 @@ class BaseMindooDB {
7681
7745
  }
7682
7746
  }
7683
7747
  }
7684
- async applyRichTextStepsPatch(doc, patch) {
7748
+ async applyRichTextStepsPatch(doc, patch, options = {}) {
7685
7749
  this.assertWritable("applyRichTextStepsPatch");
7750
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
7686
7751
  const docId = doc.getId();
7687
7752
  this.logger.debug(`===== applyRichTextStepsPatch called for document ${docId} =====`);
7688
7753
  let internalDoc = this.getCachedDocument(docId);
@@ -7734,10 +7799,10 @@ class BaseMindooDB {
7734
7799
  try {
7735
7800
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => {
7736
7801
  if (patch.baseHeads && patch.baseHeads.length > 0) {
7737
- const result = automerge_adapter_1.Automerge.changeAt(doc, patch.baseHeads, applySteps);
7802
+ const result = automerge_adapter_1.Automerge.changeAt(doc, patch.baseHeads, { message: changeMessage }, applySteps);
7738
7803
  return result.newDoc;
7739
7804
  }
7740
- return automerge_adapter_1.Automerge.change(doc, applySteps);
7805
+ return automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, applySteps);
7741
7806
  });
7742
7807
  if (this.isDebugEnabled())
7743
7808
  this.logger.debug(`Successfully applied rich-text steps, new document heads: ${JSON.stringify(automerge_adapter_1.Automerge.getHeads(newDoc))}`);
@@ -7977,8 +8042,9 @@ class BaseMindooDB {
7977
8042
  }
7978
8043
  return internalDoc;
7979
8044
  }
7980
- async applyJsonPatch(doc, patch) {
8045
+ async applyJsonPatch(doc, patch, options = {}) {
7981
8046
  this.assertWritable("applyJsonPatch");
8047
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options.attribution);
7982
8048
  const docId = doc.getId();
7983
8049
  this.logger.debug(`===== applyJsonPatch called for document ${docId} =====`);
7984
8050
  let internalDoc = this.getCachedDocument(docId);
@@ -8003,10 +8069,10 @@ class BaseMindooDB {
8003
8069
  try {
8004
8070
  newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => {
8005
8071
  if (patch.baseHeads && patch.baseHeads.length > 0) {
8006
- const result = automerge_adapter_1.Automerge.changeAt(doc, patch.baseHeads, applyPatch);
8072
+ const result = automerge_adapter_1.Automerge.changeAt(doc, patch.baseHeads, { message: changeMessage }, applyPatch);
8007
8073
  return result.newDoc;
8008
8074
  }
8009
- return automerge_adapter_1.Automerge.change(doc, applyPatch);
8075
+ return automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, applyPatch);
8010
8076
  });
8011
8077
  if (this.isDebugEnabled())
8012
8078
  this.logger.debug(`Successfully applied JSON patch, new document heads: ${JSON.stringify(automerge_adapter_1.Automerge.getHeads(newDoc))}`);
@@ -8030,8 +8096,9 @@ class BaseMindooDB {
8030
8096
  data: wrapped.getData(),
8031
8097
  };
8032
8098
  }
8033
- async changeDocInternal(doc, changeFunc, signingKeyPair, signingKeyPassword, bypassPrecheck, restoredAttachments = [], internalMutation) {
8099
+ async changeDocInternal(doc, changeFunc, signingKeyPair, signingKeyPassword, bypassPrecheck, restoredAttachments = [], internalMutation, attribution) {
8034
8100
  this.assertWritable("changeDoc");
8101
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(attribution);
8035
8102
  const docId = doc.getId();
8036
8103
  if ((signingKeyPair !== undefined) !== (signingKeyPassword !== undefined)) {
8037
8104
  throw new Error("changeDoc: signingKeyPair and signingKeyPassword must be provided together");
@@ -8326,7 +8393,7 @@ class BaseMindooDB {
8326
8393
  const headsBeforeChange = automerge_adapter_1.Automerge.getHeads(internalDoc.doc);
8327
8394
  let newDoc;
8328
8395
  try {
8329
- newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => automerge_adapter_1.Automerge.change(doc, (automergeDoc) => {
8396
+ newDoc = this.runChangeWithOutdatedDocRecovery(internalDoc, (doc) => automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, (automergeDoc) => {
8330
8397
  // Apply all pending changes (sets/updates)
8331
8398
  for (const [key, value] of pendingChanges) {
8332
8399
  this.assignHydrated(automergeDoc, key, this.hydrateTypedValues(value));
@@ -9113,17 +9180,17 @@ class BaseMindooDB {
9113
9180
  * after `baseHeads` (its next sequence number is taken then); in that case a
9114
9181
  * fresh actor writes the one change.
9115
9182
  */
9116
- changeOnForkAt(doc, baseHeads, apply) {
9183
+ changeOnForkAt(doc, baseHeads, apply, changeMessage) {
9117
9184
  const current = automerge_adapter_1.Automerge.getHeads(doc);
9118
9185
  if (current.length === baseHeads.length &&
9119
9186
  current.every((head) => baseHeads.includes(head))) {
9120
- return automerge_adapter_1.Automerge.change(doc, apply);
9187
+ return automerge_adapter_1.Automerge.change(doc, { message: changeMessage }, apply);
9121
9188
  }
9122
9189
  const base = automerge_adapter_1.Automerge.view(doc, baseHeads);
9123
9190
  const actor = automerge_adapter_1.Automerge.getActorId(doc);
9124
9191
  const ownChangeSince = automerge_adapter_1.Automerge.getChanges(base, doc).some((change) => automerge_adapter_1.Automerge.decodeChange(change).actor === actor);
9125
9192
  const fork = automerge_adapter_1.Automerge.clone(base, ownChangeSince ? undefined : { actor });
9126
- const changed = automerge_adapter_1.Automerge.change(fork, apply);
9193
+ const changed = automerge_adapter_1.Automerge.change(fork, { message: changeMessage }, apply);
9127
9194
  return automerge_adapter_1.Automerge.merge(doc, changed);
9128
9195
  }
9129
9196
  runChangeWithOutdatedDocRecovery(internalDoc, apply) {
@@ -9874,6 +9941,7 @@ class BaseMindooDB {
9874
9941
  message: typeof decodedChange.message === "string"
9875
9942
  ? decodedChange.message
9876
9943
  : null,
9944
+ attribution: (0, changeAttribution_1.decodeChangeAttribution)(typeof decodedChange.message === "string" ? decodedChange.message : null),
9877
9945
  dependencyHashes: Array.isArray(decodedChange.deps)
9878
9946
  ? decodedChange.deps.filter((dep) => typeof dep === "string")
9879
9947
  : [],
@@ -11078,6 +11146,7 @@ class BaseMindooDB {
11078
11146
  }
11079
11147
  async mutateRecipients(doc, mode, specs, options) {
11080
11148
  this.assertWritable("changeDoc");
11149
+ const changeMessage = (0, changeAttribution_1.encodeChangeAttribution)(options?.attribution);
11081
11150
  if (!(0, sealedTypes_1.isSealedKeyId)(doc.getDecryptionKeyId())) {
11082
11151
  throw new Error("Recipient mutators require a document created with `recipients`");
11083
11152
  }
@@ -11195,7 +11264,7 @@ class BaseMindooDB {
11195
11264
  });
11196
11265
  tenant.rememberSealedGenerations(internalDoc.decryptionKeyId, generations);
11197
11266
  const headsBeforeChange = automerge_adapter_1.Automerge.getHeads(internalDoc.doc);
11198
- const newDoc = automerge_adapter_1.Automerge.change(internalDoc.doc, (draft) => {
11267
+ const newDoc = automerge_adapter_1.Automerge.change(internalDoc.doc, { message: changeMessage }, (draft) => {
11199
11268
  if (addedTargets.length > 0) {
11200
11269
  const addMap = {};
11201
11270
  for (const target of addedTargets) {
@@ -11235,6 +11304,7 @@ class BaseMindooDB {
11235
11304
  signingKeyPair: options.signingKeyPair,
11236
11305
  signingKeyPassword: options.signingKeyPassword,
11237
11306
  bypassAccessControlPrecheck: options.bypassAccessControlPrecheck,
11307
+ attribution: options.attribution,
11238
11308
  });
11239
11309
  }
11240
11310
  const wrapped = this.wrapDocument(internalDoc);