@automerge/automerge-repo 2.0.4 → 2.0.6

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.
@@ -153,9 +153,14 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
153
153
  * Called by the repo when a doc handle changes or we receive new remote heads.
154
154
  * @hidden
155
155
  */
156
- setRemoteHeads(storageId: StorageId, heads: UrlHeads): void;
157
- /** Returns the heads of the storageId. */
156
+ setSyncInfo(storageId: StorageId, syncInfo: SyncInfo): void;
157
+ /** Returns the heads of the storageId.
158
+ *
159
+ * @deprecated Use getSyncInfo instead.
160
+ */
158
161
  getRemoteHeads(storageId: StorageId): UrlHeads | undefined;
162
+ /** Returns the heads and the timestamp of the last update for the storageId. */
163
+ getSyncInfo(storageId: StorageId): SyncInfo | undefined;
159
164
  /**
160
165
  * All changes to an Automerge document should be made through this method.
161
166
  * Inside the callback, the document should be treated as mutable: all edits will be recorded
@@ -178,6 +183,17 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
178
183
  * @returns A set of heads representing the concurrent change that was made.
179
184
  */
180
185
  changeAt(heads: UrlHeads, callback: A.ChangeFn<T>, options?: A.ChangeOptions<T>): UrlHeads | undefined;
186
+ /**
187
+ * Check if the document can be change()ed. Currently, documents can be
188
+ * edited unless we are viewing a particular point in time.
189
+ *
190
+ * @remarks It is technically possible to back-date changes using changeAt(),
191
+ * but we block it for usability reasons when viewing a particular point in time.
192
+ * To make changes in the past, use the primary document handle with no heads set.
193
+ *
194
+ * @returns boolean indicating whether changes are possible
195
+ */
196
+ isReadOnly(): boolean;
181
197
  /**
182
198
  * Merges another document into this document. Any peers we are sharing changes with will be
183
199
  * notified of the changes resulting from the merge.
@@ -218,6 +234,10 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
218
234
  numChanges: number;
219
235
  };
220
236
  }
237
+ export type SyncInfo = {
238
+ lastHeads: UrlHeads;
239
+ lastSyncTimestamp: number;
240
+ };
221
241
  /** @hidden */
222
242
  export type DocHandleOptions<T> = {
223
243
  /** If we know this is a new document (because we're creating it) this should be set to true. */
@@ -278,6 +298,7 @@ export interface DocHandleOutboundEphemeralMessagePayload<T> {
278
298
  export interface DocHandleRemoteHeadsPayload {
279
299
  storageId: StorageId;
280
300
  heads: UrlHeads;
301
+ timestamp: number;
281
302
  }
282
303
  /**
283
304
  * Possible internal states for a DocHandle
@@ -1 +1 @@
1
- {"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"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"}
package/dist/DocHandle.js CHANGED
@@ -31,8 +31,8 @@ export class DocHandle extends EventEmitter {
31
31
  /** How long to wait before giving up on a document. (Note that a document will be marked
32
32
  * unavailable much sooner if all known peers respond that they don't have it.) */
33
33
  #timeoutDelay = 60_000;
34
- /** A dictionary mapping each peer to the last heads we know they have. */
35
- #remoteHeads = {};
34
+ /** A dictionary mapping each peer to the last known heads we have. */
35
+ #syncInfoByStorageId = {};
36
36
  /** Cache for view handles, keyed by the stringified heads */
37
37
  #viewCache = new Map();
38
38
  /** @hidden */
@@ -150,6 +150,39 @@ export class DocHandle extends EventEmitter {
150
150
  // use a longer delay here so as not to race with other delays
151
151
  { timeout: this.#timeoutDelay * 2 });
152
152
  }
153
+ /**
154
+ * Update the document with whatever the result of callback is
155
+ *
156
+ * This is necessary instead of directly calling
157
+ * `this.#machine.send({ type: UPDATE, payload: { callback } })` because we
158
+ * want to catch any exceptions that the callback might throw, then rethrow
159
+ * them after the state machine has processed the update.
160
+ */
161
+ #sendUpdate(callback) {
162
+ // This is kind of awkward. we have to pass the callback to xstate and wait for it to run it.
163
+ // We're relying here on the fact that xstate runs everything synchronously, so by the time
164
+ // `send` returns we know that the callback will have been run and so `thrownException` will
165
+ // be set if the callback threw an error.
166
+ let thrownException = null;
167
+ this.#machine.send({
168
+ type: UPDATE,
169
+ payload: {
170
+ callback: doc => {
171
+ try {
172
+ return callback(doc);
173
+ }
174
+ catch (e) {
175
+ thrownException = e;
176
+ return doc;
177
+ }
178
+ },
179
+ },
180
+ });
181
+ if (thrownException) {
182
+ // If the callback threw an error, we throw it here so the caller can handle it
183
+ throw thrownException;
184
+ }
185
+ }
153
186
  /**
154
187
  * Called after state transitions. If the document has changed, emits a change event. If we just
155
188
  * received the document for the first time, signal that our request has been completed.
@@ -389,7 +422,7 @@ export class DocHandle extends EventEmitter {
389
422
  * @hidden
390
423
  */
391
424
  update(callback) {
392
- this.#machine.send({ type: UPDATE, payload: { callback } });
425
+ this.#sendUpdate(callback);
393
426
  }
394
427
  /**
395
428
  * `doneLoading` is called by the repo after it decides it has all the changes
@@ -403,13 +436,24 @@ export class DocHandle extends EventEmitter {
403
436
  * Called by the repo when a doc handle changes or we receive new remote heads.
404
437
  * @hidden
405
438
  */
