@automerge/automerge-repo 2.0.0-alpha.7 → 2.0.0-collectionsync-alpha.1
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/CollectionHandle.d.ts +14 -0
- package/dist/CollectionHandle.d.ts.map +1 -0
- package/dist/CollectionHandle.js +37 -0
- package/dist/DocHandle.d.ts +37 -6
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +64 -6
- package/dist/DocUrl.d.ts +47 -0
- package/dist/DocUrl.d.ts.map +1 -0
- package/dist/DocUrl.js +72 -0
- package/dist/EphemeralData.d.ts +20 -0
- package/dist/EphemeralData.d.ts.map +1 -0
- package/dist/EphemeralData.js +1 -0
- package/dist/Repo.d.ts +28 -7
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +142 -143
- package/dist/ferigan.d.ts +51 -0
- package/dist/ferigan.d.ts.map +1 -0
- package/dist/ferigan.js +98 -0
- package/dist/helpers/tests/storage-adapter-tests.d.ts +2 -2
- package/dist/helpers/tests/storage-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/storage-adapter-tests.js +19 -39
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/network/NetworkSubsystem.d.ts +1 -0
- package/dist/network/NetworkSubsystem.d.ts.map +1 -1
- package/dist/network/NetworkSubsystem.js +3 -0
- package/dist/network/messages.d.ts +7 -1
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/network/messages.js +2 -1
- package/dist/src/DocHandle.d.ts +182 -0
- package/dist/src/DocHandle.d.ts.map +1 -0
- package/dist/src/DocHandle.js +405 -0
- package/dist/src/DocUrl.d.ts +49 -0
- package/dist/src/DocUrl.d.ts.map +1 -0
- package/dist/src/DocUrl.js +72 -0
- package/dist/src/EphemeralData.d.ts +19 -0
- package/dist/src/EphemeralData.d.ts.map +1 -0
- package/dist/src/EphemeralData.js +1 -0
- package/dist/src/Repo.d.ts +74 -0
- package/dist/src/Repo.d.ts.map +1 -0
- package/dist/src/Repo.js +208 -0
- package/dist/src/helpers/arraysAreEqual.d.ts +2 -0
- package/dist/src/helpers/arraysAreEqual.d.ts.map +1 -0
- package/dist/src/helpers/arraysAreEqual.js +2 -0
- package/dist/src/helpers/cbor.d.ts +4 -0
- package/dist/src/helpers/cbor.d.ts.map +1 -0
- package/dist/src/helpers/cbor.js +8 -0
- package/dist/src/helpers/eventPromise.d.ts +11 -0
- package/dist/src/helpers/eventPromise.d.ts.map +1 -0
- package/dist/src/helpers/eventPromise.js +7 -0
- package/dist/src/helpers/headsAreSame.d.ts +2 -0
- package/dist/src/helpers/headsAreSame.d.ts.map +1 -0
- package/dist/src/helpers/headsAreSame.js +4 -0
- package/dist/src/helpers/mergeArrays.d.ts +2 -0
- package/dist/src/helpers/mergeArrays.d.ts.map +1 -0
- package/dist/src/helpers/mergeArrays.js +15 -0
- package/dist/src/helpers/pause.d.ts +6 -0
- package/dist/src/helpers/pause.d.ts.map +1 -0
- package/dist/src/helpers/pause.js +10 -0
- package/dist/src/helpers/tests/network-adapter-tests.d.ts +21 -0
- package/dist/src/helpers/tests/network-adapter-tests.d.ts.map +1 -0
- package/dist/src/helpers/tests/network-adapter-tests.js +122 -0
- package/dist/src/helpers/withTimeout.d.ts +12 -0
- package/dist/src/helpers/withTimeout.d.ts.map +1 -0
- package/dist/src/helpers/withTimeout.js +24 -0
- package/dist/src/index.d.ts +53 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +40 -0
- package/dist/src/network/NetworkAdapter.d.ts +26 -0
- package/dist/src/network/NetworkAdapter.d.ts.map +1 -0
- package/dist/src/network/NetworkAdapter.js +4 -0
- package/dist/src/network/NetworkSubsystem.d.ts +23 -0
- package/dist/src/network/NetworkSubsystem.d.ts.map +1 -0
- package/dist/src/network/NetworkSubsystem.js +120 -0
- package/dist/src/network/messages.d.ts +85 -0
- package/dist/src/network/messages.d.ts.map +1 -0
- package/dist/src/network/messages.js +23 -0
- package/dist/src/storage/StorageAdapter.d.ts +14 -0
- package/dist/src/storage/StorageAdapter.d.ts.map +1 -0
- package/dist/src/storage/StorageAdapter.js +1 -0
- package/dist/src/storage/StorageSubsystem.d.ts +12 -0
- package/dist/src/storage/StorageSubsystem.d.ts.map +1 -0
- package/dist/src/storage/StorageSubsystem.js +145 -0
- package/dist/src/synchronizer/CollectionSynchronizer.d.ts +25 -0
- package/dist/src/synchronizer/CollectionSynchronizer.d.ts.map +1 -0
- package/dist/src/synchronizer/CollectionSynchronizer.js +106 -0
- package/dist/src/synchronizer/DocSynchronizer.d.ts +29 -0
- package/dist/src/synchronizer/DocSynchronizer.d.ts.map +1 -0
- package/dist/src/synchronizer/DocSynchronizer.js +263 -0
- package/dist/src/synchronizer/Synchronizer.d.ts +9 -0
- package/dist/src/synchronizer/Synchronizer.d.ts.map +1 -0
- package/dist/src/synchronizer/Synchronizer.js +2 -0
- package/dist/src/types.d.ts +16 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +1 -0
- package/dist/storage/StorageAdapter.d.ts +9 -0
- package/dist/storage/StorageAdapter.d.ts.map +1 -1
- package/dist/storage/StorageAdapter.js +33 -0
- package/dist/storage/StorageSubsystem.d.ts +12 -2
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +42 -100
- package/dist/synchronizer/CollectionSynchronizer.d.ts +4 -2
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +28 -15
- package/dist/synchronizer/DocSynchronizer.d.ts +6 -5
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +76 -178
- package/dist/synchronizer/Synchronizer.d.ts +11 -0
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/dist/test/CollectionSynchronizer.test.d.ts +2 -0
- package/dist/test/CollectionSynchronizer.test.d.ts.map +1 -0
- package/dist/test/CollectionSynchronizer.test.js +57 -0
- package/dist/test/DocHandle.test.d.ts +2 -0
- package/dist/test/DocHandle.test.d.ts.map +1 -0
- package/dist/test/DocHandle.test.js +238 -0
- package/dist/test/DocSynchronizer.test.d.ts +2 -0
- package/dist/test/DocSynchronizer.test.d.ts.map +1 -0
- package/dist/test/DocSynchronizer.test.js +111 -0
- package/dist/test/Network.test.d.ts +2 -0
- package/dist/test/Network.test.d.ts.map +1 -0
- package/dist/test/Network.test.js +11 -0
- package/dist/test/Repo.test.d.ts +2 -0
- package/dist/test/Repo.test.d.ts.map +1 -0
- package/dist/test/Repo.test.js +568 -0
- package/dist/test/StorageSubsystem.test.d.ts +2 -0
- package/dist/test/StorageSubsystem.test.d.ts.map +1 -0
- package/dist/test/StorageSubsystem.test.js +56 -0
- package/dist/test/helpers/DummyNetworkAdapter.d.ts +9 -0
- package/dist/test/helpers/DummyNetworkAdapter.d.ts.map +1 -0
- package/dist/test/helpers/DummyNetworkAdapter.js +15 -0
- package/dist/test/helpers/DummyStorageAdapter.d.ts +16 -0
- package/dist/test/helpers/DummyStorageAdapter.d.ts.map +1 -0
- package/dist/test/helpers/DummyStorageAdapter.js +33 -0
- package/dist/test/helpers/generate-large-object.d.ts +5 -0
- package/dist/test/helpers/generate-large-object.d.ts.map +1 -0
- package/dist/test/helpers/generate-large-object.js +9 -0
- package/dist/test/helpers/getRandomItem.d.ts +2 -0
- package/dist/test/helpers/getRandomItem.d.ts.map +1 -0
- package/dist/test/helpers/getRandomItem.js +4 -0
- package/dist/test/types.d.ts +4 -0
- package/dist/test/types.d.ts.map +1 -0
- package/dist/test/types.js +1 -0
- package/package.json +3 -3
- package/src/CollectionHandle.ts +54 -0
- package/src/DocHandle.ts +80 -8
- package/src/Repo.ts +192 -183
- package/src/ferigan.ts +184 -0
- package/src/helpers/tests/storage-adapter-tests.ts +31 -62
- package/src/index.ts +2 -0
- package/src/network/NetworkSubsystem.ts +4 -0
- package/src/network/messages.ts +11 -2
- package/src/storage/StorageAdapter.ts +42 -0
- package/src/storage/StorageSubsystem.ts +59 -119
- package/src/synchronizer/CollectionSynchronizer.ts +34 -26
- package/src/synchronizer/DocSynchronizer.ts +84 -231
- package/src/synchronizer/Synchronizer.ts +14 -0
- package/test/CollectionSynchronizer.test.ts +4 -2
- package/test/DocHandle.test.ts +72 -13
- package/test/DocSynchronizer.test.ts +6 -1
- package/test/RemoteHeadsSubscriptions.test.ts +1 -1
- package/test/Repo.test.ts +225 -117
- package/test/StorageSubsystem.test.ts +20 -16
- package/test/remoteHeads.test.ts +1 -1
package/dist/Repo.js
CHANGED
|
@@ -2,13 +2,12 @@ import { next as Automerge } from "@automerge/automerge/slim";
|
|
|
2
2
|
import debug from "debug";
|
|
3
3
|
import { EventEmitter } from "eventemitter3";
|
|
4
4
|
import { generateAutomergeUrl, interpretAsDocumentId, parseAutomergeUrl, } from "./AutomergeUrl.js";
|
|
5
|
-
import { DocHandle } from "./DocHandle.js";
|
|
6
|
-
import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js";
|
|
7
|
-
import { headsAreSame } from "./helpers/headsAreSame.js";
|
|
8
|
-
import { throttle } from "./helpers/throttle.js";
|
|
5
|
+
import { DELETED, DocHandle, READY, UNAVAILABLE, UNLOADED, } from "./DocHandle.js";
|
|
9
6
|
import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
|
|
10
7
|
import { StorageSubsystem } from "./storage/StorageSubsystem.js";
|
|
11
8
|
import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
|
|
9
|
+
import { next as A } from "@automerge/automerge/slim";
|
|
10
|
+
import { InMemoryStorageAdapter } from "./storage/StorageAdapter.js";
|
|
12
11
|
function randomPeerId() {
|
|
13
12
|
return ("peer-" + Math.random().toString(36).slice(4));
|
|
14
13
|
}
|
|
@@ -24,11 +23,7 @@ export class Repo extends EventEmitter {
|
|
|
24
23
|
#log;
|
|
25
24
|
/** @hidden */
|
|
26
25
|
networkSubsystem;
|
|
27
|
-
/** @hidden */
|
|
28
26
|
storageSubsystem;
|
|
29
|
-
/** The debounce rate is adjustable on the repo. */
|
|
30
|
-
/** @hidden */
|
|
31
|
-
saveDebounceRate = 100;
|
|
32
27
|
#handleCache = {};
|
|
33
28
|
/** @hidden */
|
|
34
29
|
synchronizer;
|
|
@@ -38,43 +33,73 @@ export class Repo extends EventEmitter {
|
|
|
38
33
|
/** maps peer id to to persistence information (storageId, isEphemeral), access by collection synchronizer */
|
|
39
34
|
/** @hidden */
|
|
40
35
|
peerMetadataByPeerId = {};
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
constructor({ storage, network = [], peerId = randomPeerId(), sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, } = {}) {
|
|
36
|
+
#beelay;
|
|
37
|
+
constructor({ storage, network = [], peerId = randomPeerId(), sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, denylist = [], } = {}) {
|
|
44
38
|
super();
|
|
45
|
-
|
|
39
|
+
if (storage == null) {
|
|
40
|
+
// beelayStorage = new InMemoryStorageAdapter()
|
|
41
|
+
storage = new InMemoryStorageAdapter();
|
|
42
|
+
}
|
|
43
|
+
this.#beelay = new A.beelay.Beelay({
|
|
44
|
+
storage,
|
|
45
|
+
peerId,
|
|
46
|
+
requestPolicy: async ({ docId }) => {
|
|
47
|
+
const peers = Array.from(this.networkSubsystem.peers);
|
|
48
|
+
const generousPeers = [];
|
|
49
|
+
for (const peerId of peers) {
|
|
50
|
+
const okToShare = await this.sharePolicy(peerId);
|
|
51
|
+
if (okToShare)
|
|
52
|
+
generousPeers.push(peerId);
|
|
53
|
+
}
|
|
54
|
+
return generousPeers;
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
this.storageSubsystem = new StorageSubsystem(this.#beelay, storage);
|
|
46
58
|
this.#log = debug(`automerge-repo:repo`);
|
|
47
59
|
this.sharePolicy = sharePolicy ?? this.sharePolicy;
|
|
48
|
-
this.on("
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
this.#beelay.on("message", ({ message }) => {
|
|
61
|
+
this.#log(`sending ${message} message to ${message.recipient}`);
|
|
62
|
+
networkSubsystem.send({
|
|
63
|
+
targetId: message.recipient,
|
|
64
|
+
type: "beelay",
|
|
65
|
+
...message,
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
this.#beelay.on("docEvent", event => {
|
|
69
|
+
this.#log(`received ${event.data.type} event for ${event.docId}`);
|
|
70
|
+
const handle = this.#handleCache[event.docId];
|
|
71
|
+
if (handle != null) {
|
|
72
|
+
handle.update(d => Automerge.loadIncremental(d, event.data.contents));
|
|
55
73
|
}
|
|
56
74
|
});
|
|
75
|
+
this.#beelay.on("bundleRequired", ({ start, end, checkpoints, docId }) => {
|
|
76
|
+
;
|
|
77
|
+
(async () => {
|
|
78
|
+
const doc = await this.storageSubsystem.loadDoc(docId);
|
|
79
|
+
if (doc == null) {
|
|
80
|
+
console.warn("document not found when creating bundle");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const bundle = A.saveBundle(doc, start, end);
|
|
84
|
+
this.#beelay.addBundle({
|
|
85
|
+
docId,
|
|
86
|
+
checkpoints,
|
|
87
|
+
start,
|
|
88
|
+
end,
|
|
89
|
+
data: bundle,
|
|
90
|
+
});
|
|
91
|
+
})();
|
|
92
|
+
});
|
|
57
93
|
// SYNCHRONIZER
|
|
58
|
-
|
|
59
|
-
this.synchronizer = new CollectionSynchronizer(this);
|
|
60
|
-
// When the synchronizer emits messages, send them to peers
|
|
94
|
+
this.synchronizer = new CollectionSynchronizer(this.#beelay, this, []);
|
|
61
95
|
this.synchronizer.on("message", message => {
|
|
62
96
|
this.#log(`sending ${message.type} message to ${message.targetId}`);
|
|
63
97
|
networkSubsystem.send(message);
|
|
64
98
|
});
|
|
65
|
-
if (this.#remoteHeadsGossipingEnabled) {
|
|
66
|
-
this.synchronizer.on("open-doc", ({ peerId, documentId }) => {
|
|
67
|
-
this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId);
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
// STORAGE
|
|
71
|
-
// The storage subsystem has access to some form of persistence, and deals with save and loading documents.
|
|
72
|
-
const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
|
|
73
|
-
this.storageSubsystem = storageSubsystem;
|
|
74
99
|
// NETWORK
|
|
75
100
|
// The network subsystem deals with sending and receiving messages to and from peers.
|
|
76
101
|
const myPeerMetadata = (async () => ({
|
|
77
|
-
storageId: await storageSubsystem
|
|
102
|
+
// storageId: await this.storageSubsystem.id(),
|
|
78
103
|
isEphemeral,
|
|
79
104
|
}))();
|
|
80
105
|
const networkSubsystem = new NetworkSubsystem(network, peerId, myPeerMetadata);
|
|
@@ -85,92 +110,50 @@ export class Repo extends EventEmitter {
|
|
|
85
110
|
if (peerMetadata) {
|
|
86
111
|
this.peerMetadataByPeerId[peerId] = { ...peerMetadata };
|
|
87
112
|
}
|
|
88
|
-
this.sharePolicy(peerId)
|
|
89
|
-
.then(shouldShare => {
|
|
90
|
-
if (shouldShare && this.#remoteHeadsGossipingEnabled) {
|
|
91
|
-
this.#remoteHeadsSubscriptions.addGenerousPeer(peerId);
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
.catch(err => {
|
|
95
|
-
console.log("error in share policy", { err });
|
|
96
|
-
});
|
|
97
113
|
this.synchronizer.addPeer(peerId);
|
|
98
114
|
});
|
|
99
|
-
// When a peer disconnects, remove it from the synchronizer
|
|
100
|
-
networkSubsystem.on("peer-disconnected", ({ peerId }) => {
|
|
101
|
-
this.synchronizer.removePeer(peerId);
|
|
102
|
-
this.#remoteHeadsSubscriptions.removePeer(peerId);
|
|
103
|
-
});
|
|
104
115
|
// Handle incoming messages
|
|
105
116
|
networkSubsystem.on("message", async (msg) => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
(!heads || !headsAreSame(heads, message.syncState.theirHeads));
|
|
118
|
-
if (haveHeadsChanged && message.syncState.theirHeads) {
|
|
119
|
-
handle.setRemoteHeads(storageId, message.syncState.theirHeads);
|
|
120
|
-
if (storageId && this.#remoteHeadsGossipingEnabled) {
|
|
121
|
-
this.#remoteHeadsSubscriptions.handleImmediateRemoteHeadsChanged(message.documentId, storageId, message.syncState.theirHeads);
|
|
117
|
+
//@ts-ignore
|
|
118
|
+
// const inspected = A.beelay.inspectMessage(msg.message)
|
|
119
|
+
// this.#log(`received msg: ${JSON.stringify(inspected)}`)
|
|
120
|
+
//@ts-ignore
|
|
121
|
+
if (msg.type === "beelay") {
|
|
122
|
+
if (!(msg.message instanceof Uint8Array)) {
|
|
123
|
+
// The Uint8Array instance in the vitest VM is _different_ from the
|
|
124
|
+
// Uint8Array instance which is available in this file for some reason.
|
|
125
|
+
// So, even though `msg.message` _is_ a `Uint8Array`, we have to do this
|
|
126
|
+
// absurd thing to get the tests to pass
|
|
127
|
+
msg.message = Uint8Array.from(msg.message);
|
|
122
128
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
type: "remote-heads-changed",
|
|
129
|
-
targetId: message.targetId,
|
|
130
|
-
documentId: message.documentId,
|
|
131
|
-
newHeads: {
|
|
132
|
-
[message.storageId]: {
|
|
133
|
-
heads: message.heads,
|
|
134
|
-
timestamp: message.timestamp,
|
|
135
|
-
},
|
|
129
|
+
this.#beelay.receiveMessage({
|
|
130
|
+
message: {
|
|
131
|
+
sender: msg.senderId,
|
|
132
|
+
recipient: msg.targetId,
|
|
133
|
+
message: msg.message,
|
|
136
134
|
},
|
|
137
135
|
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
this.#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
type: "remote-subscription-change",
|
|
144
|
-
targetId: peer,
|
|
145
|
-
add: message.add,
|
|
146
|
-
remove: message.remove,
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
this.#remoteHeadsSubscriptions.on("remote-heads-changed", message => {
|
|
151
|
-
const handle = this.#handleCache[message.documentId];
|
|
152
|
-
handle.setRemoteHeads(message.storageId, message.remoteHeads);
|
|
153
|
-
});
|
|
154
|
-
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
this.#receiveMessage(msg);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
155
141
|
}
|
|
156
142
|
// The `document` event is fired by the DocCollection any time we create a new document or look
|
|
157
143
|
// up a document by ID. We listen for it in order to wire up storage and network synchronization.
|
|
158
144
|
#registerHandleWithSubsystems(handle) {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
|
|
166
|
-
}
|
|
145
|
+
handle.on("heads-changed", () => {
|
|
146
|
+
const doc = handle.docSync();
|
|
147
|
+
if (doc != null) {
|
|
148
|
+
this.storageSubsystem.saveDoc(handle.documentId, doc);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
167
151
|
handle.on("unavailable", () => {
|
|
168
152
|
this.#log("document unavailable", { documentId: handle.documentId });
|
|
169
153
|
this.emit("unavailable-document", {
|
|
170
154
|
documentId: handle.documentId,
|
|
171
155
|
});
|
|
172
156
|
});
|
|
173
|
-
// Register the document with the synchronizer. This advertises our interest in the document.
|
|
174
157
|
this.synchronizer.addDocument(handle.documentId);
|
|
175
158
|
// Preserve the old event in case anyone was using it.
|
|
176
159
|
this.emit("document", { handle });
|
|
@@ -178,42 +161,18 @@ export class Repo extends EventEmitter {
|
|
|
178
161
|
#receiveMessage(message) {
|
|
179
162
|
switch (message.type) {
|
|
180
163
|
case "remote-subscription-change":
|
|
181
|
-
if (this.#remoteHeadsGossipingEnabled) {
|
|
182
|
-
this.#remoteHeadsSubscriptions.handleControlMessage(message);
|
|
183
|
-
}
|
|
184
|
-
break;
|
|
185
164
|
case "remote-heads-changed":
|
|
186
|
-
if (this.#remoteHeadsGossipingEnabled) {
|
|
187
|
-
this.#remoteHeadsSubscriptions.handleRemoteHeads(message);
|
|
188
|
-
}
|
|
189
165
|
break;
|
|
190
166
|
case "sync":
|
|
191
167
|
case "request":
|
|
192
168
|
case "ephemeral":
|
|
193
169
|
case "doc-unavailable":
|
|
194
170
|
this.synchronizer.receiveMessage(message).catch(err => {
|
|
195
|
-
console.
|
|
171
|
+
console.error("error receiving message", { err });
|
|
196
172
|
});
|
|
173
|
+
break;
|
|
197
174
|
}
|
|
198
175
|
}
|
|
199
|
-
#throttledSaveSyncStateHandlers = {};
|
|
200
|
-
/** saves sync state throttled per storage id, if a peer doesn't have a storage id it's sync state is not persisted */
|
|
201
|
-
#saveSyncState(payload) {
|
|
202
|
-
if (!this.storageSubsystem) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const { storageId, isEphemeral } = this.peerMetadataByPeerId[payload.peerId] || {};
|
|
206
|
-
if (!storageId || isEphemeral) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
let handler = this.#throttledSaveSyncStateHandlers[storageId];
|
|
210
|
-
if (!handler) {
|
|
211
|
-
handler = this.#throttledSaveSyncStateHandlers[storageId] = throttle(({ documentId, syncState }) => {
|
|
212
|
-
void this.storageSubsystem.saveSyncState(documentId, storageId, syncState);
|
|
213
|
-
}, this.saveDebounceRate);
|
|
214
|
-
}
|
|
215
|
-
handler(payload);
|
|
216
|
-
}
|
|
217
176
|
/** Returns an existing handle if we have it; creates one otherwise. */
|
|
218
177
|
#getHandle({ documentId, }) {
|
|
219
178
|
// If we have the handle cached, return it
|
|
@@ -232,7 +191,7 @@ export class Repo extends EventEmitter {
|
|
|
232
191
|
}
|
|
233
192
|
/** Returns a list of all connected peer ids */
|
|
234
193
|
get peers() {
|
|
235
|
-
return this.
|
|
194
|
+
return this.networkSubsystem.peers;
|
|
236
195
|
}
|
|
237
196
|
getStorageIdOfPeer(peerId) {
|
|
238
197
|
return this.peerMetadataByPeerId[peerId]?.storageId;
|
|
@@ -248,7 +207,7 @@ export class Repo extends EventEmitter {
|
|
|
248
207
|
const handle = this.#getHandle({
|
|
249
208
|
documentId,
|
|
250
209
|
});
|
|
251
|
-
|
|
210
|
+
let initialLinks = [];
|
|
252
211
|
handle.update(() => {
|
|
253
212
|
let nextDoc;
|
|
254
213
|
if (initialValue) {
|
|
@@ -257,8 +216,27 @@ export class Repo extends EventEmitter {
|
|
|
257
216
|
else {
|
|
258
217
|
nextDoc = Automerge.emptyChange(Automerge.init());
|
|
259
218
|
}
|
|
219
|
+
const patches = A.diff(nextDoc, [], A.getHeads(nextDoc));
|
|
220
|
+
for (const patch of patches) {
|
|
221
|
+
initialLinks = patches
|
|
222
|
+
.map(patch => {
|
|
223
|
+
if (patch.action === "put") {
|
|
224
|
+
if (patch.value instanceof A.Link) {
|
|
225
|
+
return patch.value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
})
|
|
230
|
+
.filter(v => v != null);
|
|
231
|
+
}
|
|
260
232
|
return nextDoc;
|
|
261
233
|
});
|
|
234
|
+
for (const link of initialLinks) {
|
|
235
|
+
const { documentId: target } = parseAutomergeUrl(link.target);
|
|
236
|
+
this.#beelay.addLink({ from: documentId, to: target });
|
|
237
|
+
}
|
|
238
|
+
this.storageSubsystem.saveDoc(handle.documentId, handle.docSync());
|
|
239
|
+
this.#registerHandleWithSubsystems(handle);
|
|
262
240
|
handle.doneLoading();
|
|
263
241
|
return handle;
|
|
264
242
|
}
|
|
@@ -280,7 +258,7 @@ export class Repo extends EventEmitter {
|
|
|
280
258
|
clone(clonedHandle) {
|
|
281
259
|
if (!clonedHandle.isReady()) {
|
|
282
260
|
throw new Error(`Cloned handle is not yet in ready state.
|
|
283
|
-
(Try await handle.
|
|
261
|
+
(Try await handle.whenReady() first.)`);
|
|
284
262
|
}
|
|
285
263
|
const sourceDoc = clonedHandle.docSync();
|
|
286
264
|
if (!sourceDoc) {
|
|
@@ -300,6 +278,7 @@ export class Repo extends EventEmitter {
|
|
|
300
278
|
find(
|
|
301
279
|
/** The url or documentId of the handle to retrieve */
|
|
302
280
|
id) {
|
|
281
|
+
this.#log("find", { id });
|
|
303
282
|
const documentId = interpretAsDocumentId(id);
|
|
304
283
|
// If we have the handle cached, return it
|
|
305
284
|
if (this.#handleCache[documentId]) {
|
|
@@ -319,9 +298,7 @@ export class Repo extends EventEmitter {
|
|
|
319
298
|
});
|
|
320
299
|
// Loading & network is going to be asynchronous no matter what,
|
|
321
300
|
// but we want to return the handle immediately.
|
|
322
|
-
const attemptLoad = this.storageSubsystem
|
|
323
|
-
? this.storageSubsystem.loadDoc(handle.documentId)
|
|
324
|
-
: Promise.resolve(null);
|
|
301
|
+
const attemptLoad = this.storageSubsystem.loadDoc(handle.documentId);
|
|
325
302
|
attemptLoad
|
|
326
303
|
.then(async (loadedDoc) => {
|
|
327
304
|
if (loadedDoc) {
|
|
@@ -333,6 +310,7 @@ export class Repo extends EventEmitter {
|
|
|
333
310
|
// we want to wait for the network subsystem to be ready before
|
|
334
311
|
// we request the document. this prevents entering unavailable during initialization.
|
|
335
312
|
await this.networkSubsystem.whenReady();
|
|
313
|
+
console.log("we didn't find it so we're requesting");
|
|
336
314
|
handle.request();
|
|
337
315
|
}
|
|
338
316
|
this.#registerHandleWithSubsystems(handle);
|
|
@@ -378,15 +356,7 @@ export class Repo extends EventEmitter {
|
|
|
378
356
|
});
|
|
379
357
|
return handle;
|
|
380
358
|
}
|
|
381
|
-
subscribeToRemotes = (remotes) => {
|
|
382
|
-
if (this.#remoteHeadsGossipingEnabled) {
|
|
383
|
-
this.#log("subscribeToRemotes", { remotes });
|
|
384
|
-
this.#remoteHeadsSubscriptions.subscribeToRemotes(remotes);
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
this.#log("WARN: subscribeToRemotes called but remote heads gossiping is not enabled");
|
|
388
|
-
}
|
|
389
|
-
};
|
|
359
|
+
subscribeToRemotes = (remotes) => { };
|
|
390
360
|
storageId = async () => {
|
|
391
361
|
if (!this.storageSubsystem) {
|
|
392
362
|
return undefined;
|
|
@@ -416,6 +386,34 @@ export class Repo extends EventEmitter {
|
|
|
416
386
|
return this.storageSubsystem.saveDoc(handle.documentId, doc);
|
|
417
387
|
}));
|
|
418
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* Removes a DocHandle from the handleCache.
|
|
391
|
+
* @hidden this API is experimental and may change.
|
|
392
|
+
* @param documentId - documentId of the DocHandle to remove from handleCache, if present in cache.
|
|
393
|
+
* @returns Promise<void>
|
|
394
|
+
*/
|
|
395
|
+
async removeFromCache(documentId) {
|
|
396
|
+
if (!this.#handleCache[documentId]) {
|
|
397
|
+
this.#log(`WARN: removeFromCache called but handle not found in handleCache for documentId: ${documentId}`);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const handle = this.#getHandle({ documentId });
|
|
401
|
+
const doc = await handle.doc([READY, UNLOADED, DELETED, UNAVAILABLE]);
|
|
402
|
+
if (doc) {
|
|
403
|
+
if (handle.isReady()) {
|
|
404
|
+
handle.unload();
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
this.#log(`WARN: removeFromCache called but handle for documentId: ${documentId} in unexpected state: ${handle.state}`);
|
|
408
|
+
}
|
|
409
|
+
delete this.#handleCache[documentId];
|
|
410
|
+
// TODO: remove document from synchronizer when removeDocument is implemented
|
|
411
|
+
// this.synchronizer.removeDocument(documentId)
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
this.#log(`WARN: removeFromCache called but doc undefined for documentId: ${documentId}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
419
417
|
shutdown() {
|
|
420
418
|
this.networkSubsystem.adapters.forEach(adapter => {
|
|
421
419
|
adapter.disconnect();
|
|
@@ -423,6 +421,7 @@ export class Repo extends EventEmitter {
|
|
|
423
421
|
return this.flush();
|
|
424
422
|
}
|
|
425
423
|
metrics() {
|
|
426
|
-
return { documents: this.synchronizer.metrics() }
|
|
424
|
+
//return { documents: this.synchronizer.metrics() }
|
|
425
|
+
return { documents: {} };
|
|
427
426
|
}
|
|
428
427
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { EventEmitter } from "eventemitter3";
|
|
2
|
+
import { MessageContents, RepoMessage } from "./network/messages.js";
|
|
3
|
+
import { AutomergeUrl } from "./types.js";
|
|
4
|
+
import { Repo } from "./Repo.js";
|
|
5
|
+
export interface Ferigan extends EventEmitter<FeriganEvents> {
|
|
6
|
+
receiveMessage(message: RepoMessage): Promise<void>;
|
|
7
|
+
load(doc: ChangeLogId, since: ChangeHash[]): AsyncIterableIterator<Progress<ChangeLog | undefined>>;
|
|
8
|
+
loadCollection(doc: ChangeLogId): AsyncIterableIterator<Progress<Index | undefined>>;
|
|
9
|
+
append(doc: ChangeLogId, parents: ChangeHash[], changes: Uint8Array): Promise<void>;
|
|
10
|
+
replace(doc: ChangeLogId, start: ChangeHash, end: ChangeHash, changes: Uint8Array): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function makeFerigan(repo: Repo): Ferigan;
|
|
13
|
+
interface FeriganEvents {
|
|
14
|
+
message: (event: {
|
|
15
|
+
message: MessageContents;
|
|
16
|
+
}) => void;
|
|
17
|
+
changed: (event: {
|
|
18
|
+
changedLog: ChangeLogId;
|
|
19
|
+
}) => void;
|
|
20
|
+
indexChanged: (event: {
|
|
21
|
+
indexUrl: ChangeLogId;
|
|
22
|
+
change: IndexChange;
|
|
23
|
+
}) => void;
|
|
24
|
+
}
|
|
25
|
+
type IndexChange = {
|
|
26
|
+
type: "add";
|
|
27
|
+
url: AutomergeUrl;
|
|
28
|
+
};
|
|
29
|
+
type ChangeLogId = string;
|
|
30
|
+
type ChangeHash = string;
|
|
31
|
+
type ChangeLog = {
|
|
32
|
+
start: ChangeHash;
|
|
33
|
+
end: ChangeHash;
|
|
34
|
+
changes: Uint8Array;
|
|
35
|
+
};
|
|
36
|
+
export type Progress<T> = {
|
|
37
|
+
type: "synchronizing_index";
|
|
38
|
+
} | {
|
|
39
|
+
type: "synchronizing_docs";
|
|
40
|
+
progress: number;
|
|
41
|
+
total: number;
|
|
42
|
+
} | {
|
|
43
|
+
type: "done";
|
|
44
|
+
value: T;
|
|
45
|
+
};
|
|
46
|
+
export type Index = {
|
|
47
|
+
rootUrl: AutomergeUrl;
|
|
48
|
+
entries: AutomergeUrl[];
|
|
49
|
+
};
|
|
50
|
+
export {};
|
|
51
|
+
//# sourceMappingURL=ferigan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ferigan.d.ts","sourceRoot":"","sources":["../src/ferigan.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACpE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAIhC,MAAM,WAAW,OAAQ,SAAQ,YAAY,CAAC,aAAa,CAAC;IAC1D,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnD,IAAI,CACF,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,UAAU,EAAE,GAClB,qBAAqB,CAAC,QAAQ,CAAC,SAAS,GAAG,SAAS,CAAC,CAAC,CAAA;IACzD,cAAc,CACZ,GAAG,EAAE,WAAW,GACf,qBAAqB,CAAC,QAAQ,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,CAAA;IACrD,MAAM,CACJ,GAAG,EAAE,WAAW,EAChB,OAAO,EAAE,UAAU,EAAE,EACrB,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,OAAO,CACL,GAAG,EAAE,WAAW,EAChB,KAAK,EAAE,UAAU,EACjB,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CA+G/C;AAED,UAAU,aAAa;IACrB,OAAO,EAAE,CAAC,KAAK,EAAE;QAAC,OAAO,EAAE,eAAe,CAAA;KAAC,KAAK,IAAI,CAAA;IACpD,OAAO,EAAE,CAAC,KAAK,EAAE;QAAC,UAAU,EAAE,WAAW,CAAA;KAAC,KAAK,IAAI,CAAA;IACnD,YAAY,EAAE,CAAC,KAAK,EAAE;QAAC,QAAQ,EAAE,WAAW,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAC,KAAK,IAAI,CAAA;CAC5E;AAED,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,KAAK,CAAA;IACX,GAAG,EAAE,YAAY,CAAA;CAClB,CAAA;AAED,KAAK,WAAW,GAAG,MAAM,CAAA;AAEzB,KAAK,UAAU,GAAG,MAAM,CAAA;AAExB,KAAK,SAAS,GAAG;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,UAAU,CAAC;IAAC,OAAO,EAAE,UAAU,CAAA;CAAE,CAAA;AAE5E,MAAM,MAAM,QAAQ,CAAC,CAAC,IAClB;IAAE,IAAI,EAAE,qBAAqB,CAAA;CAAE,GAC/B;IACA,IAAI,EAAE,oBAAoB,CAAA;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;CACd,GACC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAA;AAE9B,MAAM,MAAM,KAAK,GAAG;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB,CAAA"}
|
package/dist/ferigan.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { EventEmitter } from "eventemitter3";
|
|
2
|
+
const URL_RE = /automerge:([\w\d+/=]+)(?:\?([\w\d+/=&]*))*/;
|
|
3
|
+
export function makeFerigan(repo) {
|
|
4
|
+
function fakeProgress() {
|
|
5
|
+
return (async function* () {
|
|
6
|
+
yield { type: "synchronizing_index" };
|
|
7
|
+
})();
|
|
8
|
+
}
|
|
9
|
+
class FakeFerigan extends EventEmitter {
|
|
10
|
+
#repo;
|
|
11
|
+
constructor(repo) {
|
|
12
|
+
super();
|
|
13
|
+
this.#repo = repo;
|
|
14
|
+
}
|
|
15
|
+
async receiveMessage(message) { }
|
|
16
|
+
load(doc) {
|
|
17
|
+
return fakeProgress();
|
|
18
|
+
}
|
|
19
|
+
async *loadCollection(doc) {
|
|
20
|
+
yield { type: "synchronizing_index" };
|
|
21
|
+
const index = { rootUrl: doc, entries: [] };
|
|
22
|
+
function findLinks(obj) {
|
|
23
|
+
const links = [];
|
|
24
|
+
const traverse = (value) => {
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
const url = parseUrl(value);
|
|
27
|
+
if (url != null) {
|
|
28
|
+
links.push(url);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
else if (Array.isArray(value)) {
|
|
32
|
+
value.forEach(traverse);
|
|
33
|
+
}
|
|
34
|
+
else if (typeof value === "object" && value !== null) {
|
|
35
|
+
Object.values(value).forEach(traverse);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
traverse(obj);
|
|
39
|
+
return links;
|
|
40
|
+
}
|
|
41
|
+
const indexDoc = this.#repo.find(doc);
|
|
42
|
+
const handlesToProcess = [indexDoc];
|
|
43
|
+
while (handlesToProcess.length > 0) {
|
|
44
|
+
const handle = handlesToProcess.pop();
|
|
45
|
+
if (!handle) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const doc = await handle.doc();
|
|
49
|
+
if (!doc) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
handle.on("change", change => {
|
|
53
|
+
for (const patch of change.patches) {
|
|
54
|
+
if (patch.action === "splice") {
|
|
55
|
+
const possibleUrl = parseUrl(patch.value);
|
|
56
|
+
if (possibleUrl != null) {
|
|
57
|
+
this.emit("indexChanged", {
|
|
58
|
+
indexUrl: indexDoc.url,
|
|
59
|
+
change: {
|
|
60
|
+
type: "add",
|
|
61
|
+
url: possibleUrl.url,
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
const links = findLinks(doc);
|
|
69
|
+
for (const { url: urlStr } of links) {
|
|
70
|
+
const url = urlStr;
|
|
71
|
+
if (index.entries.some(entry => entry === url)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
index.entries.push(url);
|
|
75
|
+
const childHandle = this.#repo.find(url);
|
|
76
|
+
handlesToProcess.push(childHandle);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
yield { type: "done", value: index };
|
|
80
|
+
}
|
|
81
|
+
append(doc, parents, changes) {
|
|
82
|
+
return Promise.resolve();
|
|
83
|
+
}
|
|
84
|
+
replace(doc, start, end, changes) {
|
|
85
|
+
return Promise.resolve();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return new FakeFerigan(repo);
|
|
89
|
+
}
|
|
90
|
+
function parseUrl(urlStr) {
|
|
91
|
+
const match = urlStr.match(URL_RE);
|
|
92
|
+
if (!match) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const url = new URL(match[0]);
|
|
96
|
+
const normalisedUrl = `automerge:${url.pathname}`;
|
|
97
|
+
return { url: normalisedUrl };
|
|
98
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { StorageAdapterInterface } from "../../storage/StorageAdapterInterface.js";
|
|
2
|
-
export declare function runStorageAdapterTests(
|
|
2
|
+
export declare function runStorageAdapterTests(setup: SetupFn, title?: string): void;
|
|
3
3
|
export type SetupFn = () => Promise<{
|
|
4
4
|
adapter: StorageAdapterInterface;
|
|
5
|
-
teardown?: () => void
|
|
5
|
+
teardown?: () => void | Promise<void>;
|
|
6
6
|
}>;
|
|
7
7
|
//# sourceMappingURL=storage-adapter-tests.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/storage-adapter-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;
|
|
1
|
+
{"version":3,"file":"storage-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/storage-adapter-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAcvF,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA0I3E;AAID,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,OAAO,EAAE,uBAAuB,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACtC,CAAC,CAAA"}
|