@automerge/automerge-repo 1.0.0-alpha.0 → 1.0.0-alpha.3
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/DocCollection.d.ts +2 -1
- package/dist/DocCollection.d.ts.map +1 -1
- package/dist/DocCollection.js +17 -8
- package/dist/DocHandle.d.ts +27 -7
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +47 -23
- package/dist/DocUrl.d.ts +3 -3
- package/dist/DocUrl.js +9 -9
- package/dist/EphemeralData.d.ts +8 -16
- package/dist/EphemeralData.d.ts.map +1 -1
- package/dist/EphemeralData.js +1 -28
- package/dist/Repo.d.ts +0 -2
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +18 -36
- package/dist/helpers/headsAreSame.d.ts +2 -2
- package/dist/helpers/headsAreSame.d.ts.map +1 -1
- package/dist/helpers/headsAreSame.js +1 -4
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +15 -13
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/network/NetworkAdapter.d.ts +4 -13
- package/dist/network/NetworkAdapter.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.d.ts +5 -4
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +39 -25
- package/dist/network/messages.d.ts +57 -0
- package/dist/network/messages.d.ts.map +1 -0
- package/dist/network/messages.js +21 -0
- package/dist/storage/StorageSubsystem.d.ts +2 -2
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +36 -6
- package/dist/synchronizer/CollectionSynchronizer.d.ts +3 -2
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +19 -13
- package/dist/synchronizer/DocSynchronizer.d.ts +9 -3
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +145 -29
- package/dist/synchronizer/Synchronizer.d.ts +3 -4
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/dist/types.d.ts +1 -3
- package/dist/types.d.ts.map +1 -1
- package/fuzz/fuzz.ts +4 -4
- package/package.json +3 -3
- package/src/DocCollection.ts +19 -9
- package/src/DocHandle.ts +82 -37
- package/src/DocUrl.ts +9 -9
- package/src/EphemeralData.ts +6 -36
- package/src/Repo.ts +20 -52
- package/src/helpers/headsAreSame.ts +3 -5
- package/src/helpers/tests/network-adapter-tests.ts +18 -14
- package/src/index.ts +12 -2
- package/src/network/NetworkAdapter.ts +4 -20
- package/src/network/NetworkSubsystem.ts +61 -38
- package/src/network/messages.ts +123 -0
- package/src/storage/StorageSubsystem.ts +42 -6
- package/src/synchronizer/CollectionSynchronizer.ts +38 -19
- package/src/synchronizer/DocSynchronizer.ts +196 -38
- package/src/synchronizer/Synchronizer.ts +3 -8
- package/src/types.ts +4 -1
- package/test/CollectionSynchronizer.test.ts +6 -7
- package/test/DocHandle.test.ts +36 -22
- package/test/DocSynchronizer.test.ts +85 -9
- package/test/Repo.test.ts +279 -59
- package/test/StorageSubsystem.test.ts +9 -9
- package/test/helpers/DummyNetworkAdapter.ts +1 -1
- package/tsconfig.json +2 -1
- package/test/EphemeralData.test.ts +0 -44
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import * as A from "@automerge/automerge";
|
|
2
|
-
import { READY, REQUESTING } from "../DocHandle.js";
|
|
2
|
+
import { READY, REQUESTING, UNAVAILABLE, } from "../DocHandle.js";
|
|
3
3
|
import { Synchronizer } from "./Synchronizer.js";
|
|
4
4
|
import debug from "debug";
|
|
5
|
+
import { isRequestMessage, } from "../network/messages.js";
|
|
6
|
+
import { decode } from "cbor-x";
|
|
5
7
|
/**
|
|
6
8
|
* DocSynchronizer takes a handle to an Automerge document, and receives & dispatches sync messages
|
|
7
9
|
* to bring it inline with all other peers' versions.
|
|
@@ -13,9 +15,11 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
13
15
|
#opsLog;
|
|
14
16
|
/** Active peers */
|
|
15
17
|
#peers = [];
|
|
18
|
+
#peerDocumentStatuses = {};
|
|
16
19
|
/** Sync state for each peer we've communicated with (including inactive peers) */
|
|
17
20
|
#syncStates = {};
|
|
18
21
|
#pendingSyncMessages = [];
|
|
22
|
+
#syncStarted = false;
|
|
19
23
|
constructor(handle) {
|
|
20
24
|
super();
|
|
21
25
|
this.handle = handle;
|
|
@@ -24,12 +28,16 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
24
28
|
this.#log = debug(`automerge-repo:docsync:${docId}`);
|
|
25
29
|
this.#opsLog = debug(`automerge-repo:ops:docsync:${docId}`); // Log list of ops of each message
|
|
26
30
|
handle.on("change", () => this.#syncWithPeers());
|
|
31
|
+
handle.on("ephemeral-message-outbound", payload => this.#broadcastToPeers(payload));
|
|
27
32
|
// Process pending sync messages immediately after the handle becomes ready.
|
|
28
33
|
void (async () => {
|
|
29
34
|
await handle.doc([READY, REQUESTING]);
|
|
30
35
|
this.#processAllPendingSyncMessages();
|
|
31
36
|
})();
|
|
32
37
|
}
|
|
38
|
+
get peerStates() {
|
|
39
|
+
return this.#peerDocumentStatuses;
|
|
40
|
+
}
|
|
33
41
|
get documentId() {
|
|
34
42
|
return this.handle.documentId;
|
|
35
43
|
}
|
|
@@ -37,13 +45,32 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
37
45
|
async #syncWithPeers() {
|
|
38
46
|
this.#log(`syncWithPeers`);
|
|
39
47
|
const doc = await this.handle.doc();
|
|
48
|
+
if (doc === undefined)
|
|
49
|
+
return;
|
|
40
50
|
this.#peers.forEach(peerId => this.#sendSyncMessage(peerId, doc));
|
|
41
51
|
}
|
|
52
|
+
async #broadcastToPeers({ data }) {
|
|
53
|
+
this.#log(`broadcastToPeers`, this.#peers);
|
|
54
|
+
this.#peers.forEach(peerId => this.#sendEphemeralMessage(peerId, data));
|
|
55
|
+
}
|
|
56
|
+
#sendEphemeralMessage(peerId, data) {
|
|
57
|
+
this.#log(`sendEphemeralMessage ->${peerId}`);
|
|
58
|
+
this.emit("message", {
|
|
59
|
+
type: "ephemeral",
|
|
60
|
+
targetId: peerId,
|
|
61
|
+
documentId: this.handle.documentId,
|
|
62
|
+
data,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
42
65
|
#getSyncState(peerId) {
|
|
43
66
|
if (!this.#peers.includes(peerId)) {
|
|
44
67
|
this.#log("adding a new peer", peerId);
|
|
45
68
|
this.#peers.push(peerId);
|
|
46
69
|
}
|
|
70
|
+
// when a peer is added, we don't know if it has the document or not
|
|
71
|
+
if (!(peerId in this.#peerDocumentStatuses)) {
|
|
72
|
+
this.#peerDocumentStatuses[peerId] = "unknown";
|
|
73
|
+
}
|
|
47
74
|
return this.#syncStates[peerId] ?? A.initSyncState();
|
|
48
75
|
}
|
|
49
76
|
#setSyncState(peerId, syncState) {
|
|
@@ -59,16 +86,32 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
59
86
|
this.#setSyncState(peerId, newSyncState);
|
|
60
87
|
if (message) {
|
|
61
88
|
this.#logMessage(`sendSyncMessage 🡒 ${peerId}`, message);
|
|
62
|
-
const
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
const decoded = A.decodeSyncMessage(message);
|
|
90
|
+
if (!this.handle.isReady() &&
|
|
91
|
+
decoded.heads.length === 0 &&
|
|
92
|
+
newSyncState.sharedHeads.length === 0 &&
|
|
93
|
+
!Object.values(this.#peerDocumentStatuses).includes("has") &&
|
|
94
|
+
this.#peerDocumentStatuses[peerId] === "unknown") {
|
|
95
|
+
// we don't have the document (or access to it), so we request it
|
|
96
|
+
this.emit("message", {
|
|
97
|
+
type: "request",
|
|
98
|
+
targetId: peerId,
|
|
99
|
+
documentId: this.handle.documentId,
|
|
100
|
+
data: message,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
this.emit("message", {
|
|
105
|
+
type: "sync",
|
|
106
|
+
targetId: peerId,
|
|
107
|
+
data: message,
|
|
108
|
+
documentId: this.handle.documentId,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// if we have sent heads, then the peer now has or will have the document
|
|
112
|
+
if (decoded.heads.length > 0) {
|
|
113
|
+
this.#peerDocumentStatuses[peerId] = "has";
|
|
114
|
+
}
|
|
72
115
|
}
|
|
73
116
|
}
|
|
74
117
|
#logMessage = (label, message) => {
|
|
@@ -89,48 +132,121 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
89
132
|
hasPeer(peerId) {
|
|
90
133
|
return this.#peers.includes(peerId);
|
|
91
134
|
}
|
|
92
|
-
beginSync(
|
|
93
|
-
this.#log(`beginSync: ${
|
|
135
|
+
beginSync(peerIds) {
|
|
136
|
+
this.#log(`beginSync: ${peerIds.join(", ")}`);
|
|
94
137
|
// At this point if we don't have anything in our storage, we need to use an empty doc to sync
|
|
95
138
|
// with; but we don't want to surface that state to the front end
|
|
96
|
-
void this.handle.doc([READY, REQUESTING]).then(doc => {
|
|
139
|
+
void this.handle.doc([READY, REQUESTING, UNAVAILABLE]).then(doc => {
|
|
140
|
+
// if we don't have any peers, then we can say the document is unavailable
|
|
97
141
|
// HACK: if we have a sync state already, we round-trip it through the encoding system to make
|
|
98
142
|
// sure state is preserved. This prevents an infinite loop caused by failed attempts to send
|
|
99
143
|
// messages during disconnection.
|
|
100
144
|
// TODO: cover that case with a test and remove this hack
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
145
|
+
peerIds.forEach(peerId => {
|
|
146
|
+
const syncStateRaw = this.#getSyncState(peerId);
|
|
147
|
+
const syncState = A.decodeSyncState(A.encodeSyncState(syncStateRaw));
|
|
148
|
+
this.#setSyncState(peerId, syncState);
|
|
149
|
+
});
|
|
150
|
+
// we register out peers first, then say that sync has started
|
|
151
|
+
this.#syncStarted = true;
|
|
152
|
+
this.#checkDocUnavailable();
|
|
153
|
+
if (doc === undefined)
|
|
154
|
+
return;
|
|
155
|
+
peerIds.forEach(peerId => {
|
|
156
|
+
this.#sendSyncMessage(peerId, doc);
|
|
157
|
+
});
|
|
105
158
|
});
|
|
106
159
|
}
|
|
107
160
|
endSync(peerId) {
|
|
108
161
|
this.#log(`removing peer ${peerId}`);
|
|
109
162
|
this.#peers = this.#peers.filter(p => p !== peerId);
|
|
110
163
|
}
|
|
111
|
-
|
|
112
|
-
|
|
164
|
+
receiveMessage(message) {
|
|
165
|
+
switch (message.type) {
|
|
166
|
+
case "sync":
|
|
167
|
+
case "request":
|
|
168
|
+
this.receiveSyncMessage(message);
|
|
169
|
+
break;
|
|
170
|
+
case "ephemeral":
|
|
171
|
+
this.receiveEphemeralMessage(message);
|
|
172
|
+
break;
|
|
173
|
+
case "doc-unavailable":
|
|
174
|
+
this.#peerDocumentStatuses[message.senderId] = "unavailable";
|
|
175
|
+
this.#checkDocUnavailable();
|
|
176
|
+
break;
|
|
177
|
+
default:
|
|
178
|
+
throw new Error(`unknown message type: ${message}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
receiveEphemeralMessage(message) {
|
|
182
|
+
if (message.documentId !== this.handle.documentId)
|
|
183
|
+
throw new Error(`channelId doesn't match documentId`);
|
|
184
|
+
const { senderId, data } = message;
|
|
185
|
+
const contents = decode(data);
|
|
186
|
+
this.handle.emit("ephemeral-message", {
|
|
187
|
+
handle: this.handle,
|
|
188
|
+
senderId,
|
|
189
|
+
message: contents,
|
|
190
|
+
});
|
|
191
|
+
this.#peers.forEach(peerId => {
|
|
192
|
+
if (peerId === senderId)
|
|
193
|
+
return;
|
|
194
|
+
this.emit("message", {
|
|
195
|
+
...message,
|
|
196
|
+
targetId: peerId,
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
receiveSyncMessage(message) {
|
|
201
|
+
if (message.documentId !== this.handle.documentId)
|
|
113
202
|
throw new Error(`channelId doesn't match documentId`);
|
|
114
203
|
// We need to block receiving the syncMessages until we've checked local storage
|
|
115
|
-
if (!this.handle.inState([READY, REQUESTING])) {
|
|
116
|
-
this.#pendingSyncMessages.push(
|
|
204
|
+
if (!this.handle.inState([READY, REQUESTING, UNAVAILABLE])) {
|
|
205
|
+
this.#pendingSyncMessages.push(message);
|
|
117
206
|
return;
|
|
118
207
|
}
|
|
119
208
|
this.#processAllPendingSyncMessages();
|
|
120
|
-
this.#processSyncMessage(
|
|
209
|
+
this.#processSyncMessage(message);
|
|
121
210
|
}
|
|
122
|
-
#processSyncMessage(
|
|
211
|
+
#processSyncMessage(message) {
|
|
212
|
+
if (isRequestMessage(message)) {
|
|
213
|
+
this.#peerDocumentStatuses[message.senderId] = "wants";
|
|
214
|
+
}
|
|
215
|
+
this.#checkDocUnavailable();
|
|
216
|
+
// if the message has heads, then the peer has the document
|
|
217
|
+
if (A.decodeSyncMessage(message.data).heads.length > 0) {
|
|
218
|
+
this.#peerDocumentStatuses[message.senderId] = "has";
|
|
219
|
+
}
|
|
123
220
|
this.handle.update(doc => {
|
|
124
|
-
const [newDoc, newSyncState] = A.receiveSyncMessage(doc, this.#getSyncState(
|
|
125
|
-
this.#setSyncState(
|
|
221
|
+
const [newDoc, newSyncState] = A.receiveSyncMessage(doc, this.#getSyncState(message.senderId), message.data);
|
|
222
|
+
this.#setSyncState(message.senderId, newSyncState);
|
|
126
223
|
// respond to just this peer (as required)
|
|
127
|
-
this.#sendSyncMessage(
|
|
224
|
+
this.#sendSyncMessage(message.senderId, doc);
|
|
128
225
|
return newDoc;
|
|
129
226
|
});
|
|
227
|
+
this.#checkDocUnavailable();
|
|
228
|
+
}
|
|
229
|
+
#checkDocUnavailable() {
|
|
230
|
+
// if we know none of the peers have the document, tell all our peers that we don't either
|
|
231
|
+
if (this.#syncStarted &&
|
|
232
|
+
this.handle.inState([REQUESTING]) &&
|
|
233
|
+
this.#peers.every(peerId => this.#peerDocumentStatuses[peerId] === "unavailable" ||
|
|
234
|
+
this.#peerDocumentStatuses[peerId] === "wants")) {
|
|
235
|
+
this.#peers
|
|
236
|
+
.filter(peerId => this.#peerDocumentStatuses[peerId] === "wants")
|
|
237
|
+
.forEach(peerId => {
|
|
238
|
+
this.emit("message", {
|
|
239
|
+
type: "doc-unavailable",
|
|
240
|
+
documentId: this.handle.documentId,
|
|
241
|
+
targetId: peerId,
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
this.handle.unavailable();
|
|
245
|
+
}
|
|
130
246
|
}
|
|
131
247
|
#processAllPendingSyncMessages() {
|
|
132
|
-
for (const
|
|
133
|
-
this.#processSyncMessage(
|
|
248
|
+
for (const message of this.#pendingSyncMessages) {
|
|
249
|
+
this.#processSyncMessage(message);
|
|
134
250
|
}
|
|
135
251
|
this.#pendingSyncMessages = [];
|
|
136
252
|
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import EventEmitter from "eventemitter3";
|
|
2
|
-
import {
|
|
3
|
-
import { MessagePayload } from "../network/NetworkAdapter.js";
|
|
2
|
+
import { Message, MessageContents } from "../network/messages.js";
|
|
4
3
|
export declare abstract class Synchronizer extends EventEmitter<SynchronizerEvents> {
|
|
5
|
-
abstract
|
|
4
|
+
abstract receiveMessage(message: Message): void;
|
|
6
5
|
}
|
|
7
6
|
export interface SynchronizerEvents {
|
|
8
|
-
message: (arg:
|
|
7
|
+
message: (arg: MessageContents) => void;
|
|
9
8
|
}
|
|
10
9
|
//# sourceMappingURL=Synchronizer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAEjE,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;CAChD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;CACxC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -10,7 +10,5 @@ export type BinaryDocumentId = Uint8Array & {
|
|
|
10
10
|
export type PeerId = string & {
|
|
11
11
|
__peerId: false;
|
|
12
12
|
};
|
|
13
|
-
export type
|
|
14
|
-
__channelId: false;
|
|
15
|
-
};
|
|
13
|
+
export type DistributiveOmit<T, K extends keyof any> = T extends any ? Omit<T, K> : never;
|
|
16
14
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,YAAY,EAAE,IAAI,CAAA;CAAE,CAAA;AACxD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,aAAa,EAAE,IAAI,CAAA;CAAE,CAAA;AAC3D,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAAE,kBAAkB,EAAE,IAAI,CAAA;CAAE,CAAA;AAExE,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,YAAY,EAAE,IAAI,CAAA;CAAE,CAAA;AACxD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,aAAa,EAAE,IAAI,CAAA;CAAE,CAAA;AAC3D,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG;IAAE,kBAAkB,EAAE,IAAI,CAAA;CAAE,CAAA;AAExE,MAAM,MAAM,MAAM,GAAG,MAAM,GAAG;IAAE,QAAQ,EAAE,KAAK,CAAA;CAAE,CAAA;AAEjD,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,SAAS,MAAM,GAAG,IAAI,CAAC,SAAS,GAAG,GAChE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GACV,KAAK,CAAA"}
|
package/fuzz/fuzz.ts
CHANGED
|
@@ -2,7 +2,7 @@ import assert from "assert"
|
|
|
2
2
|
import { MessageChannelNetworkAdapter } from "@automerge/automerge-repo-network-messagechannel"
|
|
3
3
|
import * as Automerge from "@automerge/automerge"
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { DocHandle, DocumentId, PeerId, SharePolicy } from "../src"
|
|
6
6
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
7
7
|
import { pause } from "../src/helpers/pause.js"
|
|
8
8
|
import { Repo } from "../src/Repo.js"
|
|
@@ -105,9 +105,9 @@ for (let i = 0; i < 100000; i++) {
|
|
|
105
105
|
})
|
|
106
106
|
|
|
107
107
|
await pause(0)
|
|
108
|
-
const a = await aliceRepo.find(doc.
|
|
109
|
-
const b = await bobRepo.find(doc.
|
|
110
|
-
const c = await charlieRepo.find(doc.
|
|
108
|
+
const a = await aliceRepo.find(doc.url).doc()
|
|
109
|
+
const b = await bobRepo.find(doc.url).doc()
|
|
110
|
+
const c = await charlieRepo.find(doc.url).doc()
|
|
111
111
|
assert.deepStrictEqual(a, b, "A and B should be equal")
|
|
112
112
|
assert.deepStrictEqual(b, c, "B and C should be equal")
|
|
113
113
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.3",
|
|
4
4
|
"description": "A repository object to manage a collection of automerge documents",
|
|
5
5
|
"repository": "https://github.com/automerge/automerge-repo",
|
|
6
6
|
"author": "Peter van Hardenberg <pvh@pvh.ca>",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"typescript": "^5.1.6"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@automerge/automerge": "^2.1.0-alpha.
|
|
34
|
+
"@automerge/automerge": "^2.1.0-alpha.10"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"bs58check": "^3.0.1",
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"publishConfig": {
|
|
66
66
|
"access": "public"
|
|
67
67
|
},
|
|
68
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "0ed108273084319aeea64ceccb49c3d58709f107"
|
|
69
69
|
}
|
package/src/DocCollection.ts
CHANGED
|
@@ -72,8 +72,8 @@ export class DocCollection extends EventEmitter<DocCollectionEvents> {
|
|
|
72
72
|
// - pass a "reify" function that takes a `<any>` and returns `<T>`
|
|
73
73
|
|
|
74
74
|
// Generate a new UUID and store it in the buffer
|
|
75
|
-
const {
|
|
76
|
-
const handle = this.#getHandle<T>(
|
|
75
|
+
const { documentId } = parseAutomergeUrl(generateAutomergeUrl())
|
|
76
|
+
const handle = this.#getHandle<T>(documentId, true) as DocHandle<T>
|
|
77
77
|
this.emit("document", { handle })
|
|
78
78
|
return handle
|
|
79
79
|
}
|
|
@@ -90,12 +90,21 @@ export class DocCollection extends EventEmitter<DocCollectionEvents> {
|
|
|
90
90
|
throw new Error(`Invalid AutomergeUrl: '${automergeUrl}'`)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
const {
|
|
93
|
+
const { documentId } = parseAutomergeUrl(automergeUrl)
|
|
94
94
|
// If we have the handle cached, return it
|
|
95
|
-
if (this.#handleCache[
|
|
96
|
-
|
|
95
|
+
if (this.#handleCache[documentId]) {
|
|
96
|
+
if (this.#handleCache[documentId].isUnavailable()) {
|
|
97
|
+
// this ensures that the event fires after the handle has been returned
|
|
98
|
+
setTimeout(() => {
|
|
99
|
+
this.#handleCache[documentId].emit("unavailable", {
|
|
100
|
+
handle: this.#handleCache[documentId],
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
return this.#handleCache[documentId]
|
|
105
|
+
}
|
|
97
106
|
|
|
98
|
-
const handle = this.#getHandle<T>(
|
|
107
|
+
const handle = this.#getHandle<T>(documentId, false) as DocHandle<T>
|
|
99
108
|
this.emit("document", { handle })
|
|
100
109
|
return handle
|
|
101
110
|
}
|
|
@@ -105,7 +114,7 @@ export class DocCollection extends EventEmitter<DocCollectionEvents> {
|
|
|
105
114
|
id: DocumentId | AutomergeUrl
|
|
106
115
|
) {
|
|
107
116
|
if (isValidAutomergeUrl(id)) {
|
|
108
|
-
;({
|
|
117
|
+
;({ documentId: id } = parseAutomergeUrl(id))
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
const handle = this.#getHandle(id, false)
|
|
@@ -113,7 +122,7 @@ export class DocCollection extends EventEmitter<DocCollectionEvents> {
|
|
|
113
122
|
|
|
114
123
|
delete this.#handleCache[id]
|
|
115
124
|
this.emit("delete-document", {
|
|
116
|
-
|
|
125
|
+
documentId: id,
|
|
117
126
|
})
|
|
118
127
|
}
|
|
119
128
|
}
|
|
@@ -122,6 +131,7 @@ export class DocCollection extends EventEmitter<DocCollectionEvents> {
|
|
|
122
131
|
interface DocCollectionEvents {
|
|
123
132
|
document: (arg: DocumentPayload) => void
|
|
124
133
|
"delete-document": (arg: DeleteDocumentPayload) => void
|
|
134
|
+
"unavailable-document": (arg: DeleteDocumentPayload) => void
|
|
125
135
|
}
|
|
126
136
|
|
|
127
137
|
interface DocumentPayload {
|
|
@@ -129,5 +139,5 @@ interface DocumentPayload {
|
|
|
129
139
|
}
|
|
130
140
|
|
|
131
141
|
interface DeleteDocumentPayload {
|
|
132
|
-
|
|
142
|
+
documentId: DocumentId
|
|
133
143
|
}
|