406
- setRemoteHeads(storageId, heads) {
407
- this.#remoteHeads[storageId] = heads;
408
- this.emit("remote-heads", { storageId, heads });
439
+ setSyncInfo(storageId, syncInfo) {
440
+ this.#syncInfoByStorageId[storageId] = syncInfo;
441
+ this.emit("remote-heads", {
442
+ storageId,
443
+ heads: syncInfo.lastHeads,
444
+ timestamp: syncInfo.lastSyncTimestamp,
445
+ });
409
446
  }
410
- /** Returns the heads of the storageId. */
447
+ /** Returns the heads of the storageId.
448
+ *
449
+ * @deprecated Use getSyncInfo instead.
450
+ */
411
451
  getRemoteHeads(storageId) {
412
- return this.#remoteHeads[storageId];
452
+ return this.#syncInfoByStorageId[storageId]?.lastHeads;
453
+ }
454
+ /** Returns the heads and the timestamp of the last update for the storageId. */
455
+ getSyncInfo(storageId) {
456
+ return this.#syncInfoByStorageId[storageId];
413
457
  }
414
458
  /**
415
459
  * All changes to an Automerge document should be made through this method.
@@ -433,10 +477,7 @@ export class DocHandle extends EventEmitter {
433
477
  if (this.#fixedHeads) {
434
478
  throw new Error(`DocHandle#${this.documentId} is in view-only mode at specific heads. Use clone() to create a new document from this state.`);
435
479
  }
436
- this.#machine.send({
437
- type: UPDATE,
438
- payload: { callback: doc => A.change(doc, options, callback) },
439
- });
480
+ this.#sendUpdate(doc => A.change(doc, options, callback));
440
481
  }
441
482
  /**
442
483
  * Makes a change as if the document were at `heads`.
@@ -451,21 +492,27 @@ export class DocHandle extends EventEmitter {
451
492
  throw new Error(`DocHandle#${this.documentId} is in view-only mode at specific heads. Use clone() to create a new document from this state.`);
452
493
  }
453
494
  let resultHeads = undefined;
454
- this.#machine.send({
455
- type: UPDATE,
456
- payload: {
457
- callback: doc => {
458
- const result = A.changeAt(doc, decodeHeads(heads), options, callback);
459
- resultHeads = result.newHeads
460
- ? encodeHeads(result.newHeads)
461
- : undefined;
462
- return result.newDoc;
463
- },
464
- },
495
+ this.#sendUpdate(doc => {
496
+ const result = A.changeAt(doc, decodeHeads(heads), options, callback);
497
+ resultHeads = result.newHeads ? encodeHeads(result.newHeads) : undefined;
498
+ return result.newDoc;
465
499
  });
466
500
  // the callback above will always run before we get here, so this should always contain the new heads
467
501
  return resultHeads;
468
502
  }
503
+ /**
504
+ * Check if the document can be change()ed. Currently, documents can be
505
+ * edited unless we are viewing a particular point in time.
506
+ *
507
+ * @remarks It is technically possible to back-date changes using changeAt(),
508
+ * but we block it for usability reasons when viewing a particular point in time.
509
+ * To make changes in the past, use the primary document handle with no heads set.
510
+ *
511
+ * @returns boolean indicating whether changes are possible
512
+ */
513
+ isReadOnly() {
514
+ return !!this.#fixedHeads;
515
+ }
469
516
  /**
470
517
  * Merges another document into this document. Any peers we are sharing changes with will be
471
518
  * notified of the changes resulting from the merge.
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteHeadsSubscriptions.d.ts","sourceRoot":"","sources":["../src/RemoteHeadsSubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAItC,MAAM,MAAM,mCAAmC,GAAG;IAChD,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,QAAQ,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,QAAQ,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,sBAAsB,EAAE,CAAC,OAAO,EAAE,mCAAmC,KAAK,IAAI,CAAA;IAC9E,oBAAoB,EAAE,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,GAAG,CAAC,EAAE,SAAS,EAAE,CAAA;QACjB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;KACrB,KAAK,IAAI,CAAA;IACV,qBAAqB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;CACnE,CAAA;AAED,qBAAa,wBAAyB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;;IAcvF,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE;IAkBvC,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE;IAsB3C,oBAAoB,CAAC,OAAO,EAAE,gCAAgC;IA0E9D,sEAAsE;IACtE,iBAAiB,CAAC,GAAG,EAAE,kBAAkB;IAgDzC,kEAAkE;IAClE,iCAAiC,CAC/B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,QAAQ;IAgCjB,eAAe,CAAC,MAAM,EAAE,MAAM;IAwB9B,UAAU,CAAC,MAAM,EAAE,MAAM;IA2BzB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU;CAuE1D"}
1
+ {"version":3,"file":"RemoteHeadsSubscriptions.d.ts","sourceRoot":"","sources":["../src/RemoteHeadsSubscriptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AACzD,OAAO,EACL,kBAAkB,EAClB,gCAAgC,EACjC,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAKtC,MAAM,MAAM,mCAAmC,GAAG;IAChD,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,WAAW,EAAE,QAAQ,CAAA;IACrB,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAGD,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,EAAE,QAAQ,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;CAClB,CAAA;AAED,KAAK,6BAA6B,GAAG;IACnC,sBAAsB,EAAE,CAAC,OAAO,EAAE,mCAAmC,KAAK,IAAI,CAAA;IAC9E,oBAAoB,EAAE,CAAC,OAAO,EAAE;QAC9B,KAAK,EAAE,MAAM,EAAE,CAAA;QACf,GAAG,CAAC,EAAE,SAAS,EAAE,CAAA;QACjB,MAAM,CAAC,EAAE,SAAS,EAAE,CAAA;KACrB,KAAK,IAAI,CAAA;IACV,qBAAqB,EAAE,CAAC,OAAO,EAAE,wBAAwB,KAAK,IAAI,CAAA;CACnE,CAAA;AAED,qBAAa,wBAAyB,SAAQ,YAAY,CAAC,6BAA6B,CAAC;;IAcvF,kBAAkB,CAAC,OAAO,EAAE,SAAS,EAAE;IAkBvC,sBAAsB,CAAC,OAAO,EAAE,SAAS,EAAE;IAsB3C,oBAAoB,CAAC,OAAO,EAAE,gCAAgC;IA0E9D,sEAAsE;IACtE,iBAAiB,CAAC,GAAG,EAAE,kBAAkB;IAgDzC,kEAAkE;IAClE,iCAAiC,CAC/B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,QAAQ;IAqCjB,eAAe,CAAC,MAAM,EAAE,MAAM;IAwB9B,UAAU,CAAC,MAAM,EAAE,MAAM;IA2BzB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU;CAuE1D"}
@@ -1,8 +1,8 @@
1
1
  import { EventEmitter } from "eventemitter3";
2
2
  import debug from "debug";
3
3
  export class RemoteHeadsSubscriptions extends EventEmitter {
4
- // Storage IDs we have received remote heads from
5
- #knownHeads = new Map();
4
+ // Last known heads and timestamp for each storageId that we know about
5
+ #syncInfoByDocId = new Map();
6
6
  // Storage IDs we have subscribed to via Repo.subscribeToRemoteHeads
7
7
  #ourSubscriptions = new Set();
8
8
  // Storage IDs other peers have subscribed to by sending us a control message
@@ -91,17 +91,17 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
91
91
  const subscribedDocs = this.#subscribedDocsByPeer.get(control.senderId);
92
92
  if (subscribedDocs) {
93
93
  for (const documentId of subscribedDocs) {
94
- const knownHeads = this.#knownHeads.get(documentId);
95
- if (!knownHeads) {
94
+ const syncInfo = this.#syncInfoByDocId.get(documentId);
95
+ if (!syncInfo) {
96
96
  continue;
97
97
  }
98
- const lastHeads = knownHeads.get(remote);
99
- if (lastHeads) {
98
+ const syncInfoForRemote = syncInfo.get(remote);
99
+ if (syncInfoForRemote) {
100
100
  this.emit("notify-remote-heads", {
101
101
  targetId: control.senderId,
102
102
  documentId,
103
- heads: lastHeads.heads,
104
- timestamp: lastHeads.timestamp,
103
+ heads: syncInfoForRemote.lastHeads,
104
+ timestamp: syncInfoForRemote.lastSyncTimestamp,
105
105
  storageId: remote,
106
106
  });
107
107
  }
@@ -156,15 +156,20 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
156
156
  /** A peer we are directly connected to has updated their heads */
157
157
  handleImmediateRemoteHeadsChanged(documentId, storageId, heads) {
158
158
  this.#log("handleLocalHeadsChanged", documentId, storageId, heads);
159
- const remote = this.#knownHeads.get(documentId);
159
+ const remote = this.#syncInfoByDocId.get(documentId);
160
160
  const timestamp = Date.now();
161
161
  if (!remote) {
162
- this.#knownHeads.set(documentId, new Map([[storageId, { heads, timestamp }]]));
162
+ this.#syncInfoByDocId.set(documentId, new Map([
163
+ [storageId, { lastSyncTimestamp: timestamp, lastHeads: heads }],
164
+ ]));
163
165
  }
