@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.
- package/dist/DocHandle.d.ts +23 -2
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +70 -23
- package/dist/RemoteHeadsSubscriptions.d.ts.map +1 -1
- package/dist/RemoteHeadsSubscriptions.js +29 -24
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +11 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +1 -0
- package/package.json +2 -2
- package/src/DocHandle.ts +79 -23
- package/src/RemoteHeadsSubscriptions.ts +31 -30
- package/src/Repo.ts +15 -9
- package/src/index.ts +1 -0
- package/src/network/NetworkSubsystem.ts +1 -0
- package/test/DocHandle.test.ts +103 -0
- package/test/RemoteHeadsSubscriptions.test.ts +8 -7
- package/test/Repo.test.ts +7 -6
- package/test/remoteHeads.test.ts +13 -3
package/dist/DocHandle.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/DocHandle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAU5C,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAS,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;;IAwBvD,UAAU,EAAE,UAAU;IAF/B,cAAc;gBAEL,UAAU,EAAE,UAAU,EAC7B,OAAO,GAAE,gBAAgB,CAAC,CAAC,CAAM;
|
|
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
|
|
35
|
-
#
|
|
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.#
|
|
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
|
-
|
|
407
|
-
this.#
|
|
408
|
-
this.emit("remote-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.#
|
|
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.#
|
|
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.#
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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;
|
|
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
|
-
//
|
|
5
|
-
#
|
|
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
|
|
95
|
-
if (!
|
|
94
|
+
const syncInfo = this.#syncInfoByDocId.get(documentId);
|
|
95
|
+
if (!syncInfo) {
|
|
96
96
|
continue;
|
|
97
97
|
}
|
|
98
|
-
const
|
|
99
|
-
if (
|
|
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
|
|
104
|
-
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.#
|
|
159
|
+
const remote = this.#syncInfoByDocId.get(documentId);
|
|
160
160
|
const timestamp = Date.now();
|
|
161
161
|
if (!remote) {
|
|
162
|
-
this.#
|
|
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.
|
|
167
|
-
remote.set(storageId, {
|
|
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.#
|
|
195
|
-
for (const [storageId, {
|
|
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:
|
|
200
|
-
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.#
|
|
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.
|
|
243
|
-
timestamp: lastHeads.
|
|
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.#
|
|
268
|
+
let remote = this.#syncInfoByDocId.get(documentId);
|
|
264
269
|
if (!remote) {
|
|
265
270
|
remote = new Map();
|
|
266
|
-
this.#
|
|
271
|
+
this.#syncInfoByDocId.set(documentId, remote);
|
|
267
272
|
}
|
|
268
273
|
const docRemote = remote.get(storageId);
|
|
269
|
-
if (docRemote && docRemote.
|
|
274
|
+
if (docRemote && docRemote.lastSyncTimestamp >= timestamp) {
|
|
270
275
|
continue;
|
|
271
276
|
}
|
|
272
277
|
else {
|
|
273
278
|
remote.set(storageId, {
|
|
274
|
-
timestamp,
|
|
275
|
-
|
|
279
|
+
lastSyncTimestamp: timestamp,
|
|
280
|
+
lastHeads: heads,
|
|
276
281
|
});
|
|
277
282
|
changedHeads.push({
|
|
278
283
|
documentId,
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAEL,SAAS,EAKV,MAAM,gBAAgB,CAAA;AAIvB,OAAO,EACL,uBAAuB,EACvB,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,cAAc,EAEf,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,MAAM,EACP,MAAM,YAAY,CAAA;AACnB,OAAO,EAAa,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,MAAM,MAAM,uBAAuB,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG;IACzD,UAAU,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAChE,IAAI,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,CAAA;IAC3B,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CACzE,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,CAAA;IAC3B,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;IACxE,UAAU,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;CACjE,CAAA;AAMD,8FAA8F;AAC9F;;;;;;GAMG;AACH,qBAAa,IAAK,SAAQ,YAAY,CAAC,UAAU,CAAC;;IAGhD,cAAc;IACd,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc;IACd,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC,mDAAmD;IACnD,cAAc;IACd,gBAAgB,SAAM;IAItB,cAAc;IACd,YAAY,EAAE,sBAAsB,CAAA;IAEpC,sDAAsD;IACtD,cAAc;IACd,WAAW,EAAE,WAAW,CAAmB;IAE3C,8GAA8G;IAC9G,cAAc;IACd,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAK;gBAM3C,EACV,OAAO,EACP,OAAY,EACZ,MAAuB,EACvB,WAAW,EACX,WAAmC,EACnC,0BAAkC,EAClC,QAAa,GACd,GAAE,UAAe;
|
|
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.
|
|
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.
|
|
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",
|
|
158
|
-
const handle = this.#handleCache[
|
|
159
|
-
handle.
|
|
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -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;
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "2.0.
|
|
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": "
|
|
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
|
|
46
|
-
#
|
|
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.#
|
|
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
|
-
|
|
486
|
-
this.#
|
|
487
|
-
this.emit("remote-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.#
|
|
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.#
|
|
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.#
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
-
//
|
|
39
|
-
#
|
|
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
|
|
146
|
-
if (!
|
|
146
|
+
const syncInfo = this.#syncInfoByDocId.get(documentId)
|
|
147
|
+
if (!syncInfo) {
|
|
147
148
|
continue
|
|
148
149
|
}
|
|
149
150
|
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
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
|
|
156
|
-
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.#
|
|
222
|
+
const remote = this.#syncInfoByDocId.get(documentId)
|
|
222
223
|
const timestamp = Date.now()
|
|
223
224
|
if (!remote) {
|
|
224
|
-
this.#
|
|
225
|
+
this.#syncInfoByDocId.set(
|
|
225
226
|
documentId,
|
|
226
|
-
new Map([
|
|
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.
|
|
231
|
-
remote.set(storageId, {
|
|
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.#
|
|
262
|
-
for (const [storageId, {
|
|
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:
|
|
267
|
-
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.#
|
|
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.
|
|
319
|
-
timestamp: lastHeads.
|
|
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.#
|
|
354
|
+
let remote = this.#syncInfoByDocId.get(documentId)
|
|
349
355
|
if (!remote) {
|
|
350
356
|
remote = new Map()
|
|
351
|
-
this.#
|
|
357
|
+
this.#syncInfoByDocId.set(documentId, remote)
|
|
352
358
|
}
|
|
353
359
|
|
|
354
360
|
const docRemote = remote.get(storageId as StorageId)
|
|
355
|
-
if (docRemote && docRemote.
|
|
361
|
+
if (docRemote && docRemote.lastSyncTimestamp >= timestamp) {
|
|
356
362
|
continue
|
|
357
363
|
} else {
|
|
358
364
|
remote.set(storageId as StorageId, {
|
|
359
|
-
timestamp,
|
|
360
|
-
|
|
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.
|
|
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.
|
|
217
|
-
|
|
218
|
-
|
|
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(
|
|
259
|
-
|
|
260
|
-
|
|
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
package/test/DocHandle.test.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
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
|
})
|
package/test/remoteHeads.test.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
-
|
|
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", () => {
|