@automerge/automerge-repo 2.0.0-alpha.1 → 2.0.0-alpha.12
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 +71 -2
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +116 -2
- package/dist/Repo.d.ts +24 -0
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +94 -57
- package/dist/entrypoints/fullfat.d.ts +1 -0
- package/dist/entrypoints/fullfat.d.ts.map +1 -1
- package/dist/entrypoints/fullfat.js +1 -2
- package/dist/entrypoints/slim.d.ts +1 -0
- package/dist/entrypoints/slim.d.ts.map +1 -1
- package/dist/entrypoints/slim.js +2 -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/storage/StorageSubsystem.d.ts +11 -1
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +18 -3
- package/dist/synchronizer/CollectionSynchronizer.d.ts +13 -0
- package/dist/synchronizer/CollectionSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/CollectionSynchronizer.js +13 -6
- package/dist/synchronizer/DocSynchronizer.d.ts +7 -0
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +14 -0
- package/dist/synchronizer/Synchronizer.d.ts +8 -0
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/DocHandle.ts +137 -4
- package/src/Repo.ts +123 -56
- package/src/entrypoints/fullfat.ts +1 -2
- package/src/entrypoints/slim.ts +2 -0
- package/src/helpers/tests/storage-adapter-tests.ts +31 -62
- package/src/storage/StorageSubsystem.ts +26 -3
- package/src/synchronizer/CollectionSynchronizer.ts +23 -6
- package/src/synchronizer/DocSynchronizer.ts +15 -0
- package/src/synchronizer/Synchronizer.ts +9 -0
- package/test/DocHandle.test.ts +141 -0
- package/test/Repo.test.ts +73 -0
- package/test/StorageSubsystem.test.ts +17 -0
package/dist/DocHandle.d.ts
CHANGED
|
@@ -30,6 +30,13 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
30
30
|
* peers. We do not currently have an equivalent `whenSynced()`.
|
|
31
31
|
*/
|
|
32
32
|
isReady: () => boolean;
|
|
33
|
+
/**
|
|
34
|
+
* @returns true if the document has been unloaded.
|
|
35
|
+
*
|
|
36
|
+
* Unloaded documents are freed from memory but not removed from local storage. It's not currently
|
|
37
|
+
* possible at runtime to reload an unloaded document.
|
|
38
|
+
*/
|
|
39
|
+
isUnloaded: () => boolean;
|
|
33
40
|
/**
|
|
34
41
|
* @returns true if the document has been marked as deleted.
|
|
35
42
|
*
|
|
@@ -48,7 +55,7 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
48
55
|
*/
|
|
49
56
|
inState: (states: HandleState[]) => boolean;
|
|
50
57
|
/** @hidden */
|
|
51
|
-
get state(): "idle" | "loading" | "requesting" | "ready" | "unavailable" | "deleted";
|
|
58
|
+
get state(): "idle" | "loading" | "requesting" | "ready" | "unavailable" | "unloaded" | "deleted";
|
|
52
59
|
/**
|
|
53
60
|
* @returns a promise that resolves when the document is in one of the given states (if no states
|
|
54
61
|
* are passed, when the document is ready)
|
|
@@ -86,6 +93,58 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
86
93
|
* @returns the current document's heads, or undefined if the document is not ready
|
|
87
94
|
*/
|
|
88
95
|
heads(): A.Heads | undefined;
|
|
96
|
+
begin(): void;
|
|
97
|
+
/**
|
|
98
|
+
* Creates a fixed "view" of an automerge document at the given point in time represented
|
|
99
|
+
* by the `heads` passed in. The return value is the same type as docSync() and will return
|
|
100
|
+
* undefined if the object hasn't finished loading.
|
|
101
|
+
*
|
|
102
|
+
* @remarks
|
|
103
|
+
* A point-in-time in an automerge document is an *array* of heads since there may be
|
|
104
|
+
* concurrent edits. This API just returns a topologically sorted history of all edits
|
|
105
|
+
* so every previous entry will be (in some sense) before later ones, but the set of all possible
|
|
106
|
+
* history views would be quite large under concurrency (every thing in each branch against each other).
|
|
107
|
+
* There might be a clever way to think about this, but we haven't found it yet, so for now at least
|
|
108
|
+
* we present a single traversable view which excludes concurrency.
|
|
109
|
+
* @returns The individual heads for every change in the document.
|
|
110
|
+
*/
|
|
111
|
+
history(): A.Heads[] | undefined;
|
|
112
|
+
/**
|
|
113
|
+
* Creates a fixed "view" of an automerge document at the given point in time represented
|
|
114
|
+
* by the `heads` passed in. The return value is the same type as docSync() and will return
|
|
115
|
+
* undefined if the object hasn't finished loading.
|
|
116
|
+
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* Note that our Typescript types do not consider change over time and the current version
|
|
119
|
+
* of Automerge doesn't check types at runtime, so if you go back to an old set of heads
|
|
120
|
+
* that doesn't match the heads here, Typescript will not save you.
|
|
121
|
+
*
|
|
122
|
+
* @returns An Automerge.Doc<T> at the point in time.
|
|
123
|
+
*/
|
|
124
|
+
view(heads: A.Heads): A.Doc<T> | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Returns a set of Patch operations that will move a materialized document from one state to another
|
|
127
|
+
* if applied.
|
|
128
|
+
*
|
|
129
|
+
* @remarks
|
|
130
|
+
* We allow specifying both a from/to heads or just a single comparison point, in which case
|
|
131
|
+
* the base will be the current document heads.
|
|
132
|
+
*
|
|
133
|
+
* @returns Automerge patches that go from one document state to the other. Use view() to get the full state.
|
|
134
|
+
*/
|
|
135
|
+
diff(first: A.Heads, second?: A.Heads): A.Patch[] | undefined;
|
|
136
|
+
/**
|
|
137
|
+
* `metadata(head?)` allows you to look at the metadata for a change
|
|
138
|
+
* this can be used to build history graphs to find commit messages and edit times.
|
|
139
|
+
* this interface.
|
|
140
|
+
*
|
|
141
|
+
* @remarks
|
|
142
|
+
* I'm really not convinced this is the right way to surface this information so
|
|
143
|
+
* I'm leaving this API "hidden".
|
|
144
|
+
*
|
|
145
|
+
* @hidden
|
|
146
|
+
*/
|
|
147
|
+
metadata(change?: string): A.DecodedChange | undefined;
|
|
89
148
|
/**
|
|
90
149
|
* `update` is called any time we have a new document state; could be
|
|
91
150
|
* from a local change, a remote change, or a new document from storage.
|
|
@@ -148,6 +207,10 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
148
207
|
* @hidden
|
|
149
208
|
* */
|
|
150
209
|
request(): void;
|
|
210
|
+
/** Called by the repo to free memory used by the document. */
|
|
211
|
+
unload(): void;
|
|
212
|
+
/** Called by the repo to reuse an unloaded handle. */
|
|
213
|
+
reload(): void;
|
|
151
214
|
/** Called by the repo when the document is deleted. */
|
|
152
215
|
delete(): void;
|
|
153
216
|
/**
|
|
@@ -158,6 +221,10 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
158
221
|
* must have a unique PeerId.
|
|
159
222
|
*/
|
|
160
223
|
broadcast(message: unknown): void;
|
|
224
|
+
metrics(): {
|
|
225
|
+
numOps: number;
|
|
226
|
+
numChanges: number;
|
|
227
|
+
};
|
|
161
228
|
}
|
|
162
229
|
/** @hidden */
|
|
163
230
|
export type DocHandleOptions<T> = {
|
|
@@ -232,11 +299,13 @@ export declare const HandleState: {
|
|
|
232
299
|
readonly REQUESTING: "requesting";
|
|
233
300
|
/** The document is available */
|
|
234
301
|
readonly READY: "ready";
|
|
302
|
+
/** The document has been unloaded from the handle, to free memory usage */
|
|
303
|
+
readonly UNLOADED: "unloaded";
|
|
235
304
|
/** The document has been deleted from the repo */
|
|
236
305
|
readonly DELETED: "deleted";
|
|
237
306
|
/** The document was not available in storage or from any connected peers */
|
|
238
307
|
readonly UNAVAILABLE: "unavailable";
|
|
239
308
|
};
|
|
240
309
|
export type HandleState = (typeof HandleState)[keyof typeof HandleState];
|
|
241
|
-
export declare const IDLE: "idle", LOADING: "loading", REQUESTING: "requesting", READY: "ready", DELETED: "deleted", UNAVAILABLE: "unavailable";
|
|
310
|
+
export declare const IDLE: "idle", LOADING: "loading", REQUESTING: "requesting", READY: "ready", UNLOADED: "unloaded", DELETED: "deleted", UNAVAILABLE: "unavailable";
|
|
242
311
|
//# sourceMappingURL=DocHandle.d.ts.map
|
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,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAEnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAM5C,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAS,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;;IAkBvD,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
|
@@ -59,6 +59,9 @@ export class DocHandle extends EventEmitter {
|
|
|
59
59
|
this.emit("delete", { handle: this });
|
|
60
60
|
return { doc: A.init() };
|
|
61
61
|
}),
|
|
62
|
+
onUnload: assign(() => {
|
|
63
|
+
return { doc: A.init() };
|
|
64
|
+
}),
|
|
62
65
|
onUnavailable: () => {
|
|
63
66
|
this.emit("unavailable", { handle: this });
|
|
64
67
|
},
|
|
@@ -71,6 +74,7 @@ export class DocHandle extends EventEmitter {
|
|
|
71
74
|
context: { documentId, doc },
|
|
72
75
|
on: {
|
|
73
76
|
UPDATE: { actions: "onUpdate" },
|
|
77
|
+
UNLOAD: ".unloaded",
|
|
74
78
|
DELETE: ".deleted",
|
|
75
79
|
},
|
|
76
80
|
states: {
|
|
@@ -98,6 +102,12 @@ export class DocHandle extends EventEmitter {
|
|
|
98
102
|
on: { DOC_READY: "ready" },
|
|
99
103
|
},
|
|
100
104
|
ready: {},
|
|
105
|
+
unloaded: {
|
|
106
|
+
entry: "onUnload",
|
|
107
|
+
on: {
|
|
108
|
+
RELOAD: "loading",
|
|
109
|
+
},
|
|
110
|
+
},
|
|
101
111
|
deleted: { entry: "onDelete", type: "final" },
|
|
102
112
|
},
|
|
103
113
|
});
|
|
@@ -113,7 +123,7 @@ export class DocHandle extends EventEmitter {
|
|
|
113
123
|
});
|
|
114
124
|
// Start the machine, and send a create or find event to get things going
|
|
115
125
|
this.#machine.start();
|
|
116
|
-
this
|
|
126
|
+
this.begin();
|
|
117
127
|
}
|
|
118
128
|
// PRIVATE
|
|
119
129
|
/** Returns the current document, regardless of state */
|
|
@@ -172,6 +182,13 @@ export class DocHandle extends EventEmitter {
|
|
|
172
182
|
* peers. We do not currently have an equivalent `whenSynced()`.
|
|
173
183
|
*/
|
|
174
184
|
isReady = () => this.inState(["ready"]);
|
|
185
|
+
/**
|
|
186
|
+
* @returns true if the document has been unloaded.
|
|
187
|
+
*
|
|
188
|
+
* Unloaded documents are freed from memory but not removed from local storage. It's not currently
|
|
189
|
+
* possible at runtime to reload an unloaded document.
|
|
190
|
+
*/
|
|
191
|
+
isUnloaded = () => this.inState(["unloaded"]);
|
|
175
192
|
/**
|
|
176
193
|
* @returns true if the document has been marked as deleted.
|
|
177
194
|
*
|
|
@@ -253,6 +270,88 @@ export class DocHandle extends EventEmitter {
|
|
|
253
270
|
}
|
|
254
271
|
return A.getHeads(this.#doc);
|
|
255
272
|
}
|
|
273
|
+
begin() {
|
|
274
|
+
this.#machine.send({ type: BEGIN });
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Creates a fixed "view" of an automerge document at the given point in time represented
|
|
278
|
+
* by the `heads` passed in. The return value is the same type as docSync() and will return
|
|
279
|
+
* undefined if the object hasn't finished loading.
|
|
280
|
+
*
|
|
281
|
+
* @remarks
|
|
282
|
+
* A point-in-time in an automerge document is an *array* of heads since there may be
|
|
283
|
+
* concurrent edits. This API just returns a topologically sorted history of all edits
|
|
284
|
+
* so every previous entry will be (in some sense) before later ones, but the set of all possible
|
|
285
|
+
* history views would be quite large under concurrency (every thing in each branch against each other).
|
|
286
|
+
* There might be a clever way to think about this, but we haven't found it yet, so for now at least
|
|
287
|
+
* we present a single traversable view which excludes concurrency.
|
|
288
|
+
* @returns The individual heads for every change in the document.
|
|
289
|
+
*/
|
|
290
|
+
history() {
|
|
291
|
+
if (!this.isReady()) {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
// This just returns all the heads as individual strings.
|
|
295
|
+
return A.topoHistoryTraversal(this.#doc).map(h => [h]);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Creates a fixed "view" of an automerge document at the given point in time represented
|
|
299
|
+
* by the `heads` passed in. The return value is the same type as docSync() and will return
|
|
300
|
+
* undefined if the object hasn't finished loading.
|
|
301
|
+
*
|
|
302
|
+
* @remarks
|
|
303
|
+
* Note that our Typescript types do not consider change over time and the current version
|
|
304
|
+
* of Automerge doesn't check types at runtime, so if you go back to an old set of heads
|
|
305
|
+
* that doesn't match the heads here, Typescript will not save you.
|
|
306
|
+
*
|
|
307
|
+
* @returns An Automerge.Doc<T> at the point in time.
|
|
308
|
+
*/
|
|
309
|
+
view(heads) {
|
|
310
|
+
if (!this.isReady()) {
|
|
311
|
+
return undefined;
|
|
312
|
+
}
|
|
313
|
+
return A.view(this.#doc, heads);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Returns a set of Patch operations that will move a materialized document from one state to another
|
|
317
|
+
* if applied.
|
|
318
|
+
*
|
|
319
|
+
* @remarks
|
|
320
|
+
* We allow specifying both a from/to heads or just a single comparison point, in which case
|
|
321
|
+
* the base will be the current document heads.
|
|
322
|
+
*
|
|
323
|
+
* @returns Automerge patches that go from one document state to the other. Use view() to get the full state.
|
|
324
|
+
*/
|
|
325
|
+
diff(first, second) {
|
|
326
|
+
if (!this.isReady()) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
// We allow only one set of heads to be specified, in which case we use the doc's heads
|
|
330
|
+
const from = second ? first : this.heads() || []; // because we guard above this should always have useful data
|
|
331
|
+
const to = second ? second : first;
|
|
332
|
+
return A.diff(this.#doc, from, to);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* `metadata(head?)` allows you to look at the metadata for a change
|
|
336
|
+
* this can be used to build history graphs to find commit messages and edit times.
|
|
337
|
+
* this interface.
|
|
338
|
+
*
|
|
339
|
+
* @remarks
|
|
340
|
+
* I'm really not convinced this is the right way to surface this information so
|
|
341
|
+
* I'm leaving this API "hidden".
|
|
342
|
+
*
|
|
343
|
+
* @hidden
|
|
344
|
+
*/
|
|
345
|
+
metadata(change) {
|
|
346
|
+
if (!this.isReady()) {
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
if (!change) {
|
|
350
|
+
change = this.heads()[0];
|
|
351
|
+
}
|
|
352
|
+
// we return undefined instead of null by convention in this API
|
|
353
|
+
return A.inspectChange(this.#doc, change) || undefined;
|
|
354
|
+
}
|
|
256
355
|
/**
|
|
257
356
|
* `update` is called any time we have a new document state; could be
|
|
258
357
|
* from a local change, a remote change, or a new document from storage.
|
|
@@ -365,6 +464,14 @@ export class DocHandle extends EventEmitter {
|
|
|
365
464
|
if (this.#state === "loading")
|
|
366
465
|
this.#machine.send({ type: REQUEST });
|
|
367
466
|
}
|
|
467
|
+
/** Called by the repo to free memory used by the document. */
|
|
468
|
+
unload() {
|
|
469
|
+
this.#machine.send({ type: UNLOAD });
|
|
470
|
+
}
|
|
471
|
+
/** Called by the repo to reuse an unloaded handle. */
|
|
472
|
+
reload() {
|
|
473
|
+
this.#machine.send({ type: RELOAD });
|
|
474
|
+
}
|
|
368
475
|
/** Called by the repo when the document is deleted. */
|
|
369
476
|
delete() {
|
|
370
477
|
this.#machine.send({ type: DELETE });
|
|
@@ -382,6 +489,9 @@ export class DocHandle extends EventEmitter {
|
|
|
382
489
|
data: encode(message),
|
|
383
490
|
});
|
|
384
491
|
}
|
|
492
|
+
metrics() {
|
|
493
|
+
return A.stats(this.#doc);
|
|
494
|
+
}
|
|
385
495
|
}
|
|
386
496
|
// STATE MACHINE TYPES & CONSTANTS
|
|
387
497
|
// state
|
|
@@ -397,16 +507,20 @@ export const HandleState = {
|
|
|
397
507
|
REQUESTING: "requesting",
|
|
398
508
|
/** The document is available */
|
|
399
509
|
READY: "ready",
|
|
510
|
+
/** The document has been unloaded from the handle, to free memory usage */
|
|
511
|
+
UNLOADED: "unloaded",
|
|
400
512
|
/** The document has been deleted from the repo */
|
|
401
513
|
DELETED: "deleted",
|
|
402
514
|
/** The document was not available in storage or from any connected peers */
|
|
403
515
|
UNAVAILABLE: "unavailable",
|
|
404
516
|
};
|
|
405
|
-
export const { IDLE, LOADING, REQUESTING, READY, DELETED, UNAVAILABLE } = HandleState;
|
|
517
|
+
export const { IDLE, LOADING, REQUESTING, READY, UNLOADED, DELETED, UNAVAILABLE, } = HandleState;
|
|
406
518
|
const BEGIN = "BEGIN";
|
|
407
519
|
const REQUEST = "REQUEST";
|
|
408
520
|
const DOC_READY = "DOC_READY";
|
|
409
521
|
const UPDATE = "UPDATE";
|
|
522
|
+
const UNLOAD = "UNLOAD";
|
|
523
|
+
const RELOAD = "RELOAD";
|
|
410
524
|
const DELETE = "DELETE";
|
|
411
525
|
const TIMEOUT = "TIMEOUT";
|
|
412
526
|
const DOC_UNAVAILABLE = "DOC_UNAVAILABLE";
|
package/dist/Repo.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
|
|
|
5
5
|
import { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js";
|
|
6
6
|
import { StorageSubsystem } from "./storage/StorageSubsystem.js";
|
|
7
7
|
import { StorageId } from "./storage/types.js";
|
|
8
|
+
import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
|
|
9
|
+
import { DocSyncMetrics } from "./synchronizer/Synchronizer.js";
|
|
8
10
|
import type { AnyDocumentId, DocumentId, PeerId } from "./types.js";
|
|
9
11
|
/** A Repo is a collection of documents with networking, syncing, and storage capabilities. */
|
|
10
12
|
/** The `Repo` is the main entry point of this library
|
|
@@ -23,6 +25,8 @@ export declare class Repo extends EventEmitter<RepoEvents> {
|
|
|
23
25
|
/** The debounce rate is adjustable on the repo. */
|
|
24
26
|
/** @hidden */
|
|
25
27
|
saveDebounceRate: number;
|
|
28
|
+
/** @hidden */
|
|
29
|
+
synchronizer: CollectionSynchronizer;
|
|
26
30
|
/** By default, we share generously with all peers. */
|
|
27
31
|
/** @hidden */
|
|
28
32
|
sharePolicy: SharePolicy;
|
|
@@ -89,7 +93,19 @@ export declare class Repo extends EventEmitter<RepoEvents> {
|
|
|
89
93
|
* @returns Promise<void>
|
|
90
94
|
*/
|
|
91
95
|
flush(documents?: DocumentId[]): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Removes a DocHandle from the handleCache.
|
|
98
|
+
* @hidden this API is experimental and may change.
|
|
99
|
+
* @param documentId - documentId of the DocHandle to remove from handleCache, if present in cache.
|
|
100
|
+
* @returns Promise<void>
|
|
101
|
+
*/
|
|
102
|
+
removeFromCache(documentId: DocumentId): Promise<void>;
|
|
92
103
|
shutdown(): Promise<void>;
|
|
104
|
+
metrics(): {
|
|
105
|
+
documents: {
|
|
106
|
+
[key: string]: any;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
93
109
|
}
|
|
94
110
|
export interface RepoConfig {
|
|
95
111
|
/** Our unique identifier */
|
|
@@ -127,6 +143,7 @@ export interface RepoEvents {
|
|
|
127
143
|
"delete-document": (arg: DeleteDocumentPayload) => void;
|
|
128
144
|
/** A document was marked as unavailable (we don't have it and none of our peers have it) */
|
|
129
145
|
"unavailable-document": (arg: DeleteDocumentPayload) => void;
|
|
146
|
+
"doc-metrics": (arg: DocMetrics) => void;
|
|
130
147
|
}
|
|
131
148
|
export interface DocumentPayload {
|
|
132
149
|
handle: DocHandle<any>;
|
|
@@ -134,4 +151,11 @@ export interface DocumentPayload {
|
|
|
134
151
|
export interface DeleteDocumentPayload {
|
|
135
152
|
documentId: DocumentId;
|
|
136
153
|
}
|
|
154
|
+
export type DocMetrics = DocSyncMetrics | {
|
|
155
|
+
type: "doc-loaded";
|
|
156
|
+
documentId: DocumentId;
|
|
157
|
+
durationMillis: number;
|
|
158
|
+
numOps: number;
|
|
159
|
+
numChanges: number;
|
|
160
|
+
};
|
|
137
161
|
//# sourceMappingURL=Repo.d.ts.map
|
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;AAM5C,OAAO,
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAM5C,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,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAMnE,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;gBAK3C,EACV,OAAO,EACP,OAAY,EACZ,MAAuB,EACvB,WAAW,EACX,WAAmC,EACnC,0BAAkC,GACnC,GAAE,UAAe;IAgQlB,8CAA8C;IAC9C,IAAI,OAAO,uCAEV;IAED,+CAA+C;IAC/C,IAAI,KAAK,IAAI,MAAM,EAAE,CAEpB;IAED,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIzD;;;;OAIG;IACH,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAuBzC;;;;;;;;;;;;;;OAcG;IACH,KAAK,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;IAuBnC;;;OAGG;IACH,IAAI,CAAC,CAAC;IACJ,sDAAsD;IACtD,EAAE,EAAE,aAAa,GAChB,SAAS,CAAC,CAAC,CAAC;IA+Cf,MAAM;IACJ,oDAAoD;IACpD,EAAE,EAAE,aAAa;IAWnB;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAShE;;;OAGG;IACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU;IAY5B,kBAAkB,YAAa,SAAS,EAAE,UASzC;IAED,SAAS,QAAa,OAAO,CAAC,SAAS,GAAG,SAAS,CAAC,CAMnD;IAED;;;;;OAKG;IACG,KAAK,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBpD;;;;;OAKG;IACG,eAAe,CAAC,UAAU,EAAE,UAAU;IA2B5C,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAOzB,OAAO,IAAI;QAAE,SAAS,EAAE;YAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;SAAE,CAAA;KAAE;CAGjD;AAED,MAAM,WAAW,UAAU;IACzB,4BAA4B;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;8DAC0D;IAC1D,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB,gDAAgD;IAChD,OAAO,CAAC,EAAE,uBAAuB,CAAA;IAEjC,iEAAiE;IACjE,OAAO,CAAC,EAAE,uBAAuB,EAAE,CAAA;IAEnC;;;OAGG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IAEzB;;OAEG;IACH,0BAA0B,CAAC,EAAE,OAAO,CAAA;CACrC;AAED;;;;;;;KAOK;AACL,MAAM,MAAM,WAAW,GAAG,CACxB,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,UAAU,KACpB,OAAO,CAAC,OAAO,CAAC,CAAA;AAGrB,MAAM,WAAW,UAAU;IACzB,+CAA+C;IAC/C,QAAQ,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,IAAI,CAAA;IACxC,6BAA6B;IAC7B,iBAAiB,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;IACvD,4FAA4F;IAC5F,sBAAsB,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;IAC5D,aAAa,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,IAAI,CAAA;CACzC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAA;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,UAAU,EAAE,UAAU,CAAA;CACvB;AAED,MAAM,MAAM,UAAU,GAClB,cAAc,GACd;IACE,IAAI,EAAE,YAAY,CAAA;IAClB,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,CAAA"}
|
package/dist/Repo.js
CHANGED
|
@@ -2,7 +2,7 @@ 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";
|
|
5
|
+
import { DELETED, DocHandle, READY, UNAVAILABLE, UNLOADED, } from "./DocHandle.js";
|
|
6
6
|
import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js";
|
|
7
7
|
import { headsAreSame } from "./helpers/headsAreSame.js";
|
|
8
8
|
import { throttle } from "./helpers/throttle.js";
|
|
@@ -30,7 +30,8 @@ export class Repo extends EventEmitter {
|
|
|
30
30
|
/** @hidden */
|
|
31
31
|
saveDebounceRate = 100;
|
|
32
32
|
#handleCache = {};
|
|
33
|
-
|
|
33
|
+
/** @hidden */
|
|
34
|
+
synchronizer;
|
|
34
35
|
/** By default, we share generously with all peers. */
|
|
35
36
|
/** @hidden */
|
|
36
37
|
sharePolicy = async () => true;
|
|
@@ -44,26 +45,6 @@ export class Repo extends EventEmitter {
|
|
|
44
45
|
this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping;
|
|
45
46
|
this.#log = debug(`automerge-repo:repo`);
|
|
46
47
|
this.sharePolicy = sharePolicy ?? this.sharePolicy;
|
|
47
|
-
// DOC COLLECTION
|
|
48
|
-
// The `document` event is fired by the DocCollection any time we create a new document or look
|
|
49
|
-
// up a document by ID. We listen for it in order to wire up storage and network synchronization.
|
|
50
|
-
this.on("document", async ({ handle }) => {
|
|
51
|
-
if (storageSubsystem) {
|
|
52
|
-
// Save when the document changes, but no more often than saveDebounceRate.
|
|
53
|
-
const saveFn = ({ handle, doc, }) => {
|
|
54
|
-
void storageSubsystem.saveDoc(handle.documentId, doc);
|
|
55
|
-
};
|
|
56
|
-
handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
|
|
57
|
-
}
|
|
58
|
-
handle.on("unavailable", () => {
|
|
59
|
-
this.#log("document unavailable", { documentId: handle.documentId });
|
|
60
|
-
this.emit("unavailable-document", {
|
|
61
|
-
documentId: handle.documentId,
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
// Register the document with the synchronizer. This advertises our interest in the document.
|
|
65
|
-
this.#synchronizer.addDocument(handle.documentId);
|
|
66
|
-
});
|
|
67
48
|
this.on("delete-document", ({ documentId }) => {
|
|
68
49
|
// TODO Pass the delete on to the network
|
|
69
50
|
// synchronizer.removeDocument(documentId)
|
|
@@ -75,20 +56,25 @@ export class Repo extends EventEmitter {
|
|
|
75
56
|
});
|
|
76
57
|
// SYNCHRONIZER
|
|
77
58
|
// The synchronizer uses the network subsystem to keep documents in sync with peers.
|
|
78
|
-
this
|
|
59
|
+
this.synchronizer = new CollectionSynchronizer(this);
|
|
79
60
|
// When the synchronizer emits messages, send them to peers
|
|
80
|
-
this
|
|
61
|
+
this.synchronizer.on("message", message => {
|
|
81
62
|
this.#log(`sending ${message.type} message to ${message.targetId}`);
|
|
82
63
|
networkSubsystem.send(message);
|
|
83
64
|
});
|
|
65
|
+
// Forward metrics from doc synchronizers
|
|
66
|
+
this.synchronizer.on("metrics", event => this.emit("doc-metrics", event));
|
|
84
67
|
if (this.#remoteHeadsGossipingEnabled) {
|
|
85
|
-
this
|
|
68
|
+
this.synchronizer.on("open-doc", ({ peerId, documentId }) => {
|
|
86
69
|
this.#remoteHeadsSubscriptions.subscribePeerToDoc(peerId, documentId);
|
|
87
70
|
});
|
|
88
71
|
}
|
|
89
72
|
// STORAGE
|
|
90
73
|
// The storage subsystem has access to some form of persistence, and deals with save and loading documents.
|
|
91
74
|
const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
|
|
75
|
+
if (storageSubsystem) {
|
|
76
|
+
storageSubsystem.on("document-loaded", event => this.emit("doc-metrics", { type: "doc-loaded", ...event }));
|
|
77
|
+
}
|
|
92
78
|
this.storageSubsystem = storageSubsystem;
|
|
93
79
|
// NETWORK
|
|
94
80
|
// The network subsystem deals with sending and receiving messages to and from peers.
|
|
@@ -113,18 +99,18 @@ export class Repo extends EventEmitter {
|
|
|
113
99
|
.catch(err => {
|
|
114
100
|
console.log("error in share policy", { err });
|
|
115
101
|
});
|
|
116
|
-
this
|
|
102
|
+
this.synchronizer.addPeer(peerId);
|
|
117
103
|
});
|
|
118
104
|
// When a peer disconnects, remove it from the synchronizer
|
|
119
105
|
networkSubsystem.on("peer-disconnected", ({ peerId }) => {
|
|
120
|
-
this
|
|
106
|
+
this.synchronizer.removePeer(peerId);
|
|
121
107
|
this.#remoteHeadsSubscriptions.removePeer(peerId);
|
|
122
108
|
});
|
|
123
109
|
// Handle incoming messages
|
|
124
110
|
networkSubsystem.on("message", async (msg) => {
|
|
125
111
|
this.#receiveMessage(msg);
|
|
126
112
|
});
|
|
127
|
-
this
|
|
113
|
+
this.synchronizer.on("sync-state", message => {
|
|
128
114
|
this.#saveSyncState(message);
|
|
129
115
|
const handle = this.#handleCache[message.documentId];
|
|
130
116
|
const { storageId } = this.peerMetadataByPeerId[message.peerId] || {};
|
|
@@ -172,6 +158,28 @@ export class Repo extends EventEmitter {
|
|
|
172
158
|
});
|
|
173
159
|
}
|
|
174
160
|
}
|
|
161
|
+
// The `document` event is fired by the DocCollection any time we create a new document or look
|
|
162
|
+
// up a document by ID. We listen for it in order to wire up storage and network synchronization.
|
|
163
|
+
#registerHandleWithSubsystems(handle) {
|
|
164
|
+
const { storageSubsystem } = this;
|
|
165
|
+
if (storageSubsystem) {
|
|
166
|
+
// Save when the document changes, but no more often than saveDebounceRate.
|
|
167
|
+
const saveFn = ({ handle, doc }) => {
|
|
168
|
+
void storageSubsystem.saveDoc(handle.documentId, doc);
|
|
169
|
+
};
|
|
170
|
+
handle.on("heads-changed", throttle(saveFn, this.saveDebounceRate));
|
|
171
|
+
}
|
|
172
|
+
handle.on("unavailable", () => {
|
|
173
|
+
this.#log("document unavailable", { documentId: handle.documentId });
|
|
174
|
+
this.emit("unavailable-document", {
|
|
175
|
+
documentId: handle.documentId,
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
// Register the document with the synchronizer. This advertises our interest in the document.
|
|
179
|
+
this.synchronizer.addDocument(handle.documentId);
|
|
180
|
+
// Preserve the old event in case anyone was using it.
|
|
181
|
+
this.emit("document", { handle });
|
|
182
|
+
}
|
|
175
183
|
#receiveMessage(message) {
|
|
176
184
|
switch (message.type) {
|
|
177
185
|
case "remote-subscription-change":
|
|
@@ -188,7 +196,7 @@ export class Repo extends EventEmitter {
|
|
|
188
196
|
case "request":
|
|
189
197
|
case "ephemeral":
|
|
190
198
|
case "doc-unavailable":
|
|
191
|
-
this
|
|
199
|
+
this.synchronizer.receiveMessage(message).catch(err => {
|
|
192
200
|
console.log("error receiving message", { err });
|
|
193
201
|
});
|
|
194
202
|
}
|
|
@@ -229,7 +237,7 @@ export class Repo extends EventEmitter {
|
|
|
229
237
|
}
|
|
230
238
|
/** Returns a list of all connected peer ids */
|
|
231
239
|
get peers() {
|
|
232
|
-
return this
|
|
240
|
+
return this.synchronizer.peers;
|
|
233
241
|
}
|
|
234
242
|
getStorageIdOfPeer(peerId) {
|
|
235
243
|
return this.peerMetadataByPeerId[peerId]?.storageId;
|
|
@@ -245,7 +253,7 @@ export class Repo extends EventEmitter {
|
|
|
245
253
|
const handle = this.#getHandle({
|
|
246
254
|
documentId,
|
|
247
255
|
});
|
|
248
|
-
this
|
|
256
|
+
this.#registerHandleWithSubsystems(handle);
|
|
249
257
|
handle.update(() => {
|
|
250
258
|
let nextDoc;
|
|
251
259
|
if (initialValue) {
|
|
@@ -277,7 +285,7 @@ export class Repo extends EventEmitter {
|
|
|
277
285
|
clone(clonedHandle) {
|
|
278
286
|
if (!clonedHandle.isReady()) {
|
|
279
287
|
throw new Error(`Cloned handle is not yet in ready state.
|
|
280
|
-
(Try await handle.
|
|
288
|
+
(Try await handle.whenReady() first.)`);
|
|
281
289
|
}
|
|
282
290
|
const sourceDoc = clonedHandle.docSync();
|
|
283
291
|
if (!sourceDoc) {
|
|
@@ -314,31 +322,29 @@ export class Repo extends EventEmitter {
|
|
|
314
322
|
const handle = this.#getHandle({
|
|
315
323
|
documentId,
|
|
316
324
|
});
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
this.emit("document", { handle });
|
|
341
|
-
}
|
|
325
|
+
// Loading & network is going to be asynchronous no matter what,
|
|
326
|
+
// but we want to return the handle immediately.
|
|
327
|
+
const attemptLoad = this.storageSubsystem
|
|
328
|
+
? this.storageSubsystem.loadDoc(handle.documentId)
|
|
329
|
+
: Promise.resolve(null);
|
|
330
|
+
attemptLoad
|
|
331
|
+
.then(async (loadedDoc) => {
|
|
332
|
+
if (loadedDoc) {
|
|
333
|
+
// uhhhh, sorry if you're reading this because we were lying to the type system
|
|
334
|
+
handle.update(() => loadedDoc);
|
|
335
|
+
handle.doneLoading();
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
// we want to wait for the network subsystem to be ready before
|
|
339
|
+
// we request the document. this prevents entering unavailable during initialization.
|
|
340
|
+
await this.networkSubsystem.whenReady();
|
|
341
|
+
handle.request();
|
|
342
|
+
}
|
|
343
|
+
this.#registerHandleWithSubsystems(handle);
|
|
344
|
+
})
|
|
345
|
+
.catch(err => {
|
|
346
|
+
this.#log("error waiting for network", { err });
|
|
347
|
+
});
|
|
342
348
|
return handle;
|
|
343
349
|
}
|
|
344
350
|
delete(
|
|
@@ -415,10 +421,41 @@ export class Repo extends EventEmitter {
|
|
|
415
421
|
return this.storageSubsystem.saveDoc(handle.documentId, doc);
|
|
416
422
|
}));
|
|
417
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* Removes a DocHandle from the handleCache.
|
|
426
|
+
* @hidden this API is experimental and may change.
|
|
427
|
+
* @param documentId - documentId of the DocHandle to remove from handleCache, if present in cache.
|
|
428
|
+
* @returns Promise<void>
|
|
429
|
+
*/
|
|
430
|
+
async removeFromCache(documentId) {
|
|
431
|
+
if (!this.#handleCache[documentId]) {
|
|
432
|
+
this.#log(`WARN: removeFromCache called but handle not found in handleCache for documentId: ${documentId}`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const handle = this.#getHandle({ documentId });
|
|
436
|
+
const doc = await handle.doc([READY, UNLOADED, DELETED, UNAVAILABLE]);
|
|
437
|
+
if (doc) {
|
|
438
|
+
if (handle.isReady()) {
|
|
439
|
+
handle.unload();
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
this.#log(`WARN: removeFromCache called but handle for documentId: ${documentId} in unexpected state: ${handle.state}`);
|
|
443
|
+
}
|
|
444
|
+
delete this.#handleCache[documentId];
|
|
445
|
+
// TODO: remove document from synchronizer when removeDocument is implemented
|
|
446
|
+
// this.synchronizer.removeDocument(documentId)
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
this.#log(`WARN: removeFromCache called but doc undefined for documentId: ${documentId}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
418
452
|
shutdown() {
|
|
419
453
|
this.networkSubsystem.adapters.forEach(adapter => {
|
|
420
454
|
adapter.disconnect();
|
|
421
455
|
});
|
|
422
456
|
return this.flush();
|
|
423
457
|
}
|
|
458
|
+
metrics() {
|
|
459
|
+
return { documents: this.synchronizer.metrics() };
|
|
460
|
+
}
|
|
424
461
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fullfat.d.ts","sourceRoot":"","sources":["../../src/entrypoints/fullfat.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA"}
|
|
1
|
+
{"version":3,"file":"fullfat.d.ts","sourceRoot":"","sources":["../../src/entrypoints/fullfat.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAS3B,OAAO,sBAAsB,CAAA"}
|