164
166
  else {
165
167
  const docRemote = remote.get(storageId);
166
- if (!docRemote || docRemote.timestamp < Date.now()) {
167
- remote.set(storageId, { heads, timestamp: Date.now() });
168
+ if (!docRemote || docRemote.lastSyncTimestamp < Date.now()) {
169
+ remote.set(storageId, {
170
+ lastSyncTimestamp: Date.now(),
171
+ lastHeads: heads,
172
+ });
168
173
  }
169
174
  }
170
175
  const theirSubs = this.#theirSubscriptions.get(storageId);
@@ -191,13 +196,13 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
191
196
  peers: [peerId],
192
197
  });
193
198
  }
194
- for (const [documentId, remote] of this.#knownHeads) {
195
- for (const [storageId, { heads, timestamp }] of remote) {
199
+ for (const [documentId, remote] of this.#syncInfoByDocId) {
200
+ for (const [storageId, { lastHeads, lastSyncTimestamp }] of remote) {
196
201
  this.emit("notify-remote-heads", {
197
202
  targetId: peerId,
198
203
  documentId: documentId,
199
- heads: heads,
200
- timestamp: timestamp,
204
+ heads: lastHeads,
205
+ timestamp: lastSyncTimestamp,
201
206
  storageId: storageId,
202
207
  });
203
208
  }
@@ -231,7 +236,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
231
236
  this.#subscribedDocsByPeer.set(peerId, subscribedDocs);
232
237
  }
233
238
  subscribedDocs.add(documentId);
234
- const remoteHeads = this.#knownHeads.get(documentId);
239
+ const remoteHeads = this.#syncInfoByDocId.get(documentId);
235
240
  if (remoteHeads) {
236
241
  for (const [storageId, lastHeads] of remoteHeads) {
237
242
  const subscribedPeers = this.#theirSubscriptions.get(storageId);
@@ -239,8 +244,8 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
239
244
  this.emit("notify-remote-heads", {
240
245
  targetId: peerId,
241
246
  documentId,
242
- heads: lastHeads.heads,
243
- timestamp: lastHeads.timestamp,
247
+ heads: lastHeads.lastHeads,
248
+ timestamp: lastHeads.lastSyncTimestamp,
244
249
  storageId,
245
250
  });
246
251
  }
@@ -260,19 +265,19 @@ export class RemoteHeadsSubscriptions extends EventEmitter {
260
265
  !this.#theirSubscriptions.has(storageId)) {
261
266
  continue;
262
267
  }
263
- let remote = this.#knownHeads.get(documentId);
268
+ let remote = this.#syncInfoByDocId.get(documentId);
264
269
  if (!remote) {
265
270
  remote = new Map();
266
- this.#knownHeads.set(documentId, remote);
271
+ this.#syncInfoByDocId.set(documentId, remote);
267
272
  }
268
273
  const docRemote = remote.get(storageId);
269
- if (docRemote && docRemote.timestamp >= timestamp) {
274
+ if (docRemote && docRemote.lastSyncTimestamp >= timestamp) {
270
275
  continue;
271
276
  }
272
277
  else {
273
278
  remote.set(storageId, {
274
- timestamp,
275
- heads: heads,
279
+ lastSyncTimestamp: timestamp,
280
+ lastHeads: heads,
276
281
  });
277
282
  changedHeads.push({
278
283
  documentId,
@@ -1 +1 @@
1
- {"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
package/dist/Repo.js CHANGED
@@ -118,12 +118,15 @@ export class Repo extends EventEmitter {
118
118
  if (!storageId) {
119
119
  return;
120
120
  }
121
- const heads = handle.getRemoteHeads(storageId);
121
+ const heads = handle.getSyncInfo(storageId)?.lastHeads;
122
122
  const haveHeadsChanged = message.syncState.theirHeads &&
123
123
  (!heads ||
124
124
  !headsAreSame(heads, encodeHeads(message.syncState.theirHeads)));
125
125
  if (haveHeadsChanged && message.syncState.theirHeads) {
126
- handle.setRemoteHeads(storageId, encodeHeads(message.syncState.theirHeads));
126
+ handle.setSyncInfo(storageId, {
127
+ lastHeads: encodeHeads(message.syncState.theirHeads),
128
+ lastSyncTimestamp: Date.now(),
129
+ });
127
130
  if (storageId && this.#remoteHeadsGossipingEnabled) {
128
131
  this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(message.documentId, storageId, encodeHeads(message.syncState.theirHeads));
129
132
  }
@@ -154,9 +157,12 @@ export class Repo extends EventEmitter {
154
157
  });
155
158
  }
156
159
  });
157
- this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
158
- const handle = this.#handleCache[message.documentId];
159
- handle.setRemoteHeads(message.storageId, message.remoteHeads);
160
+ this.#remoteHeadsSubscriptions.on("remote-heads-changed", ({ documentId, storageId, remoteHeads, timestamp }) => {
161
+ const handle = this.#handleCache[documentId];
162
+ handle.setSyncInfo(storageId, {
163
+ lastHeads: remoteHeads,
164
+ lastSyncTimestamp: timestamp,
165
+ });
160
166
  });
161
167
  }
162
168
  }
package/dist/index.d.ts CHANGED
@@ -36,7 +36,7 @@ export type { StorageAdapterInterface } from "./storage/StorageAdapterInterface.
36
36
  import { next as Automerge } from "@automerge/automerge/slim";
37
37
  /** @hidden **/
38
38
  export * as cbor from "./helpers/cbor.js";
39
- export type { DocHandleChangePayload, DocHandleDeletePayload, DocHandleEncodedChangePayload, DocHandleEphemeralMessagePayload, DocHandleRemoteHeadsPayload, DocHandleEvents, DocHandleOptions, DocHandleOutboundEphemeralMessagePayload, HandleState, } from "./DocHandle.js";
39
+ export type { DocHandleChangePayload, DocHandleDeletePayload, DocHandleEncodedChangePayload, DocHandleEphemeralMessagePayload, DocHandleRemoteHeadsPayload, DocHandleEvents, DocHandleOptions, DocHandleOutboundEphemeralMessagePayload, HandleState, SyncInfo, } from "./DocHandle.js";
40
40
  export type { DeleteDocumentPayload, DocumentPayload, RepoConfig, RepoEvents, SharePolicy, } from "./Repo.js";
41
41
  export type { NetworkAdapterEvents, OpenPayload, PeerCandidatePayload, PeerDisconnectedPayload, PeerMetadata, } from "./network/NetworkAdapterInterface.js";
42
42
  export type { NetworkSubsystemEvents, PeerPayload, } from "./network/NetworkSubsystem.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,EACpB,WAAW,EACX,WAAW,GACZ,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AACnF,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,YAAY,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AACnF,OAAO,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,2BAA2B,CAAA;AAE7D,eAAe;AACf,OAAO,KAAK,IAAI,MAAM,mBAAmB,CAAA;AAIzC,YAAY,EACV,sBAAsB,EACtB,sBAAsB,EACtB,6BAA6B,EAC7B,gCAAgC,EAChC,2BAA2B,EAC3B,eAAe,EACf,gBAAgB,EAChB,wCAAwC,EACxC,WAAW,EACX,QAAQ,GACT,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EACV,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,WAAW,CAAA;AAElB,YAAY,EACV,oBAAoB,EACpB,WAAW,EACX,oBAAoB,EACpB,uBAAuB,EACvB,YAAY,GACb,MAAM,sCAAsC,CAAA;AAE7C,YAAY,EACV,sBAAsB,EACtB,WAAW,GACZ,MAAM,+BAA+B,CAAA;AAEtC,YAAY,EACV,0BAA0B,EAC1B,gBAAgB,EAChB,OAAO,EACP,WAAW,EACX,cAAc,EACd,WAAW,GACZ,MAAM,uBAAuB,CAAA;AAE9B,YAAY,EACV,KAAK,EACL,SAAS,EACT,SAAS,EACT,UAAU,EACV,SAAS,GACV,MAAM,oBAAoB,CAAA;AAE3B,cAAc,YAAY,CAAA;AAiB1B,eAAO,MAAM,OAAO,0BAAoB,CAAA;AACxC,eAAO,MAAM,SAAS,4BAAsB,CAAA;AAE5C,eAAO,MAAM,eAAe,4BAAsB,CAAA;AAIlD,MAAM,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,SAAS,CAAC,SAAS,CAAC,CAAA;AAChE,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;AACvC,MAAM,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;AACrC,MAAM,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAA;AACnC,MAAM,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAA;AACnC,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,CAAA;AACzD,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAA;AACjC,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;AACvC,MAAM,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;AACrC,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;AAC/C,MAAM,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAA;AACjC,MAAM,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAA;AACvC,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;AAC3C,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAA;AAC3C,MAAM,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAA;AAIrC,eAAO,MAAM,UAAU,6BAAuB,CAAA;AAC9C,eAAO,MAAM,aAAa,gCAA0B,CAAA;AACpD,eAAO,MAAM,YAAY,+BAAyB,CAAA;AAClD,eAAO,MAAM,IAAI,uBAAiB,CAAA;AAClC,eAAO,MAAM,YAAY,+BAAyB,CAAA;AAKlD,eAAO,MAAM,SAAS,4BAAsB,CAAA;AAC5C,eAAO,MAAM,iBAAiB,oCAA8B,CAAA;AAC5D,eAAO,MAAM,MAAM,yBAAmB,CAAA;AACtC,eAAO,MAAM,UAAU,6BAAuB,CAAA;AAC9C,eAAO,MAAM,QAAQ,2BAAqB,CAAA;AAC1C,eAAO,MAAM,QAAQ,2BAAqB,CAAA;AAC1C,eAAO,MAAM,IAAI,uBAAiB,CAAA;AAClC,eAAO,MAAM,MAAM,yBAAmB,CAAA;AACtC,eAAO,MAAM,WAAW,8BAAwB,CAAA;AAEhD,eAAO,MAAM,iBAAiB,8BAAwB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAOtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAW/D,MAAM,EAAE,MAAM;IACrB,OAAO,CAAC,YAAY;IALtB,QAAQ,EAAE,uBAAuB,EAAE,CAAK;gBAGtC,QAAQ,EAAE,uBAAuB,EAAE,EAC5B,MAAM,EAAE,MAAM,EACb,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;IAO7C,UAAU;IAIV,SAAS;IAIT,iBAAiB,CAAC,cAAc,EAAE,uBAAuB;IAqEzD,oBAAoB,CAAC,cAAc,EAAE,uBAAuB;IAK5D,IAAI,CAAC,OAAO,EAAE,eAAe;IAsC7B,OAAO,gBAEN;IAED,SAAS,wBAER;CACF;AAID,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B"}
1
+ {"version":3,"file":"NetworkSubsystem.d.ts","sourceRoot":"","sources":["../../src/network/NetworkSubsystem.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAa,MAAM,aAAa,CAAA;AAC/C,OAAO,KAAK,EACV,uBAAuB,EACvB,uBAAuB,EACvB,YAAY,EACb,MAAM,8BAA8B,CAAA;AACrC,OAAO,EAEL,eAAe,EACf,WAAW,EAGZ,MAAM,eAAe,CAAA;AAOtB,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;IAW/D,MAAM,EAAE,MAAM;IACrB,OAAO,CAAC,YAAY;IALtB,QAAQ,EAAE,uBAAuB,EAAE,CAAK;gBAGtC,QAAQ,EAAE,uBAAuB,EAAE,EAC5B,MAAM,EAAE,MAAM,EACb,YAAY,EAAE,OAAO,CAAC,YAAY,CAAC;IAO7C,UAAU;IAIV,SAAS;IAIT,iBAAiB,CAAC,cAAc,EAAE,uBAAuB;IAsEzD,oBAAoB,CAAC,cAAc,EAAE,uBAAuB;IAK5D,IAAI,CAAC,OAAO,EAAE,eAAe;IAsC7B,OAAO,gBAEN;IAED,SAAS,wBAER;CACF;AAID,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;IACpC,mBAAmB,EAAE,CAAC,OAAO,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAC/D,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAA;CACxC;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,YAAY,CAAA;CAC3B"}
@@ -68,6 +68,7 @@ export class NetworkSubsystem extends EventEmitter {
68
68
  delete this.#adaptersByPeer[peerId];
69
69
  }
70
70
  });
