@automerge/automerge-repo 2.4.0-alpha.2 → 2.5.0-alpha.0
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 +8 -3
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +20 -7
- package/dist/Repo.d.ts +9 -0
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +11 -3
- package/dist/helpers/abortable.d.ts +22 -0
- package/dist/helpers/abortable.d.ts.map +1 -1
- package/dist/helpers/abortable.js +31 -1
- package/dist/storage/StorageSubsystem.d.ts +9 -0
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +13 -1
- package/dist/synchronizer/DocSynchronizer.d.ts.map +1 -1
- package/dist/synchronizer/DocSynchronizer.js +7 -0
- package/dist/synchronizer/Synchronizer.d.ts +4 -0
- package/dist/synchronizer/Synchronizer.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/DocHandle.ts +33 -7
- package/src/Repo.ts +26 -3
- package/src/helpers/abortable.ts +35 -2
- package/src/storage/StorageSubsystem.ts +24 -1
- package/src/synchronizer/DocSynchronizer.ts +8 -0
- package/src/synchronizer/Synchronizer.ts +5 -0
- package/test/Repo.test.ts +178 -6
package/dist/DocHandle.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { next as A } from "@automerge/automerge/slim";
|
|
|
2
2
|
import { EventEmitter } from "eventemitter3";
|
|
3
3
|
import type { AutomergeUrl, DocumentId, PeerId, UrlHeads } from "./types.js";
|
|
4
4
|
import { StorageId } from "./storage/types.js";
|
|
5
|
+
import { AbortOptions } from "./helpers/abortable.js";
|
|
5
6
|
/**
|
|
6
7
|
* A DocHandle is a wrapper around a single Automerge document that lets us listen for changes and
|
|
7
8
|
* notify the network and storage of new changes.
|
|
@@ -57,13 +58,17 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
57
58
|
/** @hidden */
|
|
58
59
|
get state(): "idle" | "loading" | "requesting" | "ready" | "unavailable" | "unloaded" | "deleted";
|
|
59
60
|
/**
|
|
60
|
-
*
|
|
61
|
-
* are passed, when the document is ready)
|
|
61
|
+
* Returns promise that resolves when document is in one of the given states (default is 'ready' state)
|
|
62
62
|
*
|
|
63
63
|
* Use this to block until the document handle has finished loading. The async equivalent to
|
|
64
64
|
* checking `inState()`.
|
|
65
|
+
*
|
|
66
|
+
* @param awaitStates - HandleState or HandleStates to wait for
|
|
67
|
+
* @param signal - Optional AbortSignal to cancel the waiting operation
|
|
68
|
+
* @returns a promise that resolves when the document is in one of the given states (if no states
|
|
69
|
+
* are passed, when the document is ready)
|
|
65
70
|
*/
|
|
66
|
-
whenReady(awaitStates?: HandleState[]): Promise<void>;
|
|
71
|
+
whenReady(awaitStates?: HandleState[], options?: AbortOptions): Promise<void>;
|
|
67
72
|
/**
|
|
68
73
|
* Returns the current state of the Automerge document this handle manages.
|
|
69
74
|
*
|
package/dist/DocHandle.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAErD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAU5C,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"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"}
|
package/dist/DocHandle.js
CHANGED
|
@@ -6,6 +6,7 @@ import { decodeHeads, encodeHeads, stringifyAutomergeUrl, } from "./AutomergeUrl
|
|
|
6
6
|
import { encode } from "./helpers/cbor.js";
|
|
7
7
|
import { headsAreSame } from "./helpers/headsAreSame.js";
|
|
8
8
|
import { withTimeout } from "./helpers/withTimeout.js";
|
|
9
|
+
import { AbortError, isAbortErrorLike, } from "./helpers/abortable.js";
|
|
9
10
|
/**
|
|
10
11
|
* A DocHandle is a wrapper around a single Automerge document that lets us listen for changes and
|
|
11
12
|
* notify the network and storage of new changes.
|
|
@@ -141,14 +142,19 @@ export class DocHandle extends EventEmitter {
|
|
|
141
142
|
get #state() {
|
|
142
143
|
return this.#machine?.getSnapshot().value;
|
|
143
144
|
}
|
|
144
|
-
/**
|
|
145
|
-
|
|
145
|
+
/**
|
|
146
|
+
* Returns a promise that resolves when the docHandle is in one of the given states
|
|
147
|
+
*
|
|
148
|
+
* @param awaitStates - HandleState or HandleStates to wait for
|
|
149
|
+
* @param signal - Optional AbortSignal to cancel the waiting operation
|
|
150
|
+
*/
|
|
151
|
+
#statePromise(awaitStates, options) {
|
|
146
152
|
const awaitStatesArray = Array.isArray(awaitStates)
|
|
147
153
|
? awaitStates
|
|
148
154
|
: [awaitStates];
|
|
149
155
|
return waitFor(this.#machine, s => awaitStatesArray.some(state => s.matches(state)),
|
|
150
156
|
// use a longer delay here so as not to race with other delays
|
|
151
|
-
{ timeout: this.#timeoutDelay * 2 });
|
|
157
|
+
{ timeout: this.#timeoutDelay * 2, ...options });
|
|
152
158
|
}
|
|
153
159
|
/**
|
|
154
160
|
* Update the document with whatever the result of callback is
|
|
@@ -254,17 +260,24 @@ export class DocHandle extends EventEmitter {
|
|
|
254
260
|
return this.#machine.getSnapshot().value;
|
|
255
261
|
}
|
|
256
262
|
/**
|
|
257
|
-
*
|
|
258
|
-
* are passed, when the document is ready)
|
|
263
|
+
* Returns promise that resolves when document is in one of the given states (default is 'ready' state)
|
|
259
264
|
*
|
|
260
265
|
* Use this to block until the document handle has finished loading. The async equivalent to
|
|
261
266
|
* checking `inState()`.
|
|
267
|
+
*
|
|
268
|
+
* @param awaitStates - HandleState or HandleStates to wait for
|
|
269
|
+
* @param signal - Optional AbortSignal to cancel the waiting operation
|
|
270
|
+
* @returns a promise that resolves when the document is in one of the given states (if no states
|
|
271
|
+
* are passed, when the document is ready)
|
|
262
272
|
*/
|
|
263
|
-
async whenReady(awaitStates = ["ready"]) {
|
|
273
|
+
async whenReady(awaitStates = ["ready"], options) {
|
|
264
274
|
try {
|
|
265
|
-
await withTimeout(this.#statePromise(awaitStates), this.#timeoutDelay);
|
|
275
|
+
await withTimeout(this.#statePromise(awaitStates, options), this.#timeoutDelay);
|
|
266
276
|
}
|
|
267
277
|
catch (error) {
|
|
278
|
+
if (isAbortErrorLike(error)) {
|
|
279
|
+
throw new AbortError(); //throw new error for stack trace
|
|
280
|
+
}
|
|
268
281
|
console.log(`error waiting for ${this.documentId} to be in one of states: ${awaitStates.join(", ")}`);
|
|
269
282
|
throw error;
|
|
270
283
|
}
|
package/dist/Repo.d.ts
CHANGED
|
@@ -241,6 +241,15 @@ export interface DeleteDocumentPayload {
|
|
|
241
241
|
documentId: DocumentId;
|
|
242
242
|
}
|
|
243
243
|
export type DocMetrics = DocSyncMetrics | {
|
|
244
|
+
type: "doc-compacted";
|
|
245
|
+
documentId: DocumentId;
|
|
246
|
+
durationMillis: number;
|
|
247
|
+
} | {
|
|
248
|
+
type: "doc-saved";
|
|
249
|
+
documentId: DocumentId;
|
|
250
|
+
durationMillis: number;
|
|
251
|
+
sinceHeads: Array<string>;
|
|
252
|
+
} | {
|
|
244
253
|
type: "doc-loaded";
|
|
245
254
|
documentId: DocumentId;
|
|
246
255
|
durationMillis: number;
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,EAAE,MAAM,2BAA2B,CAAA;AAEpE,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAS5C,OAAO,EAEL,SAAS,EAKV,MAAM,gBAAgB,CAAA;AAIvB,OAAO,EACL,uBAAuB,EACvB,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,cAAc,EAEf,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAEZ,UAAU,EACV,MAAM,EACP,MAAM,YAAY,CAAA;AACnB,OAAO,EAAa,YAAY,
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
|
package/dist/Repo.js
CHANGED
|
@@ -9,7 +9,7 @@ import { throttle } from "./helpers/throttle.js";
|
|
|
9
9
|
import { NetworkSubsystem } from "./network/NetworkSubsystem.js";
|
|
10
10
|
import { StorageSubsystem } from "./storage/StorageSubsystem.js";
|
|
11
11
|
import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js";
|
|
12
|
-
import { abortable } from "./helpers/abortable.js";
|
|
12
|
+
import { abortable, AbortError } from "./helpers/abortable.js";
|
|
13
13
|
function randomPeerId() {
|
|
14
14
|
return ("peer-" + Math.random().toString(36).slice(4));
|
|
15
15
|
}
|
|
@@ -92,6 +92,8 @@ export class Repo extends EventEmitter {
|
|
|
92
92
|
const storageSubsystem = storage ? new StorageSubsystem(storage) : undefined;
|
|
93
93
|
if (storageSubsystem) {
|
|
94
94
|
storageSubsystem.on("document-loaded", event => this.emit("doc-metrics", { type: "doc-loaded", ...event }));
|
|
95
|
+
storageSubsystem.on("doc-compacted", event => this.emit("doc-metrics", { type: "doc-compacted", ...event }));
|
|
96
|
+
storageSubsystem.on("doc-saved", event => this.emit("doc-metrics", { type: "doc-saved", ...event }));
|
|
95
97
|
}
|
|
96
98
|
this.storageSubsystem = storageSubsystem;
|
|
97
99
|
this.#saveDebounceRate = saveDebounceRate;
|
|
@@ -510,7 +512,13 @@ export class Repo extends EventEmitter {
|
|
|
510
512
|
catch (error) {
|
|
511
513
|
progressSignal.notify({
|
|
512
514
|
state: "failed",
|
|
513
|
-
error:
|
|
515
|
+
error:
|
|
516
|
+
// In most JS environments DOMException extends Error, but not always, in some environments it's a separate type.
|
|
517
|
+
// Some Node.js DOM polyfills do not always extend the Error
|
|
518
|
+
// Jsdom polyfill doesn't extend Error, whereas happy-dom does.
|
|
519
|
+
error instanceof Error || error instanceof DOMException
|
|
520
|
+
? error
|
|
521
|
+
: new Error(String(error)),
|
|
514
522
|
handle: this.#getHandle({ documentId }),
|
|
515
523
|
});
|
|
516
524
|
}
|
|
@@ -519,7 +527,7 @@ export class Repo extends EventEmitter {
|
|
|
519
527
|
const { allowableStates = ["ready"], signal } = options;
|
|
520
528
|
// Check if already aborted
|
|
521
529
|
if (signal?.aborted) {
|
|
522
|
-
throw new
|
|
530
|
+
throw new AbortError();
|
|
523
531
|
}
|
|
524
532
|
const progress = this.findWithProgress(id, { signal });
|
|
525
533
|
if ("subscribe" in progress) {
|
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An error thrown when an operation is aborted.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This error is thrown when an operation is aborted. It is a subclass of DOMException
|
|
6
|
+
* with name "AbortError".
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* throw new AbortError()
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export declare class AbortError extends DOMException {
|
|
14
|
+
constructor(message?: string);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Detects if candidate `Error` is an `AbortError` or AbortError-like.
|
|
18
|
+
* @remarks
|
|
19
|
+
* - This method detects if an error is AbortError-like (for which there could be many implementations)
|
|
20
|
+
* - AbortController spec defines AbortError as DOMException or Error with `name === 'AbortError'`.
|
|
21
|
+
*/
|
|
22
|
+
export declare const isAbortErrorLike: (candidate: unknown) => boolean;
|
|
1
23
|
/**
|
|
2
24
|
* Wraps a Promise and causes it to reject when the signal is aborted.
|
|
3
25
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abortable.d.ts","sourceRoot":"","sources":["../../src/helpers/abortable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;
|
|
1
|
+
{"version":3,"file":"abortable.d.ts","sourceRoot":"","sources":["../../src/helpers/abortable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,qBAAa,UAAW,SAAQ,YAAY;gBAC9B,OAAO,CAAC,EAAE,MAAM;CAG7B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,OAAO,KAAG,OAQrD,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CAAC,CAAC,EACzB,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,EACb,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,CAAC,CAAC,CAsBZ;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,WAAW,CAAA;CACrB"}
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An error thrown when an operation is aborted.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This error is thrown when an operation is aborted. It is a subclass of DOMException
|
|
6
|
+
* with name "AbortError".
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* throw new AbortError()
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export class AbortError extends DOMException {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message ?? "Operation aborted", "AbortError");
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Detects if candidate `Error` is an `AbortError` or AbortError-like.
|
|
20
|
+
* @remarks
|
|
21
|
+
* - This method detects if an error is AbortError-like (for which there could be many implementations)
|
|
22
|
+
* - AbortController spec defines AbortError as DOMException or Error with `name === 'AbortError'`.
|
|
23
|
+
*/
|
|
24
|
+
export const isAbortErrorLike = (candidate) => {
|
|
25
|
+
return (candidate instanceof AbortError ||
|
|
26
|
+
((candidate instanceof Error ||
|
|
27
|
+
//In some JS environments, DOMException is not defined, and sometimes when defined, it does not extend Error; hence extra checks
|
|
28
|
+
(DOMException && candidate instanceof DOMException)) &&
|
|
29
|
+
candidate.name === "AbortError"));
|
|
30
|
+
};
|
|
1
31
|
/**
|
|
2
32
|
* Wraps a Promise and causes it to reject when the signal is aborted.
|
|
3
33
|
*
|
|
@@ -31,7 +61,7 @@ export function abortable(p, signal) {
|
|
|
31
61
|
return new Promise((resolve, reject) => {
|
|
32
62
|
signal?.addEventListener("abort", () => {
|
|
33
63
|
if (!settled) {
|
|
34
|
-
reject(new
|
|
64
|
+
reject(new AbortError());
|
|
35
65
|
}
|
|
36
66
|
}, { once: true });
|
|
37
67
|
p.then(result => {
|
|
@@ -10,6 +10,15 @@ type StorageSubsystemEvents = {
|
|
|
10
10
|
numOps: number;
|
|
11
11
|
numChanges: number;
|
|
12
12
|
}) => void;
|
|
13
|
+
"doc-compacted": (arg: {
|
|
14
|
+
documentId: DocumentId;
|
|
15
|
+
durationMillis: number;
|
|
16
|
+
}) => void;
|
|
17
|
+
"doc-saved": (arg: {
|
|
18
|
+
documentId: DocumentId;
|
|
19
|
+
durationMillis: number;
|
|
20
|
+
sinceHeads: A.Heads;
|
|
21
|
+
}) => void;
|
|
13
22
|
};
|
|
14
23
|
/**
|
|
15
24
|
* The storage subsystem is responsible for saving and loading Automerge documents to and from
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAIrD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAG7D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAG5C,KAAK,sBAAsB,GAAG;IAC5B,iBAAiB,EAAE,CAAC,GAAG,EAAE;QACvB,UAAU,EAAE,UAAU,CAAA;QACtB,cAAc,EAAE,MAAM,CAAA;QACtB,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;KACnB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;gBAe5D,cAAc,EAAE,uBAAuB;IAK7C,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IA2B9B,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAgDrE;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAqBlE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAezE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;
|
|
1
|
+
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAIrD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAG7D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAG5C,KAAK,sBAAsB,GAAG;IAC5B,iBAAiB,EAAE,CAAC,GAAG,EAAE;QACvB,UAAU,EAAE,UAAU,CAAA;QACtB,cAAc,EAAE,MAAM,CAAA;QACtB,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;KACnB,KAAK,IAAI,CAAA;IACV,eAAe,EAAE,CAAC,GAAG,EAAE;QACrB,UAAU,EAAE,UAAU,CAAA;QACtB,cAAc,EAAE,MAAM,CAAA;KACvB,KAAK,IAAI,CAAA;IACV,WAAW,EAAE,CAAC,GAAG,EAAE;QACjB,UAAU,EAAE,UAAU,CAAA;QACtB,cAAc,EAAE,MAAM,CAAA;QACtB,UAAU,EAAE,CAAC,CAAC,KAAK,CAAA;KACpB,KAAK,IAAI,CAAA;CACX,CAAA;AAED;;;GAGG;AACH,qBAAa,gBAAiB,SAAQ,YAAY,CAAC,sBAAsB,CAAC;;gBAe5D,cAAc,EAAE,uBAAuB;IAK7C,EAAE,IAAI,OAAO,CAAC,SAAS,CAAC;IA2B9B,kCAAkC;IAC5B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAKlC,gCAAgC;IAC1B,IAAI;IACR,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,yFAAyF;IACzF,GAAG,EAAE,MAAM;IAEX,sCAAsC;IACtC,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC;IAKhB,oCAAoC;IAC9B,MAAM;IACV,iFAAiF;IACjF,SAAS,EAAE,MAAM;IAEjB,2FAA2F;IAC3F,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,IAAI,CAAC;IAOhB;;OAEG;IACG,WAAW,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAgDrE;;OAEG;IACG,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAqBlE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAezE;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,UAAU;IAgFhC,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,GACnB,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;IAW7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;CA8CjB"}
|
|
@@ -173,7 +173,16 @@ export class StorageSubsystem extends EventEmitter {
|
|
|
173
173
|
* Saves just the incremental changes since the last save.
|
|
174
174
|
*/
|
|
175
175
|
async #saveIncremental(documentId, doc) {
|
|
176
|
-
const
|
|
176
|
+
const sinceHeads = this.#storedHeads.get(documentId) ?? [];
|
|
177
|
+
const start = performance.now();
|
|
178
|
+
const binary = A.saveSince(doc, sinceHeads);
|
|
179
|
+
const end = performance.now();
|
|
180
|
+
console.log("emitting saved");
|
|
181
|
+
this.emit("doc-saved", {
|
|
182
|
+
documentId,
|
|
183
|
+
durationMillis: end - start,
|
|
184
|
+
sinceHeads,
|
|
185
|
+
});
|
|
177
186
|
if (binary && binary.length > 0) {
|
|
178
187
|
const key = [documentId, "incremental", keyHash(binary)];
|
|
179
188
|
this.#log(`Saving incremental ${key} for document ${documentId}`);
|
|
@@ -197,7 +206,10 @@ export class StorageSubsystem extends EventEmitter {
|
|
|
197
206
|
*/
|
|
198
207
|
async #saveTotal(documentId, doc, sourceChunks) {
|
|
199
208
|
this.#compacting = true;
|
|
209
|
+
const start = performance.now();
|
|
200
210
|
const binary = A.save(doc);
|
|
211
|
+
const end = performance.now();
|
|
212
|
+
this.emit("doc-compacted", { documentId, durationMillis: end - start });
|
|
201
213
|
const snapshotHash = headsHash(A.getHeads(doc));
|
|
202
214
|
const key = [documentId, "snapshot", snapshotHash];
|
|
203
215
|
const oldKeys = new Set(sourceChunks.map(c => c.key).filter(k => k[2] !== snapshotHash));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAGrD,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;CACvE;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAyBV,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyBtE,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;
|
|
1
|
+
{"version":3,"file":"DocSynchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/DocSynchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,IAAI,CAAC,EAAE,MAAM,2BAA2B,CAAA;AAGrD,OAAO,EACL,SAAS,EAKV,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAEL,gBAAgB,EAEhB,WAAW,EACX,cAAc,EACd,WAAW,EAEZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGhD,KAAK,kBAAkB,GAAG,SAAS,GAAG,KAAK,GAAG,aAAa,GAAG,OAAO,CAAA;AAOrE,UAAU,qBAAqB;IAC7B,MAAM,EAAE,SAAS,CAAC,OAAO,CAAC,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC,CAAA;CACvE;AAED;;;GAGG;AACH,qBAAa,eAAgB,SAAQ,YAAY;;IAE/C,gBAAgB,SAAM;gBAyBV,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,qBAAqB;IAyBtE,IAAI,UAAU,uCAEb;IAED,IAAI,UAAU,qCAEb;IA6ID,OAAO,CAAC,MAAM,EAAE,MAAM;IAIhB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE;IA8DjC,OAAO,CAAC,MAAM,EAAE,MAAM;IAOtB,cAAc,CAAC,OAAO,EAAE,WAAW;IAkBnC,uBAAuB,CAAC,OAAO,EAAE,gBAAgB;IAuBjD,kBAAkB,CAAC,OAAO,EAAE,WAAW,GAAG,cAAc;IA2FxD,OAAO,IAAI;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,IAAI,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;CAM7E"}
|
|
@@ -121,7 +121,14 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
121
121
|
#sendSyncMessage(peerId, doc) {
|
|
122
122
|
this.#log(`sendSyncMessage ->${peerId}`);
|
|
123
123
|
this.#withSyncState(peerId, syncState => {
|
|
124
|
+
const start = performance.now();
|
|
124
125
|
const [newSyncState, message] = A.generateSyncMessage(doc, syncState);
|
|
126
|
+
const end = performance.now();
|
|
127
|
+
this.emit("metrics", {
|
|
128
|
+
type: "generate-sync-message",
|
|
129
|
+
documentId: this.#handle.documentId,
|
|
130
|
+
durationMillis: end - start,
|
|
131
|
+
});
|
|
125
132
|
if (message) {
|
|
126
133
|
this.#setSyncState(peerId, newSyncState);
|
|
127
134
|
const isNew = A.getHeads(doc).length === 0;
|
|
@@ -23,6 +23,10 @@ export type DocSyncMetrics = {
|
|
|
23
23
|
durationMillis: number;
|
|
24
24
|
numOps: number;
|
|
25
25
|
numChanges: number;
|
|
26
|
+
} | {
|
|
27
|
+
type: "generate-sync-message";
|
|
28
|
+
documentId: DocumentId;
|
|
29
|
+
durationMillis: number;
|
|
26
30
|
} | {
|
|
27
31
|
type: "doc-denied";
|
|
28
32
|
documentId: DocumentId;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;IACjD,UAAU,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACvC;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;CACrB;AAED,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,sBAAsB,CAAA;IAC5B,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,GACD;IACE,IAAI,EAAE,YAAY,CAAA;IAClB,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA"}
|
|
1
|
+
{"version":3,"file":"Synchronizer.d.ts","sourceRoot":"","sources":["../../src/synchronizer/Synchronizer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EACL,eAAe,EACf,cAAc,EACd,WAAW,EACZ,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAEhD,8BAAsB,YAAa,SAAQ,YAAY,CAAC,kBAAkB,CAAC;IACzE,QAAQ,CAAC,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,IAAI;CACpD;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC3C,YAAY,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAA;IACjD,UAAU,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;IACzC,OAAO,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACvC;AAED,uDAAuD;AACvD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,UAAU,CAAA;IACtB,SAAS,EAAE,SAAS,CAAA;CACrB;AAED,MAAM,MAAM,cAAc,GACtB;IACE,IAAI,EAAE,sBAAsB,CAAA;IAC5B,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,EAAE,MAAM,CAAA;CACnB,GACD;IACE,IAAI,EAAE,uBAAuB,CAAA;IAC7B,UAAU,EAAE,UAAU,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;CACvB,GACD;IACE,IAAI,EAAE,YAAY,CAAA;IAClB,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0-alpha.0",
|
|
4
4
|
"description": "A repository object to manage a collection of automerge documents",
|
|
5
5
|
"repository": "https://github.com/automerge/automerge-repo/tree/master/packages/automerge-repo",
|
|
6
6
|
"author": "Peter van Hardenberg <pvh@pvh.ca>",
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "6d94459eecf708f9c508be7e22c2035abffc03a4"
|
|
63
63
|
}
|
package/src/DocHandle.ts
CHANGED
|
@@ -12,6 +12,11 @@ import { headsAreSame } from "./helpers/headsAreSame.js"
|
|
|
12
12
|
import { withTimeout } from "./helpers/withTimeout.js"
|
|
13
13
|
import type { AutomergeUrl, DocumentId, PeerId, UrlHeads } from "./types.js"
|
|
14
14
|
import { StorageId } from "./storage/types.js"
|
|
15
|
+
import {
|
|
16
|
+
AbortError,
|
|
17
|
+
AbortOptions,
|
|
18
|
+
isAbortErrorLike,
|
|
19
|
+
} from "./helpers/abortable.js"
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* A DocHandle is a wrapper around a single Automerge document that lets us listen for changes and
|
|
@@ -170,8 +175,16 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
170
175
|
return this.#machine?.getSnapshot().value
|
|
171
176
|
}
|
|
172
177
|
|
|
173
|
-
/**
|
|
174
|
-
|
|
178
|
+
/**
|
|
179
|
+
* Returns a promise that resolves when the docHandle is in one of the given states
|
|
180
|
+
*
|
|
181
|
+
* @param awaitStates - HandleState or HandleStates to wait for
|
|
182
|
+
* @param signal - Optional AbortSignal to cancel the waiting operation
|
|
183
|
+
*/
|
|
184
|
+
#statePromise(
|
|
185
|
+
awaitStates: HandleState | HandleState[],
|
|
186
|
+
options?: AbortOptions
|
|
187
|
+
) {
|
|
175
188
|
const awaitStatesArray = Array.isArray(awaitStates)
|
|
176
189
|
? awaitStates
|
|
177
190
|
: [awaitStates]
|
|
@@ -179,7 +192,7 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
179
192
|
this.#machine,
|
|
180
193
|
s => awaitStatesArray.some(state => s.matches(state)),
|
|
181
194
|
// use a longer delay here so as not to race with other delays
|
|
182
|
-
{ timeout: this.#timeoutDelay * 2 }
|
|
195
|
+
{ timeout: this.#timeoutDelay * 2, ...options }
|
|
183
196
|
)
|
|
184
197
|
}
|
|
185
198
|
|
|
@@ -301,16 +314,29 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
301
314
|
}
|
|
302
315
|
|
|
303
316
|
/**
|
|
304
|
-
*
|
|
305
|
-
* are passed, when the document is ready)
|
|
317
|
+
* Returns promise that resolves when document is in one of the given states (default is 'ready' state)
|
|
306
318
|
*
|
|
307
319
|
* Use this to block until the document handle has finished loading. The async equivalent to
|
|
308
320
|
* checking `inState()`.
|
|
321
|
+
*
|
|
322
|
+
* @param awaitStates - HandleState or HandleStates to wait for
|
|
323
|
+
* @param signal - Optional AbortSignal to cancel the waiting operation
|
|
324
|
+
* @returns a promise that resolves when the document is in one of the given states (if no states
|
|
325
|
+
* are passed, when the document is ready)
|
|
309
326
|
*/
|
|
310
|
-
async whenReady(
|
|
327
|
+
async whenReady(
|
|
328
|
+
awaitStates: HandleState[] = ["ready"],
|
|
329
|
+
options?: AbortOptions
|
|
330
|
+
) {
|
|
311
331
|
try {
|
|
312
|
-
await withTimeout(
|
|
332
|
+
await withTimeout(
|
|
333
|
+
this.#statePromise(awaitStates, options),
|
|
334
|
+
this.#timeoutDelay
|
|
335
|
+
)
|
|
313
336
|
} catch (error) {
|
|
337
|
+
if (isAbortErrorLike(error)) {
|
|
338
|
+
throw new AbortError() //throw new error for stack trace
|
|
339
|
+
}
|
|
314
340
|
console.log(
|
|
315
341
|
`error waiting for ${
|
|
316
342
|
this.documentId
|
package/src/Repo.ts
CHANGED
|
@@ -41,7 +41,7 @@ import type {
|
|
|
41
41
|
DocumentId,
|
|
42
42
|
PeerId,
|
|
43
43
|
} from "./types.js"
|
|
44
|
-
import { abortable, AbortOptions } from "./helpers/abortable.js"
|
|
44
|
+
import { abortable, AbortOptions, AbortError } from "./helpers/abortable.js"
|
|
45
45
|
import { FindProgress } from "./FindProgress.js"
|
|
46
46
|
|
|
47
47
|
export type FindProgressWithMethods<T> = FindProgress<T> & {
|
|
@@ -172,6 +172,12 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
172
172
|
storageSubsystem.on("document-loaded", event =>
|
|
173
173
|
this.emit("doc-metrics", { type: "doc-loaded", ...event })
|
|
174
174
|
)
|
|
175
|
+
storageSubsystem.on("doc-compacted", event =>
|
|
176
|
+
this.emit("doc-metrics", { type: "doc-compacted", ...event })
|
|
177
|
+
)
|
|
178
|
+
storageSubsystem.on("doc-saved", event =>
|
|
179
|
+
this.emit("doc-metrics", { type: "doc-saved", ...event })
|
|
180
|
+
)
|
|
175
181
|
}
|
|
176
182
|
|
|
177
183
|
this.storageSubsystem = storageSubsystem
|
|
@@ -704,7 +710,13 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
704
710
|
} catch (error) {
|
|
705
711
|
progressSignal.notify({
|
|
706
712
|
state: "failed" as const,
|
|
707
|
-
error:
|
|
713
|
+
error:
|
|
714
|
+
// In most JS environments DOMException extends Error, but not always, in some environments it's a separate type.
|
|
715
|
+
// Some Node.js DOM polyfills do not always extend the Error
|
|
716
|
+
// Jsdom polyfill doesn't extend Error, whereas happy-dom does.
|
|
717
|
+
error instanceof Error || error instanceof DOMException
|
|
718
|
+
? error
|
|
719
|
+
: new Error(String(error)),
|
|
708
720
|
handle: this.#getHandle<T>({ documentId }),
|
|
709
721
|
})
|
|
710
722
|
}
|
|
@@ -718,7 +730,7 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
718
730
|
|
|
719
731
|
// Check if already aborted
|
|
720
732
|
if (signal?.aborted) {
|
|
721
|
-
throw new
|
|
733
|
+
throw new AbortError()
|
|
722
734
|
}
|
|
723
735
|
|
|
724
736
|
const progress = this.findWithProgress<T>(id, { signal })
|
|
@@ -1082,6 +1094,17 @@ export interface DeleteDocumentPayload {
|
|
|
1082
1094
|
|
|
1083
1095
|
export type DocMetrics =
|
|
1084
1096
|
| DocSyncMetrics
|
|
1097
|
+
| {
|
|
1098
|
+
type: "doc-compacted"
|
|
1099
|
+
documentId: DocumentId
|
|
1100
|
+
durationMillis: number
|
|
1101
|
+
}
|
|
1102
|
+
| {
|
|
1103
|
+
type: "doc-saved"
|
|
1104
|
+
documentId: DocumentId
|
|
1105
|
+
durationMillis: number
|
|
1106
|
+
sinceHeads: Array<string>
|
|
1107
|
+
}
|
|
1085
1108
|
| {
|
|
1086
1109
|
type: "doc-loaded"
|
|
1087
1110
|
documentId: DocumentId
|
package/src/helpers/abortable.ts
CHANGED
|
@@ -1,3 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An error thrown when an operation is aborted.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* This error is thrown when an operation is aborted. It is a subclass of DOMException
|
|
6
|
+
* with name "AbortError".
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* throw new AbortError()
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export class AbortError extends DOMException {
|
|
14
|
+
constructor(message?: string) {
|
|
15
|
+
super(message ?? "Operation aborted", "AbortError")
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Detects if candidate `Error` is an `AbortError` or AbortError-like.
|
|
21
|
+
* @remarks
|
|
22
|
+
* - This method detects if an error is AbortError-like (for which there could be many implementations)
|
|
23
|
+
* - AbortController spec defines AbortError as DOMException or Error with `name === 'AbortError'`.
|
|
24
|
+
*/
|
|
25
|
+
export const isAbortErrorLike = (candidate: unknown): boolean => {
|
|
26
|
+
return (
|
|
27
|
+
candidate instanceof AbortError ||
|
|
28
|
+
((candidate instanceof Error ||
|
|
29
|
+
//In some JS environments, DOMException is not defined, and sometimes when defined, it does not extend Error; hence extra checks
|
|
30
|
+
(DOMException && candidate instanceof DOMException)) &&
|
|
31
|
+
candidate.name === "AbortError")
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
1
35
|
/**
|
|
2
36
|
* Wraps a Promise and causes it to reject when the signal is aborted.
|
|
3
37
|
*
|
|
@@ -26,7 +60,6 @@
|
|
|
26
60
|
* before the promise p settles, and settles as p settles otherwise
|
|
27
61
|
* @throws {DOMException} With name "AbortError" if aborted before p settles
|
|
28
62
|
*/
|
|
29
|
-
|
|
30
63
|
export function abortable<T>(
|
|
31
64
|
p: Promise<T>,
|
|
32
65
|
signal: AbortSignal | undefined
|
|
@@ -37,7 +70,7 @@ export function abortable<T>(
|
|
|
37
70
|
"abort",
|
|
38
71
|
() => {
|
|
39
72
|
if (!settled) {
|
|
40
|
-
reject(new
|
|
73
|
+
reject(new AbortError())
|
|
41
74
|
}
|
|
42
75
|
},
|
|
43
76
|
{ once: true }
|
|
@@ -17,6 +17,15 @@ type StorageSubsystemEvents = {
|
|
|
17
17
|
numOps: number
|
|
18
18
|
numChanges: number
|
|
19
19
|
}) => void
|
|
20
|
+
"doc-compacted": (arg: {
|
|
21
|
+
documentId: DocumentId
|
|
22
|
+
durationMillis: number
|
|
23
|
+
}) => void
|
|
24
|
+
"doc-saved": (arg: {
|
|
25
|
+
documentId: DocumentId
|
|
26
|
+
durationMillis: number
|
|
27
|
+
sinceHeads: A.Heads
|
|
28
|
+
}) => void
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
/**
|
|
@@ -224,7 +233,17 @@ export class StorageSubsystem extends EventEmitter<StorageSubsystemEvents> {
|
|
|
224
233
|
documentId: DocumentId,
|
|
225
234
|
doc: A.Doc<unknown>
|
|
226
235
|
): Promise<void> {
|
|
227
|
-
const
|
|
236
|
+
const sinceHeads = this.#storedHeads.get(documentId) ?? []
|
|
237
|
+
const start = performance.now()
|
|
238
|
+
const binary = A.saveSince(doc, sinceHeads)
|
|
239
|
+
const end = performance.now()
|
|
240
|
+
console.log("emitting saved")
|
|
241
|
+
this.emit("doc-saved", {
|
|
242
|
+
documentId,
|
|
243
|
+
durationMillis: end - start,
|
|
244
|
+
sinceHeads,
|
|
245
|
+
})
|
|
246
|
+
|
|
228
247
|
if (binary && binary.length > 0) {
|
|
229
248
|
const key = [documentId, "incremental", keyHash(binary)]
|
|
230
249
|
this.#log(`Saving incremental ${key} for document ${documentId}`)
|
|
@@ -253,7 +272,11 @@ export class StorageSubsystem extends EventEmitter<StorageSubsystemEvents> {
|
|
|
253
272
|
): Promise<void> {
|
|
254
273
|
this.#compacting = true
|
|
255
274
|
|
|
275
|
+
const start = performance.now()
|
|
256
276
|
const binary = A.save(doc)
|
|
277
|
+
const end = performance.now()
|
|
278
|
+
this.emit("doc-compacted", { documentId, durationMillis: end - start })
|
|
279
|
+
|
|
257
280
|
const snapshotHash = headsHash(A.getHeads(doc))
|
|
258
281
|
const key = [documentId, "snapshot", snapshotHash]
|
|
259
282
|
const oldKeys = new Set(
|
|
@@ -191,7 +191,15 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
191
191
|
this.#log(`sendSyncMessage ->${peerId}`)
|
|
192
192
|
|
|
193
193
|
this.#withSyncState(peerId, syncState => {
|
|
194
|
+
const start = performance.now()
|
|
194
195
|
const [newSyncState, message] = A.generateSyncMessage(doc, syncState)
|
|
196
|
+
const end = performance.now()
|
|
197
|
+
this.emit("metrics", {
|
|
198
|
+
type: "generate-sync-message",
|
|
199
|
+
documentId: this.#handle.documentId,
|
|
200
|
+
durationMillis: end - start,
|
|
201
|
+
})
|
|
202
|
+
|
|
195
203
|
if (message) {
|
|
196
204
|
this.#setSyncState(peerId, newSyncState)
|
|
197
205
|
const isNew = A.getHeads(doc).length === 0
|
package/test/Repo.test.ts
CHANGED
|
@@ -11,7 +11,12 @@ import {
|
|
|
11
11
|
generateAutomergeUrl,
|
|
12
12
|
stringifyAutomergeUrl,
|
|
13
13
|
} from "../src/AutomergeUrl.js"
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
DocMetrics,
|
|
16
|
+
FindProgressWithMethods,
|
|
17
|
+
Repo,
|
|
18
|
+
ShareConfig,
|
|
19
|
+
} from "../src/Repo.js"
|
|
15
20
|
import { eventPromise } from "../src/helpers/eventPromise.js"
|
|
16
21
|
import { pause } from "../src/helpers/pause.js"
|
|
17
22
|
import {
|
|
@@ -34,6 +39,7 @@ import { getRandomItem } from "./helpers/getRandomItem.js"
|
|
|
34
39
|
import { TestDoc } from "./types.js"
|
|
35
40
|
import { StorageId, StorageKey } from "../src/storage/types.js"
|
|
36
41
|
import { FindProgress } from "../src/FindProgress.js"
|
|
42
|
+
import { AbortError } from "../src/helpers/abortable.js"
|
|
37
43
|
|
|
38
44
|
describe("Repo", () => {
|
|
39
45
|
describe("constructor", () => {
|
|
@@ -1993,7 +1999,7 @@ describe("Repo.find() abort behavior", () => {
|
|
|
1993
1999
|
|
|
1994
2000
|
await expect(
|
|
1995
2001
|
repo.find(generateAutomergeUrl(), { signal: controller.signal })
|
|
1996
|
-
).rejects.toThrow(
|
|
2002
|
+
).rejects.toThrow(AbortError)
|
|
1997
2003
|
})
|
|
1998
2004
|
|
|
1999
2005
|
it("can abort while waiting for ready state", async () => {
|
|
@@ -2007,7 +2013,13 @@ describe("Repo.find() abort behavior", () => {
|
|
|
2007
2013
|
const findPromise = repo.find(url, { signal: controller.signal })
|
|
2008
2014
|
controller.abort()
|
|
2009
2015
|
|
|
2010
|
-
|
|
2016
|
+
// Official specification just says to check `reason.name === "AbortError"`
|
|
2017
|
+
// Using AbortError promotes correctness across different JS environments and provides a simpler check.
|
|
2018
|
+
await expect(findPromise).rejects.toThrow(AbortError)
|
|
2019
|
+
await expect(findPromise).rejects.rejects.toHaveProperty(
|
|
2020
|
+
"name",
|
|
2021
|
+
"AbortError"
|
|
2022
|
+
)
|
|
2011
2023
|
await expect(findPromise).rejects.not.toThrow("unavailable")
|
|
2012
2024
|
})
|
|
2013
2025
|
|
|
@@ -2032,7 +2044,7 @@ describe("Repo.find() abort behavior", () => {
|
|
|
2032
2044
|
it("creates a document with the custom ID", async () => {
|
|
2033
2045
|
const id = new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0)))
|
|
2034
2046
|
const repo = new Repo({
|
|
2035
|
-
idFactory: () => id,
|
|
2047
|
+
idFactory: async () => id,
|
|
2036
2048
|
})
|
|
2037
2049
|
const handle = await repo.create2()
|
|
2038
2050
|
expect(handle.documentId).toBe("9HUp4wuzRMx9MRvN4x")
|
|
@@ -2042,7 +2054,7 @@ describe("Repo.find() abort behavior", () => {
|
|
|
2042
2054
|
const id = new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0)))
|
|
2043
2055
|
let calledHeads: Heads | null = null
|
|
2044
2056
|
const repo = new Repo({
|
|
2045
|
-
idFactory: (heads: Heads) => {
|
|
2057
|
+
idFactory: async (heads: Heads) => {
|
|
2046
2058
|
calledHeads = heads
|
|
2047
2059
|
return id
|
|
2048
2060
|
},
|
|
@@ -2056,7 +2068,7 @@ describe("Repo.find() abort behavior", () => {
|
|
|
2056
2068
|
const [aliceToBob, bobToAlice] = DummyNetworkAdapter.createConnectedPair()
|
|
2057
2069
|
const alice = new Repo({
|
|
2058
2070
|
peerId: "alice" as PeerId,
|
|
2059
|
-
idFactory: () =>
|
|
2071
|
+
idFactory: async () =>
|
|
2060
2072
|
new Uint8Array("custom-id".split("").map(c => c.charCodeAt(0))),
|
|
2061
2073
|
network: [aliceToBob],
|
|
2062
2074
|
})
|
|
@@ -2071,6 +2083,166 @@ describe("Repo.find() abort behavior", () => {
|
|
|
2071
2083
|
assert.deepStrictEqual(bobHandle.doc(), { foo: "bar" })
|
|
2072
2084
|
})
|
|
2073
2085
|
})
|
|
2086
|
+
|
|
2087
|
+
describe("emitted metrics", () => {
|
|
2088
|
+
async function setup(): Promise<{ alice: Repo; bob: Repo }> {
|
|
2089
|
+
const [aliceToBob, bobToAlice] = DummyNetworkAdapter.createConnectedPair()
|
|
2090
|
+
const alice = new Repo({
|
|
2091
|
+
peerId: "alice" as PeerId,
|
|
2092
|
+
network: [aliceToBob],
|
|
2093
|
+
})
|
|
2094
|
+
const bob = new Repo({ peerId: "bob" as PeerId, network: [bobToAlice] })
|
|
2095
|
+
aliceToBob.peerCandidate("bob" as PeerId)
|
|
2096
|
+
bobToAlice.peerCandidate("alice" as PeerId)
|
|
2097
|
+
|
|
2098
|
+
await pause(50)
|
|
2099
|
+
|
|
2100
|
+
return { alice, bob }
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
it("should emit events for receive sync message", async () => {
|
|
2104
|
+
const { alice, bob } = await setup()
|
|
2105
|
+
|
|
2106
|
+
const bobEvents: DocMetrics[] = []
|
|
2107
|
+
bob.on("doc-metrics", e => {
|
|
2108
|
+
if (e.type === "receive-sync-message") {
|
|
2109
|
+
bobEvents.push(e)
|
|
2110
|
+
}
|
|
2111
|
+
})
|
|
2112
|
+
|
|
2113
|
+
const handle = await alice.create2({ foo: "bar" })
|
|
2114
|
+
const bobHandle = await bob.find(handle.url)
|
|
2115
|
+
|
|
2116
|
+
assert.notEqual(bobEvents.length, 0)
|
|
2117
|
+
assert(
|
|
2118
|
+
bobEvents.every(
|
|
2119
|
+
e =>
|
|
2120
|
+
e.type === "receive-sync-message" &&
|
|
2121
|
+
e.documentId == handle.documentId &&
|
|
2122
|
+
e.durationMillis > 0
|
|
2123
|
+
)
|
|
2124
|
+
)
|
|
2125
|
+
|
|
2126
|
+
await Promise.all([bob.shutdown(), alice.shutdown()])
|
|
2127
|
+
})
|
|
2128
|
+
|
|
2129
|
+
it("should emit events for generate sync message", async () => {
|
|
2130
|
+
const { alice, bob } = await setup()
|
|
2131
|
+
|
|
2132
|
+
const bobEvents: DocMetrics[] = []
|
|
2133
|
+
bob.on("doc-metrics", e => {
|
|
2134
|
+
if (e.type === "generate-sync-message") {
|
|
2135
|
+
bobEvents.push(e)
|
|
2136
|
+
}
|
|
2137
|
+
})
|
|
2138
|
+
|
|
2139
|
+
const handle = await alice.create2({ foo: "bar" })
|
|
2140
|
+
const bobHandle = await bob.find(handle.url)
|
|
2141
|
+
|
|
2142
|
+
assert.notEqual(bobEvents.length, 0)
|
|
2143
|
+
assert(
|
|
2144
|
+
bobEvents.every(
|
|
2145
|
+
e =>
|
|
2146
|
+
e.type === "generate-sync-message" &&
|
|
2147
|
+
e.documentId == handle.documentId &&
|
|
2148
|
+
e.durationMillis > 0
|
|
2149
|
+
)
|
|
2150
|
+
)
|
|
2151
|
+
|
|
2152
|
+
await Promise.all([bob.shutdown(), alice.shutdown()])
|
|
2153
|
+
})
|
|
2154
|
+
|
|
2155
|
+
it("should emit events on compaction", async () => {
|
|
2156
|
+
const bob = new Repo({ storage: new DummyStorageAdapter() })
|
|
2157
|
+
// Create a doc and change it enough times to trigger compaction
|
|
2158
|
+
const doc = bob.create({ foo: "bar" })
|
|
2159
|
+
|
|
2160
|
+
const events: DocMetrics[] = []
|
|
2161
|
+
bob.on("doc-metrics", e => {
|
|
2162
|
+
if (e.type === "doc-compacted") {
|
|
2163
|
+
events.push(e)
|
|
2164
|
+
}
|
|
2165
|
+
})
|
|
2166
|
+
|
|
2167
|
+
for (let i = 0; i < 1000; i++) {
|
|
2168
|
+
doc.change(d => {
|
|
2169
|
+
A.splice(d, ["foo"], 0, 1, `${i}`)
|
|
2170
|
+
})
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
await pause(50)
|
|
2174
|
+
|
|
2175
|
+
assert.notEqual(events.length, 0)
|
|
2176
|
+
assert(
|
|
2177
|
+
events.every(
|
|
2178
|
+
e =>
|
|
2179
|
+
e.type === "doc-compacted" &&
|
|
2180
|
+
e.documentId == doc.documentId &&
|
|
2181
|
+
e.durationMillis > 0
|
|
2182
|
+
)
|
|
2183
|
+
)
|
|
2184
|
+
|
|
2185
|
+
await bob.shutdown()
|
|
2186
|
+
})
|
|
2187
|
+
|
|
2188
|
+
it("should emit events on save since", async () => {
|
|
2189
|
+
const bob = new Repo({
|
|
2190
|
+
storage: new DummyStorageAdapter(),
|
|
2191
|
+
saveDebounceRate: 10,
|
|
2192
|
+
})
|
|
2193
|
+
|
|
2194
|
+
const events: DocMetrics[] = []
|
|
2195
|
+
bob.on("doc-metrics", e => {
|
|
2196
|
+
console.log("event: ", e)
|
|
2197
|
+
if (e.type === "doc-saved") {
|
|
2198
|
+
events.push(e)
|
|
2199
|
+
}
|
|
2200
|
+
})
|
|
2201
|
+
|
|
2202
|
+
const doc = bob.create({ foo: "bar" })
|
|
2203
|
+
|
|
2204
|
+
// We have to save, then pause, then save again in order to trigger the
|
|
2205
|
+
// initial compaction and then get to the point where the save actually
|
|
2206
|
+
// triggers incremental saves rather than compactions. This is because the
|
|
2207
|
+
// logic in the storage adapter is designed to initially compact on every
|
|
2208
|
+
// change and only start incremental saves as the document gets a little
|
|
2209
|
+
// larger.
|
|
2210
|
+
|
|
2211
|
+
// First create enough changes to get past the "always compact" threshold
|
|
2212
|
+
for (let i = 0; i < 1000; i++) {
|
|
2213
|
+
doc.change(d => {
|
|
2214
|
+
A.splice(d, ["foo"], 0, 1, `${i}`)
|
|
2215
|
+
})
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// Wait for the debounced save routine to finish
|
|
2219
|
+
await pause(20)
|
|
2220
|
+
|
|
2221
|
+
// Now trigger some changes which will cause incremental saves
|
|
2222
|
+
for (let i = 0; i < 10; i++) {
|
|
2223
|
+
doc.change(d => {
|
|
2224
|
+
A.splice(d, ["foo"], 0, 1, `${i}`)
|
|
2225
|
+
})
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
// Wait for the debounced save routine again
|
|
2229
|
+
await pause(20)
|
|
2230
|
+
|
|
2231
|
+
// Now actually test the events we got
|
|
2232
|
+
assert.notEqual(events.length, 0)
|
|
2233
|
+
assert(
|
|
2234
|
+
events.every(
|
|
2235
|
+
e =>
|
|
2236
|
+
e.type === "doc-saved" &&
|
|
2237
|
+
e.documentId == doc.documentId &&
|
|
2238
|
+
e.durationMillis > 0 &&
|
|
2239
|
+
A.hasHeads(doc.doc(), e.sinceHeads)
|
|
2240
|
+
)
|
|
2241
|
+
)
|
|
2242
|
+
|
|
2243
|
+
await bob.shutdown()
|
|
2244
|
+
})
|
|
2245
|
+
})
|
|
2074
2246
|
})
|
|
2075
2247
|
|
|
2076
2248
|
const warn = console.warn
|