71
+ this.adapters = this.adapters.filter(a => a !== networkAdapter);
71
72
  });
72
73
  this.peerMetadata
73
74
  .then(peerMetadata => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automerge/automerge-repo",
3
- "version": "2.0.4",
3
+ "version": "2.0.6",
4
4
  "description": "A repository object to manage a collection of automerge documents",
5
5
  "repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
6
6
  "author": "Peter van Hardenberg <pvh@pvh.ca>",
@@ -59,5 +59,5 @@
59
59
  "publishConfig": {
60
60
  "access": "public"
61
61
  },
62
- "gitHead": "7100c8827c5da9e04fd2dc4319d015f5e624f8fa"
62
+ "gitHead": "d90f3e51d9ed357fd6f3098ac11190996ae0c05a"
63
63
  }
package/src/DocHandle.ts CHANGED
@@ -42,8 +42,8 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
42
42
  * unavailable much sooner if all known peers respond that they don't have it.) */
43
43
  #timeoutDelay = 60_000
44
44
 
45
- /** A dictionary mapping each peer to the last heads we know they have. */
46
- #remoteHeads: Record<StorageId, UrlHeads> = {}
45
+ /** A dictionary mapping each peer to the last known heads we have. */
46
+ #syncInfoByStorageId: Record<StorageId, SyncInfo> = {}
47
47
 
48
48
  /** Cache for view handles, keyed by the stringified heads */
49
49
  #viewCache: Map<string, DocHandle<T>> = new Map()
@@ -183,6 +183,39 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
183
183
  )
184
184
  }
185
185
 
186
+ /**
187
+ * Update the document with whatever the result of callback is
188
+ *
189
+ * This is necessary instead of directly calling
190
+ * `this.#machine.send({ type: UPDATE, payload: { callback } })` because we
191
+ * want to catch any exceptions that the callback might throw, then rethrow
192
+ * them after the state machine has processed the update.
193
+ */
194
+ #sendUpdate(callback: (doc: A.Doc<T>) => A.Doc<T>) {
195
+ // This is kind of awkward. we have to pass the callback to xstate and wait for it to run it.
196
+ // We're relying here on the fact that xstate runs everything synchronously, so by the time
197
+ // `send` returns we know that the callback will have been run and so `thrownException` will
198
+ // be set if the callback threw an error.
199
+ let thrownException: null | Error = null
200
+ this.#machine.send({
201
+ type: UPDATE,
202
+ payload: {
203
+ callback: doc => {
204
+ try {
205
+ return callback(doc)
206
+ } catch (e) {
207
+ thrownException = e as Error
208
+ return doc
209
+ }
210
+ },
211
+ },
212
+ })
213
+ if (thrownException) {
214
+ // If the callback threw an error, we throw it here so the caller can handle it
215
+ throw thrownException
216
+ }
217
+ }
218
+
186
219
  /**
187
220
  * Called after state transitions. If the document has changed, emits a change event. If we just
188
221
  * received the document for the first time, signal that our request has been completed.
@@ -466,7 +499,7 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
466
499
  * @hidden
467
500
  */
468
501
  update(callback: (doc: A.Doc<T>) => A.Doc<T>) {
469
- this.#machine.send({ type: UPDATE, payload: { callback } })
502
+ this.#sendUpdate(callback)
470
503
  }
471
504
 
472
505
  /**
@@ -482,14 +515,26 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
482
515
  * Called by the repo when a doc handle changes or we receive new remote heads.
483
516
  * @hidden
484
517
  */
485
- setRemoteHeads(storageId: StorageId, heads: UrlHeads) {
486
- this.#remoteHeads[storageId] = heads
487
- this.emit("remote-heads", { storageId, heads })
518
+ setSyncInfo(storageId: StorageId, syncInfo: SyncInfo) {
519
+ this.#syncInfoByStorageId[storageId] = syncInfo
520
+ this.emit("remote-heads", {
521
+ storageId,
522
+ heads: syncInfo.lastHeads,
523
+ timestamp: syncInfo.lastSyncTimestamp,
524
+ })
488
525
  }
489
526
 
490
- /** Returns the heads of the storageId. */
527
+ /** Returns the heads of the storageId.
528
+ *
529
+ * @deprecated Use getSyncInfo instead.
530
+ */
491
531
  getRemoteHeads(storageId: StorageId): UrlHeads | undefined {
492
- return this.#remoteHeads[storageId]
532
+ return this.#syncInfoByStorageId[storageId]?.lastHeads
533
+ }
534
+
535
+ /** Returns the heads and the timestamp of the last update for the storageId. */
536
+ getSyncInfo(storageId: StorageId): SyncInfo | undefined {
537
+ return this.#syncInfoByStorageId[storageId]
493
538
  }
494
539
 
495
540
  /**
@@ -520,10 +565,7 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
520
565
  )
521
566
  }
522
567
 
523
- this.#machine.send({
524
- type: UPDATE,
525
- payload: { callback: doc => A.change(doc, options, callback) },
526
- })
568
+ this.#sendUpdate(doc => A.change(doc, options, callback))
527
569
  }
528
570
  /**
529
571
  * Makes a change as if the document were at `heads`.
@@ -545,24 +587,32 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
545
587
  `DocHandle#${this.documentId} is in view-only mode at specific heads. Use clone() to create a new document from this state.`
546
588
  )
547
589
  }
590
+
548
591
  let resultHeads: UrlHeads | undefined = undefined
549
- this.#machine.send({
550
- type: UPDATE,
551
- payload: {
552
- callback: doc => {
553
- const result = A.changeAt(doc, decodeHeads(heads), options, callback)
554
- resultHeads = result.newHeads
555
- ? encodeHeads(result.newHeads)
556
- : undefined
557
- return result.newDoc
558
- },
559
- },
592
+ this.#sendUpdate(doc => {
593
+ const result = A.changeAt(doc, decodeHeads(heads), options, callback)
594
+ resultHeads = result.newHeads ? encodeHeads(result.newHeads) : undefined
595
+ return result.newDoc
560
596
  })
561
597
 
562
598
  // the callback above will always run before we get here, so this should always contain the new heads
563
599
  return resultHeads
564
600
  }
565
601
 
602
+ /**
603
+ * Check if the document can be change()ed. Currently, documents can be
604
+ * edited unless we are viewing a particular point in time.
605
+ *
606
+ * @remarks It is technically possible to back-date changes using changeAt(),
607
+ * but we block it for usability reasons when viewing a particular point in time.
608
+ * To make changes in the past, use the primary document handle with no heads set.
609
+ *
610
+ * @returns boolean indicating whether changes are possible
611
+ */
612
+ isReadOnly() {
613
+ return !!this.#fixedHeads
614
+ }
615
+
566
616
  /**
567
617
  * Merges another document into this document. Any peers we are sharing changes with will be
568
618
  * notified of the changes resulting from the merge.
@@ -642,6 +692,11 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
642
692
 
643
693
  // TYPES
644
694
 
695
+ export type SyncInfo = {
696
+ lastHeads: UrlHeads
697
+ lastSyncTimestamp: number
698
+ }
699
+
645
700
  /** @hidden */
646
701
  export type DocHandleOptions<T> =
647
702
  // NEW DOCUMENTS
@@ -722,6 +777,7 @@ export interface DocHandleOutboundEphemeralMessagePayload<T> {
722
777
  export interface DocHandleRemoteHeadsPayload {
723
778
  storageId: StorageId
724
779
  heads: UrlHeads
780
+ timestamp: number
725
781
  }
726
782
 
727
783
  // STATE MACHINE TYPES & CONSTANTS
@@ -6,6 +6,7 @@ import {
6
6
  } from "./network/messages.js"
7
7
  import { StorageId } from "./index.js"
8
8
  import debug from "debug"
9
+ import { SyncInfo } from "./DocHandle.js"
9
10
 
10
11
  // Notify a DocHandle that remote heads have changed
11
12
  export type RemoteHeadsSubscriptionEventPayload = {
@@ -35,8 +36,8 @@ type RemoteHeadsSubscriptionEvents = {
35
36
  }
36
37
 
37
38
  export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscriptionEvents> {
38
- // Storage IDs we have received remote heads from
39
- #knownHeads: Map<DocumentId, Map<StorageId, LastHeads>> = new Map()
39
+ // Last known heads and timestamp for each storageId that we know about
40
+ #syncInfoByDocId: Map<DocumentId, Map<StorageId, SyncInfo>> = new Map()
40
41
  // Storage IDs we have subscribed to via Repo.subscribeToRemoteHeads
41
42
  #ourSubscriptions: Set<StorageId> = new Set()
42
43
  // Storage IDs other peers have subscribed to by sending us a control message
@@ -142,18 +143,18 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
142
143
  const subscribedDocs = this.#subscribedDocsByPeer.get(control.senderId)
143
144
  if (subscribedDocs) {
144
145
  for (const documentId of subscribedDocs) {
145
- const knownHeads = this.#knownHeads.get(documentId)
146
- if (!knownHeads) {
146
+ const syncInfo = this.#syncInfoByDocId.get(documentId)
147
+ if (!syncInfo) {
147
148
  continue
148
149
  }
149
150
 
150
- const lastHeads = knownHeads.get(remote)
151
- if (lastHeads) {
151
+ const syncInfoForRemote = syncInfo.get(remote)
152
+ if (syncInfoForRemote) {
152
153
  this.emit("notify-remote-heads", {
153
154
  targetId: control.senderId,
154
155
  documentId,
155
- heads: lastHeads.heads,
156
- timestamp: lastHeads.timestamp,
156
+ heads: syncInfoForRemote.lastHeads,
157
+ timestamp: syncInfoForRemote.lastSyncTimestamp,
157
158
  storageId: remote,
158
159
  })
159
160
  }
@@ -218,17 +219,22 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
218
219
  heads: UrlHeads
219
220
  ) {
220
221
  this.#log("handleLocalHeadsChanged", documentId, storageId, heads)
221
- const remote = this.#knownHeads.get(documentId)
222
+ const remote = this.#syncInfoByDocId.get(documentId)
222
223
  const timestamp = Date.now()
223
224
  if (!remote) {
224
- this.#knownHeads.set(
225
+ this.#syncInfoByDocId.set(
225
226
  documentId,
226
- new Map([[storageId, { heads, timestamp }]])
227
+ new Map([
228
+ [storageId, { lastSyncTimestamp: timestamp, lastHeads: heads }],
229
+ ])
227
230
  )
228
231
  } else {
229
232
  const docRemote = remote.get(storageId)
230
- if (!docRemote || docRemote.timestamp < Date.now()) {
231
- remote.set(storageId, { heads, timestamp: Date.now() })
233
+ if (!docRemote || docRemote.lastSyncTimestamp < Date.now()) {
234
+ remote.set(storageId, {
235
+ lastSyncTimestamp: Date.now(),
236
+ lastHeads: heads,
237
+ })
232
238
  }
233
239
  }
234
240
  const theirSubs = this.#theirSubscriptions.get(storageId)
@@ -258,13 +264,13 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
258
264
  })
259
265
  }
260
266
 
261
- for (const [documentId, remote] of this.#knownHeads) {
262
- for (const [storageId, { heads, timestamp }] of remote) {
267
+ for (const [documentId, remote] of this.#syncInfoByDocId) {
268
+ for (const [storageId, { lastHeads, lastSyncTimestamp }] of remote) {
263
269
  this.emit("notify-remote-heads", {
264
270
  targetId: peerId,
265
271
  documentId: documentId,
266
- heads: heads,
267
- timestamp: timestamp,
272
+ heads: lastHeads,
273
+ timestamp: lastSyncTimestamp,
268
274
  storageId: storageId,
269
275
  })
270
276
  }
@@ -307,7 +313,7 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
307
313
 
308
314
  subscribedDocs.add(documentId)
309
315
 
310
- const remoteHeads = this.#knownHeads.get(documentId)
316
+ const remoteHeads = this.#syncInfoByDocId.get(documentId)
311
317
  if (remoteHeads) {
312
318
  for (const [storageId, lastHeads] of remoteHeads) {
313
319
  const subscribedPeers = this.#theirSubscriptions.get(storageId)
@@ -315,8 +321,8 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
315
321
  this.emit("notify-remote-heads", {
316
322
  targetId: peerId,
317
323
  documentId,
318
- heads: lastHeads.heads,
319
- timestamp: lastHeads.timestamp,
324
+ heads: lastHeads.lastHeads,
325
+ timestamp: lastHeads.lastSyncTimestamp,
320
326
  storageId,
321
327
  })
322
328
  }
@@ -345,19 +351,19 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
345
351
  ) {
346
352
  continue
347
353
  }
348
- let remote = this.#knownHeads.get(documentId)
354
+ let remote = this.#syncInfoByDocId.get(documentId)
349
355
  if (!remote) {
350
356
  remote = new Map()
351
- this.#knownHeads.set(documentId, remote)
357
+ this.#syncInfoByDocId.set(documentId, remote)
352
358
  }
353
359
 
354
360
  const docRemote = remote.get(storageId as StorageId)
355
- if (docRemote && docRemote.timestamp >= timestamp) {
361
+ if (docRemote && docRemote.lastSyncTimestamp >= timestamp) {
356
362
  continue
357
363
  } else {
358
364
  remote.set(storageId as StorageId, {
359
- timestamp,
360
- heads: heads as UrlHeads,
365
+ lastSyncTimestamp: timestamp,
366
+ lastHeads: heads as UrlHeads,
361
367
  })
362
368
  changedHeads.push({
363
369
  documentId,
@@ -370,8 +376,3 @@ export class RemoteHeadsSubscriptions extends EventEmitter<RemoteHeadsSubscripti
370
376
  return changedHeads
371
377
  }
372
378
  }
373
-
374
- type LastHeads = {
375
- timestamp: number
376
- heads: UrlHeads
377
- }
package/src/Repo.ts CHANGED
@@ -206,17 +206,17 @@ export class Repo extends EventEmitter<RepoEvents> {
206
206
  return
207
207
  }
208
208
 
209
- const heads = handle.getRemoteHeads(storageId)
209
+ const heads = handle.getSyncInfo(storageId)?.lastHeads
210
210
  const haveHeadsChanged =
211
211
  message.syncState.theirHeads &&
212
212
  (!heads ||
213
213
  !headsAreSame(heads, encodeHeads(message.syncState.theirHeads)))
214
214
 
215
215
  if (haveHeadsChanged && message.syncState.theirHeads) {
216
- handle.setRemoteHeads(
217
- storageId,
218
- encodeHeads(message.syncState.theirHeads)
219
- )
216
+ handle.setSyncInfo(storageId, {
217
+ lastHeads: encodeHeads(message.syncState.theirHeads),
218
+ lastSyncTimestamp: Date.now(),
219
+ })
220
220
 
221
221
  if (storageId && this.#remoteHeadsGossipingEnabled) {
222
222
  this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
@@ -255,10 +255,16 @@ export class Repo extends EventEmitter<RepoEvents> {
255
255
  }
256
256
  })
257
257
 
258
- this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
259
- const handle = this.#handleCache[message.documentId]
260
- handle.setRemoteHeads(message.storageId, message.remoteHeads)
261
- })
258
+ this.#remoteHeadsSubscriptions.on(
259
+ "remote-heads-changed",
260
+ ({ documentId, storageId, remoteHeads, timestamp }) => {
261
+ const handle = this.#handleCache[documentId]
262
+ handle.setSyncInfo(storageId, {
263
+ lastHeads: remoteHeads,
264
+ lastSyncTimestamp: timestamp,
265
+ })
266
+ }
267
+ )
262
268
  }
263
269
  }
264
270
 
package/src/index.ts CHANGED
@@ -60,6 +60,7 @@ export type {
60
60
  DocHandleOptions,
61
61
  DocHandleOutboundEphemeralMessagePayload,
62
62
  HandleState,
63
+ SyncInfo,
63
64
  } from "./DocHandle.js"
64
65
 
65
66
  export type {
@@ -102,6 +102,7 @@ export class NetworkSubsystem extends EventEmitter<NetworkSubsystemEvents> {
102
102
  delete this.#adaptersByPeer[peerId as PeerId]
103
103
  }
104
104
  })
105
+ this.adapters = this.adapters.filter(a => a !== networkAdapter)
105
106
  })
106
107
 
107
108
  this.peerMetadata
@@ -587,4 +587,107 @@ describe("DocHandle", () => {
587
587
  // Cached access should be significantly faster
588
588
  expect(timeForCachedAccesses).toBeLessThan(timeForFirstAccess / 10)
589
589
  })
590
+
591
+ describe("isReadOnly", () => {
592
+ it("should return false for a regular document handle", () => {
593
+ const handle = setup()
594
+ expect(handle.isReadOnly()).toBe(false)
595
+ })
596
+
597
+ it("should return false for a newly created document handle", () => {
598
+ const handle = new DocHandle<TestDoc>(TEST_ID)
599
+ expect(handle.isReadOnly()).toBe(false)
600
+ })
601
+
602
+ it("should return true for a view handle with fixed heads", () => {
603
+ const handle = setup()
604
+ handle.change(doc => {
605
+ doc.foo = "test"
606
+ })
607
+
608
+ const heads = handle.heads()
609
+ const viewHandle = handle.view(heads)
610
+
611
+ expect(viewHandle.isReadOnly()).toBe(true)
612
+ })
613
+
614
+ it("should return true for a handle constructed with fixed heads", () => {
615
+ const handle = setup()
616
+ handle.change(doc => {
617
+ doc.foo = "test"
618
+ })
619
+
620
+ const heads = handle.heads()
621
+ const fixedHeadsHandle = new DocHandle<TestDoc>(TEST_ID, { heads })
622
+ fixedHeadsHandle.update(() => A.clone(handle.doc()!))
623
+ fixedHeadsHandle.doneLoading()
624
+
625
+ expect(fixedHeadsHandle.isReadOnly()).toBe(true)
626
+ })
627
+
628
+ it("should return false after regular changes", () => {
629
+ const handle = setup()
630
+
631
+ // Initially not read-only
632
+ expect(handle.isReadOnly()).toBe(false)
633
+
634
+ // Make a change
635
+ handle.change(doc => {
636
+ doc.foo = "changed"
637
+ })
638
+
639
+ // Still not read-only
640
+ expect(handle.isReadOnly()).toBe(false)
641
+ })
642
+ })
643
+
644
+ it("should continue to function after recovering from an exception in change", () => {
645
+ const handle = setup()
646
+
647
+ // throw an error in the change handler, but catch it
648
+ let expectedErr = new Error("Argh!")
649
+ let err: Error | null = null
650
+ try {
651
+ handle.change(doc => {
652
+ doc.foo = "bar"
653
+ throw expectedErr
654
+ })
655
+ } catch (e) {
656
+ err = e
657
+ }
658
+ assert.equal(err, expectedErr, "should have thrown the error")
659
+
660
+ // Future changes should still work
661
+ handle.change(doc => {
662
+ doc.foo = "baz"
663
+ })
664
+ assert.equal(handle.doc()?.foo, "baz", "should have changed foo to baz")
665
+ })
666
+
667
+ it("should continue to function after recovering from an exception in changeAt", () => {
668
+ const handle = setup()
669
+ handle.change(d => (d.foo = "bar"))
670
+
671
+ const heads = handle.heads()!
672
+ handle.change(d => (d.foo = "qux"))
673
+
674
+ // throw an error in the change handler, but catch it
675
+ let expectedErr = new Error("Argh!")
676
+ let err: Error | null = null
677
+ try {
678
+ handle.changeAt(heads, doc => {
679
+ doc.foo = "bar"
680
+ throw expectedErr
681
+ })
682
+ } catch (e) {
683
+ err = e
684
+ }
685
+ assert.equal(err, expectedErr, "should have thrown the error")
686
+
687
+ // Future changes should still work
688
+ const newHeads = handle.changeAt(heads, doc => {
689
+ doc.foo = "baz"
690
+ })
691
+ assert.equal(handle.view(newHeads).doc().foo, "baz")
692
+ })
590
693
  })
@@ -3,7 +3,7 @@ import assert from "assert"
3
3
  import { describe, it } from "vitest"
4
4
  import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
5
5
  import { RemoteHeadsSubscriptions } from "../src/RemoteHeadsSubscriptions.js"
6
- import { PeerId, StorageId } from "../src/index.js"
6
+ import { PeerId, StorageId, UrlHeads } from "../src/index.js"
7
7
  import {
8
8
  RemoteHeadsChanged,
9
9
  RemoteSubscriptionControlMessage,
@@ -32,7 +32,7 @@ describe("RepoHeadsSubscriptions", () => {
32
32
  newHeads: {
33
33
  [storageB]: {
34
34
  heads: [],
35
- timestamp: Date.now(),
35
+ timestamp: 1000,
36
36
  },
37
37
  },
38
38
  }
@@ -45,7 +45,7 @@ describe("RepoHeadsSubscriptions", () => {
45
45
  newHeads: {
46
46
  [storageB]: {
47
47
  heads: [],
48
- timestamp: Date.now(),
48
+ timestamp: 2000,
49
49
  },
50
50
  },
51
51
  }
@@ -64,7 +64,7 @@ describe("RepoHeadsSubscriptions", () => {
64
64
  newHeads: {
65
65
  [storageB]: {
66
66
  heads: docBHeads,
67
- timestamp: Date.now() + 1,
67
+ timestamp: 3000,
68
68
  },
69
69
  },
70
70
  }
@@ -153,7 +153,7 @@ describe("RepoHeadsSubscriptions", () => {
153
153
  remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
154
154
  docC,
155
155
  storageB,
156
- []
156
+ [] as UrlHeads
157
157
  )
158
158
 
159
159
  // should forward remote-heads events
@@ -233,7 +233,7 @@ describe("RepoHeadsSubscriptions", () => {
233
233
  remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
234
234
  docC,
235
235
  storageB,
236
- []
236
+ [] as UrlHeads
237
237
  )
238
238
 
239
239
  // expect peer c to be notified both changes
@@ -279,7 +279,7 @@ describe("RepoHeadsSubscriptions", () => {
279
279
  remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(
280
280
  docC,
281
281
  storageB,
282
- []
282
+ [] as UrlHeads
283
283
  )
284
284
 
285
285
  // expect peer c to be notified both changes
@@ -309,6 +309,7 @@ describe("RepoHeadsSubscriptions", () => {
309
309
  assert.strictEqual(messages[0].storageId, storageB)
310
310
  assert.strictEqual(messages[0].documentId, docB)
311
311
  assert.deepStrictEqual(messages[0].remoteHeads, docBHeads)
312
+ assert.strictEqual(messages[0].timestamp, 3000)
312
313
  })
313
314
 
314
315
  it("should remove subs of disconnected peers", async () => {
package/test/Repo.test.ts CHANGED
@@ -1326,9 +1326,10 @@ describe("Repo", () => {
1326
1326
  const nextRemoteHeadsPromise = new Promise<{
1327
1327
  storageId: StorageId
1328
1328
  heads: UrlHeads
1329
+ timestamp: number
1329
1330
  }>(resolve => {
1330
- handle.on("remote-heads", ({ storageId, heads }) => {
1331
- resolve({ storageId, heads })
1331
+ handle.on("remote-heads", ({ storageId, heads, timestamp }) => {
1332
+ resolve({ storageId, heads, timestamp })
1332
1333
  })
1333
1334
  })
1334
1335
 
@@ -1348,10 +1349,10 @@ describe("Repo", () => {
1348
1349
  assert.deepStrictEqual(nextRemoteHeads.storageId, charliedStorageId)
1349
1350
  assert.deepStrictEqual(nextRemoteHeads.heads, charlieHandle.heads())
1350
1351
 
1351
- assert.deepStrictEqual(
1352
- handle.getRemoteHeads(charliedStorageId),
1353
- charlieHandle.heads()
1354
- )
1352
+ const syncInfo = handle.getSyncInfo(charliedStorageId)
1353
+
1354
+ assert.deepStrictEqual(syncInfo?.lastHeads, charlieHandle.heads())
1355
+ assert.strictEqual(syncInfo?.lastSyncTimestamp, nextRemoteHeads.timestamp)
1355
1356
 
1356
1357
  teardown()
1357
1358
  })
@@ -9,6 +9,7 @@ import {
9
9
  DocHandleRemoteHeadsPayload,
10
10
  PeerId,
11
11
  Repo,
12
+ UrlHeads,
12
13
  } from "../src/index.js"
13
14
  import { DummyStorageAdapter } from "../src/helpers/DummyStorageAdapter.js"
14
15
  import { collectMessages } from "./helpers/collectMessages.js"
@@ -18,7 +19,7 @@ import { pause } from "../src/helpers/pause.js"
18
19
  describe("DocHandle.remoteHeads", () => {
19
20
  const TEST_ID = parseAutomergeUrl(generateAutomergeUrl()).documentId
20
21
 
21
- it("should allow to listen for remote head changes and manually read remote heads", async () => {
22
+ it("should allow to listen for remote head changes and manually read sync info", async () => {
22
23
  const handle = new DocHandle<TestDoc>(TEST_ID, { isNew: true })
23
24
  const bobRepo = new Repo({
24
25
  peerId: "bob" as PeerId,
@@ -29,7 +30,10 @@ describe("DocHandle.remoteHeads", () => {
29
30
  const bobStorageId = await bobRepo.storageId()
30
31
 
31
32
  const remoteHeadsMessagePromise = eventPromise(handle, "remote-heads")
32
- handle.setRemoteHeads(bobStorageId, [])
33
+ handle.setSyncInfo(bobStorageId, {
34
+ lastHeads: [] as UrlHeads,
35
+ lastSyncTimestamp: 1000,
36
+ })
33
37
 
34
38
  const remoteHeadsMessage = await remoteHeadsMessagePromise
35
39
 
@@ -37,7 +41,13 @@ describe("DocHandle.remoteHeads", () => {
37
41
  assert.deepStrictEqual(remoteHeadsMessage.heads, [])
38
42
 
39
43
  // read remote heads manually
40
- assert.deepStrictEqual(handle.getRemoteHeads(bobStorageId), [])
44
+
45
+ const syncInfo = handle.getSyncInfo(bobStorageId)
46
+
47
+ assert.deepStrictEqual(syncInfo, {
48
+ lastHeads: [] as UrlHeads,
49
+ lastSyncTimestamp: 1000,
50
+ })
41
51
  })
42
52
 
43
53
  describe("multi hop sync", () => {