@automerge/automerge-repo 2.0.0-alpha.23 → 2.0.0-alpha.27
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/AutomergeUrl.d.ts.map +1 -1
- package/dist/DocHandle.d.ts +4 -3
- package/dist/DocHandle.d.ts.map +1 -1
- package/dist/DocHandle.js +15 -3
- package/dist/Repo.d.ts +11 -1
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +121 -71
- package/dist/helpers/arraysAreEqual.d.ts.map +1 -1
- package/dist/helpers/bufferFromHex.d.ts.map +1 -1
- package/dist/helpers/debounce.d.ts.map +1 -1
- package/dist/helpers/eventPromise.d.ts.map +1 -1
- package/dist/helpers/headsAreSame.d.ts.map +1 -1
- package/dist/helpers/pause.d.ts.map +1 -1
- package/dist/helpers/throttle.d.ts.map +1 -1
- package/dist/helpers/withTimeout.d.ts.map +1 -1
- package/dist/network/messages.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.d.ts +4 -0
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +38 -12
- package/package.json +2 -2
- package/src/DocHandle.ts +20 -3
- package/src/Repo.ts +154 -75
- package/src/storage/StorageSubsystem.ts +44 -14
- package/test/DocHandle.test.ts +67 -0
- package/test/Repo.test.ts +35 -0
- package/test/StorageSubsystem.test.ts +80 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AutomergeUrl.d.ts","sourceRoot":"","sources":["../src/AutomergeUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,QAAQ,EACT,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAExE,eAAO,MAAM,SAAS,eAAe,CAAA;AAErC,UAAU,kBAAkB;IAC1B,2BAA2B;IAC3B,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,8BAA8B;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sGAAsG;AACtG,eAAO,MAAM,iBAAiB,
|
|
1
|
+
{"version":3,"file":"AutomergeUrl.d.ts","sourceRoot":"","sources":["../src/AutomergeUrl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,QAAQ,EACT,MAAM,YAAY,CAAA;AASnB,OAAO,KAAK,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAExE,eAAO,MAAM,SAAS,eAAe,CAAA;AAErC,UAAU,kBAAkB;IAC1B,2BAA2B;IAC3B,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,8BAA8B;IAC9B,UAAU,EAAE,UAAU,CAAA;IACtB,mDAAmD;IACnD,KAAK,CAAC,EAAE,QAAQ,CAAA;IAChB,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,sGAAsG;AACtG,eAAO,MAAM,iBAAiB,GAAI,KAAK,YAAY,KAAG,kBAsBrD,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,qBAAqB,GAChC,KAAK,UAAU,GAAG,UAAU,GAAG,gBAAgB,KAC9C,YAgCF,CAAA;AAED,gEAAgE;AAChE,eAAO,MAAM,eAAe,GAAI,KAAK,YAAY,KAAG,MAAM,EAAE,GAAG,SAG9D,CAAA;AAED,eAAO,MAAM,2BAA2B,GAAI,IAAI,aAAa,6BAO9C,CAAA;AAEf;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,YAsBzD,CAAA;AAED,eAAO,MAAM,iBAAiB,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,UASvD,CAAA;AAED,eAAO,MAAM,WAAW,GAAI,KAAK,OAAO,KAAG,GAAG,IAAI,gBACH,CAAA;AAE/C;;GAEG;AACH,eAAO,MAAM,oBAAoB,QAAO,YAGvC,CAAA;AAED,eAAO,MAAM,kBAAkB,GAAI,OAAO,UAAU,KACjB,gBAAgB,GAAG,SAAS,CAAA;AAE/D,eAAO,MAAM,kBAAkB,GAAI,OAAO,gBAAgB,KAC7B,UAAU,CAAA;AAEvC,eAAO,MAAM,WAAW,GAAI,OAAO,cAAc,KAAG,QACsB,CAAA;AAE1E,eAAO,MAAM,WAAW,GAAI,OAAO,QAAQ,KAAG,cACgC,CAAA;AAE9E,eAAO,MAAM,eAAe,GAAI,KAAK,MAAM,6BAI1C,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,qBAAqB,GAAI,IAAI,aAAa,eAqBtD,CAAA;AAID,KAAK,UAAU,GAAG;IAChB,UAAU,EAAE,UAAU,GAAG,gBAAgB,CAAA;IACzC,KAAK,CAAC,EAAE,QAAQ,CAAA;CACjB,CAAA"}
|
package/dist/DocHandle.d.ts
CHANGED
|
@@ -150,7 +150,7 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
150
150
|
*/
|
|
151
151
|
doneLoading(): void;
|
|
152
152
|
/**
|
|
153
|
-
* Called by the repo
|
|
153
|
+
* Called by the repo when a doc handle changes or we receive new remote heads.
|
|
154
154
|
* @hidden
|
|
155
155
|
*/
|
|
156
156
|
setRemoteHeads(storageId: StorageId, heads: UrlHeads): void;
|
|
@@ -190,11 +190,12 @@ export declare class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
190
190
|
/** the handle of the document to merge into this one */
|
|
191
191
|
otherHandle: DocHandle<T>): void;
|
|
192
192
|
/**
|
|
193
|
-
*
|
|
193
|
+
* Updates the internal state machine to mark the document unavailable.
|
|
194
194
|
* @hidden
|
|
195
195
|
*/
|
|
196
196
|
unavailable(): void;
|
|
197
|
-
/**
|
|
197
|
+
/**
|
|
198
|
+
* Called by the repo either when the document is not found in storage.
|
|
198
199
|
* @hidden
|
|
199
200
|
* */
|
|
200
201
|
request(): void;
|
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;AAU5C,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C;;;;;;;;;;;;GAYG;AACH,qBAAa,SAAS,CAAC,CAAC,CAAE,SAAQ,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;;
|
|
1
|
+
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"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"}
|
package/dist/DocHandle.js
CHANGED
|
@@ -33,6 +33,8 @@ export class DocHandle extends EventEmitter {
|
|
|
33
33
|
#timeoutDelay = 60_000;
|
|
34
34
|
/** A dictionary mapping each peer to the last heads we know they have. */
|
|
35
35
|
#remoteHeads = {};
|
|
36
|
+
/** Cache for view handles, keyed by the stringified heads */
|
|
37
|
+
#viewCache = new Map();
|
|
36
38
|
/** @hidden */
|
|
37
39
|
constructor(documentId, options = {}) {
|
|
38
40
|
super();
|
|
@@ -302,6 +304,13 @@ export class DocHandle extends EventEmitter {
|
|
|
302
304
|
if (!this.isReady()) {
|
|
303
305
|
throw new Error(`DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before calling view().`);
|
|
304
306
|
}
|
|
307
|
+
// Create a cache key from the heads
|
|
308
|
+
const cacheKey = JSON.stringify(heads);
|
|
309
|
+
// Check if we have a cached handle for these heads
|
|
310
|
+
const cachedHandle = this.#viewCache.get(cacheKey);
|
|
311
|
+
if (cachedHandle) {
|
|
312
|
+
return cachedHandle;
|
|
313
|
+
}
|
|
305
314
|
// Create a new handle with the same documentId but fixed heads
|
|
306
315
|
const handle = new DocHandle(this.documentId, {
|
|
307
316
|
heads,
|
|
@@ -309,6 +318,8 @@ export class DocHandle extends EventEmitter {
|
|
|
309
318
|
});
|
|
310
319
|
handle.update(() => A.clone(this.#doc));
|
|
311
320
|
handle.doneLoading();
|
|
321
|
+
// Store in cache
|
|
322
|
+
this.#viewCache.set(cacheKey, handle);
|
|
312
323
|
return handle;
|
|
313
324
|
}
|
|
314
325
|
/**
|
|
@@ -389,7 +400,7 @@ export class DocHandle extends EventEmitter {
|
|
|
389
400
|
this.#machine.send({ type: DOC_READY });
|
|
390
401
|
}
|
|
391
402
|
/**
|
|
392
|
-
* Called by the repo
|
|
403
|
+
* Called by the repo when a doc handle changes or we receive new remote heads.
|
|
393
404
|
* @hidden
|
|
394
405
|
*/
|
|
395
406
|
setRemoteHeads(storageId, heads) {
|
|
@@ -478,13 +489,14 @@ export class DocHandle extends EventEmitter {
|
|
|
478
489
|
});
|
|
479
490
|
}
|
|
480
491
|
/**
|
|
481
|
-
*
|
|
492
|
+
* Updates the internal state machine to mark the document unavailable.
|
|
482
493
|
* @hidden
|
|
483
494
|
*/
|
|
484
495
|
unavailable() {
|
|
485
496
|
this.#machine.send({ type: DOC_UNAVAILABLE });
|
|
486
497
|
}
|
|
487
|
-
/**
|
|
498
|
+
/**
|
|
499
|
+
* Called by the repo either when the document is not found in storage.
|
|
488
500
|
* @hidden
|
|
489
501
|
* */
|
|
490
502
|
request() {
|
package/dist/Repo.d.ts
CHANGED
|
@@ -9,7 +9,17 @@ import { CollectionSynchronizer } from "./synchronizer/CollectionSynchronizer.js
|
|
|
9
9
|
import { DocSyncMetrics } from "./synchronizer/Synchronizer.js";
|
|
10
10
|
import type { AnyDocumentId, AutomergeUrl, DocumentId, PeerId } from "./types.js";
|
|
11
11
|
import { AbortOptions } from "./helpers/abortable.js";
|
|
12
|
-
import { FindProgress
|
|
12
|
+
import { FindProgress } from "./FindProgress.js";
|
|
13
|
+
export type FindProgressWithMethods<T> = FindProgress<T> & {
|
|
14
|
+
untilReady: (allowableStates: string[]) => Promise<DocHandle<T>>;
|
|
15
|
+
peek: () => FindProgress<T>;
|
|
16
|
+
subscribe: (callback: (progress: FindProgress<T>) => void) => () => void;
|
|
17
|
+
};
|
|
18
|
+
export type ProgressSignal<T> = {
|
|
19
|
+
peek: () => FindProgress<T>;
|
|
20
|
+
subscribe: (callback: (progress: FindProgress<T>) => void) => () => void;
|
|
21
|
+
untilReady: (allowableStates: string[]) => Promise<DocHandle<T>>;
|
|
22
|
+
};
|
|
13
23
|
/** A Repo is a collection of documents with networking, syncing, and storage capabilities. */
|
|
14
24
|
/** The `Repo` is the main entry point of this library
|
|
15
25
|
*
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAEL,SAAS,EAKV,MAAM,gBAAgB,CAAA;AAIvB,OAAO,EACL,uBAAuB,EACvB,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,cAAc,EAEf,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,MAAM,EACP,MAAM,YAAY,CAAA;AACnB,OAAO,EAAa,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAQ5C,OAAO,EAEL,SAAS,EAKV,MAAM,gBAAgB,CAAA;AAIvB,OAAO,EACL,uBAAuB,EACvB,KAAK,YAAY,EAClB,MAAM,sCAAsC,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAEhE,OAAO,EAAE,uBAAuB,EAAE,MAAM,sCAAsC,CAAA;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC9C,OAAO,EAAE,sBAAsB,EAAE,MAAM,0CAA0C,CAAA;AACjF,OAAO,EACL,cAAc,EAEf,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EACZ,UAAU,EACV,MAAM,EACP,MAAM,YAAY,CAAA;AACnB,OAAO,EAAa,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,MAAM,MAAM,uBAAuB,CAAC,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,GAAG;IACzD,UAAU,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;IAChE,IAAI,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,CAAA;IAC3B,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;CACzE,CAAA;AAED,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI;IAC9B,IAAI,EAAE,MAAM,YAAY,CAAC,CAAC,CAAC,CAAA;IAC3B,SAAS,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;IACxE,UAAU,EAAE,CAAC,eAAe,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;CACjE,CAAA;AAMD,8FAA8F;AAC9F;;;;;;GAMG;AACH,qBAAa,IAAK,SAAQ,YAAY,CAAC,UAAU,CAAC;;IAGhD,cAAc;IACd,gBAAgB,EAAE,gBAAgB,CAAA;IAClC,cAAc;IACd,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IAEnC,mDAAmD;IACnD,cAAc;IACd,gBAAgB,SAAM;IAItB,cAAc;IACd,YAAY,EAAE,sBAAsB,CAAA;IAEpC,sDAAsD;IACtD,cAAc;IACd,WAAW,EAAE,WAAW,CAAmB;IAE3C,8GAA8G;IAC9G,cAAc;IACd,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAK;gBAM3C,EACV,OAAO,EACP,OAAY,EACZ,MAAuB,EACvB,WAAW,EACX,WAAmC,EACnC,0BAAkC,EAClC,QAAa,GACd,GAAE,UAAe;IA0PlB,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;;;;;;;;;;;;OAYG;IACH,KAAK,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;IAmBnC,gBAAgB,CAAC,CAAC,EAChB,EAAE,EAAE,aAAa,EACjB,OAAO,GAAE,YAAiB,GACzB,uBAAuB,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;IAkKzC,IAAI,CAAC,CAAC,EACV,EAAE,EAAE,aAAa,EACjB,OAAO,GAAE,eAAe,GAAG,YAAiB,GAC3C,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IA8DxB;;;OAGG;IACG,WAAW,CAAC,CAAC;IACjB,sDAAsD;IACtD,EAAE,EAAE,aAAa,EACjB,OAAO,GAAE,eAAe,GAAG,YAAiB,GAC3C,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAmBxB,MAAM;IACJ,oDAAoD;IACpD,EAAE,EAAE,aAAa;IAYnB;;;;;;OAMG;IACG,MAAM,CAAC,EAAE,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAQhE;;;OAGG;IACH,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU;IAY5B,kBAAkB,GAAI,SAAS,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;IAcpD;;;;;OAKG;IACG,eAAe,CAAC,UAAU,EAAE,UAAU;IA6B5C,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;IAEpC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;CAC1B;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,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B;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,GACD;IACE,IAAI,EAAE,YAAY,CAAA;IAClB,UAAU,EAAE,UAAU,CAAA;CACvB,CAAA"}
|
package/dist/Repo.js
CHANGED
|
@@ -41,6 +41,7 @@ export class Repo extends EventEmitter {
|
|
|
41
41
|
peerMetadataByPeerId = {};
|
|
42
42
|
#remoteHeadsSubscriptions = new RemoteHeadsSubscriptions();
|
|
43
43
|
#remoteHeadsGossipingEnabled = false;
|
|
44
|
+
#progressCache = {};
|
|
44
45
|
constructor({ storage, network = [], peerId = randomPeerId(), sharePolicy, isEphemeral = storage === undefined, enableRemoteHeadsGossiping = false, denylist = [], } = {}) {
|
|
45
46
|
super();
|
|
46
47
|
this.#remoteHeadsGossipingEnabled = enableRemoteHeadsGossiping;
|
|
@@ -293,7 +294,7 @@ export class Repo extends EventEmitter {
|
|
|
293
294
|
const { documentId, heads } = isValidAutomergeUrl(id)
|
|
294
295
|
? parseAutomergeUrl(id)
|
|
295
296
|
: { documentId: interpretAsDocumentId(id), heads: undefined };
|
|
296
|
-
// Check cache first - return plain FindStep for terminal states
|
|
297
|
+
// Check handle cache first - return plain FindStep for terminal states
|
|
297
298
|
if (this.#handleCache[documentId]) {
|
|
298
299
|
const handle = this.#handleCache[documentId];
|
|
299
300
|
if (handle.state === UNAVAILABLE) {
|
|
@@ -305,99 +306,147 @@ export class Repo extends EventEmitter {
|
|
|
305
306
|
return result;
|
|
306
307
|
}
|
|
307
308
|
if (handle.state === DELETED) {
|
|
308
|
-
|
|
309
|
+
const result = {
|
|
309
310
|
state: "failed",
|
|
310
311
|
error: new Error(`Document ${id} was deleted`),
|
|
311
312
|
handle,
|
|
312
313
|
};
|
|
314
|
+
return result;
|
|
313
315
|
}
|
|
314
316
|
if (handle.state === READY) {
|
|
315
|
-
|
|
316
|
-
return {
|
|
317
|
+
const result = {
|
|
317
318
|
state: "ready",
|
|
318
|
-
// TODO: this handle needs to be cached (or at least avoid running clone)
|
|
319
319
|
handle: heads ? handle.view(heads) : handle,
|
|
320
320
|
};
|
|
321
|
+
return result;
|
|
321
322
|
}
|
|
322
323
|
}
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
if (loadedDoc) {
|
|
335
|
-
handle.update(() => loadedDoc);
|
|
336
|
-
handle.doneLoading();
|
|
337
|
-
yield { state: "loading", progress: 50, handle };
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
await Promise.race([that.networkSubsystem.whenReady(), abortPromise]);
|
|
341
|
-
handle.request();
|
|
342
|
-
yield { state: "loading", progress: 75, handle };
|
|
343
|
-
}
|
|
344
|
-
that.#registerHandleWithSubsystems(handle);
|
|
345
|
-
await Promise.race([
|
|
346
|
-
handle.whenReady([READY, UNAVAILABLE]),
|
|
347
|
-
abortPromise,
|
|
348
|
-
]);
|
|
349
|
-
if (handle.state === UNAVAILABLE) {
|
|
350
|
-
yield { state: "unavailable", handle };
|
|
351
|
-
}
|
|
352
|
-
if (handle.state === DELETED) {
|
|
353
|
-
throw new Error(`Document ${id} was deleted`);
|
|
354
|
-
}
|
|
355
|
-
yield { state: "ready", handle };
|
|
324
|
+
// Check progress cache for any existing signal
|
|
325
|
+
const cachedProgress = this.#progressCache[documentId];
|
|
326
|
+
if (cachedProgress) {
|
|
327
|
+
const handle = this.#handleCache[documentId];
|
|
328
|
+
// Return cached progress if we have a handle and it's either in a terminal state or loading
|
|
329
|
+
if (handle &&
|
|
330
|
+
(handle.state === READY ||
|
|
331
|
+
handle.state === UNAVAILABLE ||
|
|
332
|
+
handle.state === DELETED ||
|
|
333
|
+
handle.state === "loading")) {
|
|
334
|
+
return cachedProgress;
|
|
356
335
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
336
|
+
}
|
|
337
|
+
const handle = this.#getHandle({ documentId });
|
|
338
|
+
const initial = {
|
|
339
|
+
state: "loading",
|
|
340
|
+
progress: 0,
|
|
341
|
+
handle,
|
|
342
|
+
};
|
|
343
|
+
// Create a new progress signal
|
|
344
|
+
const progressSignal = {
|
|
345
|
+
subscribers: new Set(),
|
|
346
|
+
currentProgress: undefined,
|
|
347
|
+
notify: (progress) => {
|
|
348
|
+
progressSignal.currentProgress = progress;
|
|
349
|
+
progressSignal.subscribers.forEach(callback => callback(progress));
|
|
350
|
+
// Cache all states, not just terminal ones
|
|
351
|
+
this.#progressCache[documentId] = progress;
|
|
352
|
+
},
|
|
353
|
+
peek: () => progressSignal.currentProgress || initial,
|
|
354
|
+
subscribe: (callback) => {
|
|
355
|
+
progressSignal.subscribers.add(callback);
|
|
356
|
+
return () => progressSignal.subscribers.delete(callback);
|
|
357
|
+
},
|
|
358
|
+
};
|
|
359
|
+
progressSignal.notify(initial);
|
|
360
|
+
// Start the loading process
|
|
361
|
+
void this.#loadDocumentWithProgress(id, documentId, handle, progressSignal, abortPromise);
|
|
362
|
+
const result = {
|
|
363
|
+
...initial,
|
|
364
|
+
peek: progressSignal.peek,
|
|
365
|
+
subscribe: progressSignal.subscribe,
|
|
366
|
+
};
|
|
367
|
+
this.#progressCache[documentId] = result;
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
async #loadDocumentWithProgress(id, documentId, handle, progressSignal, abortPromise) {
|
|
371
|
+
try {
|
|
372
|
+
progressSignal.notify({
|
|
373
|
+
state: "loading",
|
|
374
|
+
progress: 25,
|
|
375
|
+
handle,
|
|
376
|
+
});
|
|
377
|
+
const loadingPromise = await (this.storageSubsystem
|
|
378
|
+
? this.storageSubsystem.loadDoc(handle.documentId)
|
|
379
|
+
: Promise.resolve(null));
|
|
380
|
+
const loadedDoc = await Promise.race([loadingPromise, abortPromise]);
|
|
381
|
+
if (loadedDoc) {
|
|
382
|
+
handle.update(() => loadedDoc);
|
|
383
|
+
handle.doneLoading();
|
|
384
|
+
progressSignal.notify({
|
|
385
|
+
state: "loading",
|
|
386
|
+
progress: 50,
|
|
387
|
+
handle,
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
await Promise.race([this.networkSubsystem.whenReady(), abortPromise]);
|
|
392
|
+
handle.request();
|
|
393
|
+
progressSignal.notify({
|
|
394
|
+
state: "loading",
|
|
395
|
+
progress: 75,
|
|
396
|
+
handle,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
this.#registerHandleWithSubsystems(handle);
|
|
400
|
+
await Promise.race([handle.whenReady([READY, UNAVAILABLE]), abortPromise]);
|
|
401
|
+
if (handle.state === UNAVAILABLE) {
|
|
402
|
+
const unavailableProgress = {
|
|
403
|
+
state: "unavailable",
|
|
361
404
|
handle,
|
|
362
405
|
};
|
|
406
|
+
progressSignal.notify(unavailableProgress);
|
|
407
|
+
return;
|
|
363
408
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
const next = async () => {
|
|
367
|
-
const result = await iterator.next();
|
|
368
|
-
return { ...result.value, next };
|
|
369
|
-
};
|
|
370
|
-
const untilReady = async (allowableStates) => {
|
|
371
|
-
for await (const state of iterator) {
|
|
372
|
-
if (allowableStates.includes(state.handle.state)) {
|
|
373
|
-
return state.handle;
|
|
374
|
-
}
|
|
375
|
-
if (state.state === "unavailable") {
|
|
376
|
-
throw new Error(`Document ${id} is unavailable`);
|
|
377
|
-
}
|
|
378
|
-
if (state.state === "ready")
|
|
379
|
-
return state.handle;
|
|
380
|
-
if (state.state === "failed")
|
|
381
|
-
throw state.error;
|
|
409
|
+
if (handle.state === DELETED) {
|
|
410
|
+
throw new Error(`Document ${id} was deleted`);
|
|
382
411
|
}
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
412
|
+
progressSignal.notify({ state: "ready", handle });
|
|
413
|
+
}
|
|
414
|
+
catch (error) {
|
|
415
|
+
progressSignal.notify({
|
|
416
|
+
state: "failed",
|
|
417
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
418
|
+
handle: this.#getHandle({ documentId }),
|
|
419
|
+
});
|
|
420
|
+
}
|
|
388
421
|
}
|
|
389
422
|
async find(id, options = {}) {
|
|
390
423
|
const { allowableStates = ["ready"], signal } = options;
|
|
391
424
|
const progress = this.findWithProgress(id, { signal });
|
|
392
|
-
|
|
393
|
-
console.log("returning early")
|
|
394
|
-
return progress.handle
|
|
395
|
-
}*/
|
|
396
|
-
if ("untilReady" in progress) {
|
|
425
|
+
if ("subscribe" in progress) {
|
|
397
426
|
this.#registerHandleWithSubsystems(progress.handle);
|
|
398
|
-
return
|
|
427
|
+
return new Promise((resolve, reject) => {
|
|
428
|
+
const unsubscribe = progress.subscribe(state => {
|
|
429
|
+
if (allowableStates.includes(state.handle.state)) {
|
|
430
|
+
unsubscribe();
|
|
431
|
+
resolve(state.handle);
|
|
432
|
+
}
|
|
433
|
+
else if (state.state === "unavailable") {
|
|
434
|
+
unsubscribe();
|
|
435
|
+
reject(new Error(`Document ${id} is unavailable`));
|
|
436
|
+
}
|
|
437
|
+
else if (state.state === "failed") {
|
|
438
|
+
unsubscribe();
|
|
439
|
+
reject(state.error);
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
});
|
|
399
443
|
}
|
|
400
444
|
else {
|
|
445
|
+
if (progress.handle.state === READY) {
|
|
446
|
+
return progress.handle;
|
|
447
|
+
}
|
|
448
|
+
// If the handle isn't ready, wait for it and then return it
|
|
449
|
+
await progress.handle.whenReady([READY, UNAVAILABLE]);
|
|
401
450
|
return progress.handle;
|
|
402
451
|
}
|
|
403
452
|
}
|
|
@@ -460,6 +509,7 @@ export class Repo extends EventEmitter {
|
|
|
460
509
|
const handle = this.#getHandle({ documentId });
|
|
461
510
|
handle.delete();
|
|
462
511
|
delete this.#handleCache[documentId];
|
|
512
|
+
delete this.#progressCache[documentId];
|
|
463
513
|
this.emit("delete-document", { documentId });
|
|
464
514
|
}
|
|
465
515
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"arraysAreEqual.d.ts","sourceRoot":"","sources":["../../src/helpers/arraysAreEqual.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,GAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"arraysAreEqual.d.ts","sourceRoot":"","sources":["../../src/helpers/arraysAreEqual.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,GAAI,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,GAAG,CAAC,EAAE,YAC4B,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bufferFromHex.d.ts","sourceRoot":"","sources":["../../src/helpers/bufferFromHex.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,
|
|
1
|
+
{"version":3,"file":"bufferFromHex.d.ts","sourceRoot":"","sources":["../../src/helpers/bufferFromHex.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,GAAI,WAAW,MAAM,KAAG,UAS3D,CAAA;AAED,eAAO,MAAM,qBAAqB,GAAI,MAAM,UAAU,KAAG,MAExD,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../../src/helpers/debounce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"debounce.d.ts","sourceRoot":"","sources":["../../src/helpers/debounce.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAC1E,IAAI,CAAC,EACL,MAAM,MAAM,MAGK,GAAG,MAAM,UAAU,CAAC,CAAC,CAAC,SAMxC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"eventPromise.d.ts","sourceRoot":"","sources":["../../src/helpers/eventPromise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,4FAA4F;AAC5F,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"eventPromise.d.ts","sourceRoot":"","sources":["../../src/helpers/eventPromise.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAE5C,4FAA4F;AAC5F,eAAO,MAAM,YAAY,GAAI,SAAS,YAAY,EAAE,OAAO,MAAM,iBACE,CAAA;AAEnE,eAAO,MAAM,aAAa,GAAI,UAAU,YAAY,EAAE,EAAE,OAAO,MAAM,mBAGpE,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headsAreSame.d.ts","sourceRoot":"","sources":["../../src/helpers/headsAreSame.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,eAAO,MAAM,YAAY,
|
|
1
|
+
{"version":3,"file":"headsAreSame.d.ts","sourceRoot":"","sources":["../../src/helpers/headsAreSame.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,eAAO,MAAM,YAAY,GAAI,GAAG,QAAQ,EAAE,GAAG,QAAQ,YAEpD,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pause.d.ts","sourceRoot":"","sources":["../../src/helpers/pause.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK
|
|
1
|
+
{"version":3,"file":"pause.d.ts","sourceRoot":"","sources":["../../src/helpers/pause.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,KAAK,GAAI,UAAK,kBACmC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/helpers/throttle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"throttle.d.ts","sourceRoot":"","sources":["../../src/helpers/throttle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,eAAO,MAAM,QAAQ,GAAI,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,KAAK,UAAU,CAAC,CAAC,CAAC,EAC1E,IAAI,CAAC,EACL,OAAO,MAAM,MAKI,GAAG,MAAM,UAAU,CAAC,CAAC,CAAC,SAQxC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/helpers/withTimeout.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,
|
|
1
|
+
{"version":3,"file":"withTimeout.d.ts","sourceRoot":"","sources":["../../src/helpers/withTimeout.ts"],"names":[],"mappings":"AACA;;;GAGG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,EACjC,SAAS,OAAO,CAAC,CAAC,CAAC,EACnB,GAAG,MAAM,KACR,OAAO,CAAC,CAAC,CAaX,CAAA;AAED,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../src/network/messages.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,6 +39,10 @@ export declare class StorageSubsystem extends EventEmitter<StorageSubsystemEvent
|
|
|
39
39
|
namespace: string,
|
|
40
40
|
/** Key to remove. Typically a UUID or other unique identifier, but could be any string. */
|
|
41
41
|
key: string): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* Loads and combines document chunks from storage, with snapshots first.
|
|
44
|
+
*/
|
|
45
|
+
loadDocData(documentId: DocumentId): Promise<Uint8Array | null>;
|
|
42
46
|
/**
|
|
43
47
|
* Loads the Automerge document with the given ID from storage.
|
|
44
48
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAInD,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gCAAgC,CAAA;AAInD,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;IAkEhC,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"}
|
|
@@ -3,7 +3,6 @@ import debug from "debug";
|
|
|
3
3
|
import { headsAreSame } from "../helpers/headsAreSame.js";
|
|
4
4
|
import { mergeArrays } from "../helpers/mergeArrays.js";
|
|
5
5
|
import { keyHash, headsHash } from "./keyHash.js";
|
|
6
|
-
import { chunkTypeFromKey } from "./chunkTypeFromKey.js";
|
|
7
6
|
import * as Uuid from "uuid";
|
|
8
7
|
import { EventEmitter } from "eventemitter3";
|
|
9
8
|
import { encodeHeads } from "../AutomergeUrl.js";
|
|
@@ -76,31 +75,58 @@ export class StorageSubsystem extends EventEmitter {
|
|
|
76
75
|
}
|
|
77
76
|
// AUTOMERGE DOCUMENT STORAGE
|
|
78
77
|
/**
|
|
79
|
-
* Loads
|
|
78
|
+
* Loads and combines document chunks from storage, with snapshots first.
|
|
80
79
|
*/
|
|
81
|
-
async
|
|
82
|
-
// Load
|
|
83
|
-
const
|
|
80
|
+
async loadDocData(documentId) {
|
|
81
|
+
// Load snapshots first
|
|
82
|
+
const snapshotChunks = await this.#storageAdapter.loadRange([
|
|
83
|
+
documentId,
|
|
84
|
+
"snapshot",
|
|
85
|
+
]);
|
|
86
|
+
const incrementalChunks = await this.#storageAdapter.loadRange([
|
|
87
|
+
documentId,
|
|
88
|
+
"incremental",
|
|
89
|
+
]);
|
|
84
90
|
const binaries = [];
|
|
85
91
|
const chunkInfos = [];
|
|
86
|
-
|
|
87
|
-
|
|
92
|
+
// Process snapshots first
|
|
93
|
+
for (const chunk of snapshotChunks) {
|
|
88
94
|
if (chunk.data === undefined)
|
|
89
95
|
continue;
|
|
90
|
-
|
|
91
|
-
|
|
96
|
+
chunkInfos.push({
|
|
97
|
+
key: chunk.key,
|
|
98
|
+
type: "snapshot",
|
|
99
|
+
size: chunk.data.length,
|
|
100
|
+
});
|
|
101
|
+
binaries.push(chunk.data);
|
|
102
|
+
}
|
|
103
|
+
// Then process incrementals
|
|
104
|
+
for (const chunk of incrementalChunks) {
|
|
105
|
+
if (chunk.data === undefined)
|
|
92
106
|
continue;
|
|
93
107
|
chunkInfos.push({
|
|
94
108
|
key: chunk.key,
|
|
95
|
-
type:
|
|
109
|
+
type: "incremental",
|
|
96
110
|
size: chunk.data.length,
|
|
97
111
|
});
|
|
98
112
|
binaries.push(chunk.data);
|
|
99
113
|
}
|
|
114
|
+
// Store chunk infos for future reference
|
|
100
115
|
this.#chunkInfos.set(documentId, chunkInfos);
|
|
116
|
+
// If no chunks were found, return null
|
|
117
|
+
if (binaries.length === 0) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
101
120
|
// Merge the chunks into a single binary
|
|
102
|
-
|
|
103
|
-
|
|
121
|
+
return mergeArrays(binaries);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Loads the Automerge document with the given ID from storage.
|
|
125
|
+
*/
|
|
126
|
+
async loadDoc(documentId) {
|
|
127
|
+
// Load and combine chunks
|
|
128
|
+
const binary = await this.loadDocData(documentId);
|
|
129
|
+
if (!binary)
|
|
104
130
|
return null;
|
|
105
131
|
// Load into an Automerge document
|
|
106
132
|
const start = performance.now();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.27",
|
|
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": "f2e9fbddc93dcc423723b6aa8e6b01d52b5e3f80"
|
|
63
63
|
}
|
package/src/DocHandle.ts
CHANGED
|
@@ -45,6 +45,9 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
45
45
|
/** A dictionary mapping each peer to the last heads we know they have. */
|
|
46
46
|
#remoteHeads: Record<StorageId, UrlHeads> = {}
|
|
47
47
|
|
|
48
|
+
/** Cache for view handles, keyed by the stringified heads */
|
|
49
|
+
#viewCache: Map<string, DocHandle<T>> = new Map()
|
|
50
|
+
|
|
48
51
|
/** @hidden */
|
|
49
52
|
constructor(
|
|
50
53
|
public documentId: DocumentId,
|
|
@@ -359,6 +362,16 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
359
362
|
`DocHandle#${this.documentId} is not ready. Check \`handle.isReady()\` before calling view().`
|
|
360
363
|
)
|
|
361
364
|
}
|
|
365
|
+
|
|
366
|
+
// Create a cache key from the heads
|
|
367
|
+
const cacheKey = JSON.stringify(heads)
|
|
368
|
+
|
|
369
|
+
// Check if we have a cached handle for these heads
|
|
370
|
+
const cachedHandle = this.#viewCache.get(cacheKey)
|
|
371
|
+
if (cachedHandle) {
|
|
372
|
+
return cachedHandle
|
|
373
|
+
}
|
|
374
|
+
|
|
362
375
|
// Create a new handle with the same documentId but fixed heads
|
|
363
376
|
const handle = new DocHandle<T>(this.documentId, {
|
|
364
377
|
heads,
|
|
@@ -367,6 +380,9 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
367
380
|
handle.update(() => A.clone(this.#doc))
|
|
368
381
|
handle.doneLoading()
|
|
369
382
|
|
|
383
|
+
// Store in cache
|
|
384
|
+
this.#viewCache.set(cacheKey, handle)
|
|
385
|
+
|
|
370
386
|
return handle
|
|
371
387
|
}
|
|
372
388
|
|
|
@@ -463,7 +479,7 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
463
479
|
}
|
|
464
480
|
|
|
465
481
|
/**
|
|
466
|
-
* Called by the repo
|
|
482
|
+
* Called by the repo when a doc handle changes or we receive new remote heads.
|
|
467
483
|
* @hidden
|
|
468
484
|
*/
|
|
469
485
|
setRemoteHeads(storageId: StorageId, heads: UrlHeads) {
|
|
@@ -575,14 +591,15 @@ export class DocHandle<T> extends EventEmitter<DocHandleEvents<T>> {
|
|
|
575
591
|
}
|
|
576
592
|
|
|
577
593
|
/**
|
|
578
|
-
*
|
|
594
|
+
* Updates the internal state machine to mark the document unavailable.
|
|
579
595
|
* @hidden
|
|
580
596
|
*/
|
|
581
597
|
unavailable() {
|
|
582
598
|
this.#machine.send({ type: DOC_UNAVAILABLE })
|
|
583
599
|
}
|
|
584
600
|
|
|
585
|
-
/**
|
|
601
|
+
/**
|
|
602
|
+
* Called by the repo either when the document is not found in storage.
|
|
586
603
|
* @hidden
|
|
587
604
|
* */
|
|
588
605
|
request() {
|
package/src/Repo.ts
CHANGED
|
@@ -40,7 +40,19 @@ import type {
|
|
|
40
40
|
PeerId,
|
|
41
41
|
} from "./types.js"
|
|
42
42
|
import { abortable, AbortOptions } from "./helpers/abortable.js"
|
|
43
|
-
import { FindProgress
|
|
43
|
+
import { FindProgress } from "./FindProgress.js"
|
|
44
|
+
|
|
45
|
+
export type FindProgressWithMethods<T> = FindProgress<T> & {
|
|
46
|
+
untilReady: (allowableStates: string[]) => Promise<DocHandle<T>>
|
|
47
|
+
peek: () => FindProgress<T>
|
|
48
|
+
subscribe: (callback: (progress: FindProgress<T>) => void) => () => void
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ProgressSignal<T> = {
|
|
52
|
+
peek: () => FindProgress<T>
|
|
53
|
+
subscribe: (callback: (progress: FindProgress<T>) => void) => () => void
|
|
54
|
+
untilReady: (allowableStates: string[]) => Promise<DocHandle<T>>
|
|
55
|
+
}
|
|
44
56
|
|
|
45
57
|
function randomPeerId() {
|
|
46
58
|
return ("peer-" + Math.random().toString(36).slice(4)) as PeerId
|
|
@@ -81,6 +93,7 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
81
93
|
|
|
82
94
|
#remoteHeadsSubscriptions = new RemoteHeadsSubscriptions()
|
|
83
95
|
#remoteHeadsGossipingEnabled = false
|
|
96
|
+
#progressCache: Record<DocumentId, FindProgress<any>> = {}
|
|
84
97
|
|
|
85
98
|
constructor({
|
|
86
99
|
storage,
|
|
@@ -425,7 +438,7 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
425
438
|
? parseAutomergeUrl(id)
|
|
426
439
|
: { documentId: interpretAsDocumentId(id), heads: undefined }
|
|
427
440
|
|
|
428
|
-
// Check cache first - return plain FindStep for terminal states
|
|
441
|
+
// Check handle cache first - return plain FindStep for terminal states
|
|
429
442
|
if (this.#handleCache[documentId]) {
|
|
430
443
|
const handle = this.#handleCache[documentId]
|
|
431
444
|
if (handle.state === UNAVAILABLE) {
|
|
@@ -437,94 +450,146 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
437
450
|
return result
|
|
438
451
|
}
|
|
439
452
|
if (handle.state === DELETED) {
|
|
440
|
-
|
|
441
|
-
state: "failed",
|
|
453
|
+
const result = {
|
|
454
|
+
state: "failed" as const,
|
|
442
455
|
error: new Error(`Document ${id} was deleted`),
|
|
443
456
|
handle,
|
|
444
457
|
}
|
|
458
|
+
return result
|
|
445
459
|
}
|
|
446
460
|
if (handle.state === READY) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
state: "ready",
|
|
450
|
-
// TODO: this handle needs to be cached (or at least avoid running clone)
|
|
461
|
+
const result = {
|
|
462
|
+
state: "ready" as const,
|
|
451
463
|
handle: heads ? handle.view(heads) : handle,
|
|
452
464
|
}
|
|
465
|
+
return result
|
|
453
466
|
}
|
|
454
467
|
}
|
|
455
468
|
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
handle.update(() => loadedDoc as Automerge.Doc<T>)
|
|
472
|
-
handle.doneLoading()
|
|
473
|
-
yield { state: "loading", progress: 50, handle }
|
|
474
|
-
} else {
|
|
475
|
-
await Promise.race([that.networkSubsystem.whenReady(), abortPromise])
|
|
476
|
-
handle.request()
|
|
477
|
-
yield { state: "loading", progress: 75, handle }
|
|
478
|
-
}
|
|
469
|
+
// Check progress cache for any existing signal
|
|
470
|
+
const cachedProgress = this.#progressCache[documentId]
|
|
471
|
+
if (cachedProgress) {
|
|
472
|
+
const handle = this.#handleCache[documentId]
|
|
473
|
+
// Return cached progress if we have a handle and it's either in a terminal state or loading
|
|
474
|
+
if (
|
|
475
|
+
handle &&
|
|
476
|
+
(handle.state === READY ||
|
|
477
|
+
handle.state === UNAVAILABLE ||
|
|
478
|
+
handle.state === DELETED ||
|
|
479
|
+
handle.state === "loading")
|
|
480
|
+
) {
|
|
481
|
+
return cachedProgress as FindProgressWithMethods<T>
|
|
482
|
+
}
|
|
483
|
+
}
|
|
479
484
|
|
|
480
|
-
|
|
485
|
+
const handle = this.#getHandle<T>({ documentId })
|
|
486
|
+
const initial = {
|
|
487
|
+
state: "loading" as const,
|
|
488
|
+
progress: 0,
|
|
489
|
+
handle,
|
|
490
|
+
}
|
|
481
491
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
492
|
+
// Create a new progress signal
|
|
493
|
+
const progressSignal = {
|
|
494
|
+
subscribers: new Set<(progress: FindProgress<T>) => void>(),
|
|
495
|
+
currentProgress: undefined as FindProgress<T> | undefined,
|
|
496
|
+
notify: (progress: FindProgress<T>) => {
|
|
497
|
+
progressSignal.currentProgress = progress
|
|
498
|
+
progressSignal.subscribers.forEach(callback => callback(progress))
|
|
499
|
+
// Cache all states, not just terminal ones
|
|
500
|
+
this.#progressCache[documentId] = progress
|
|
501
|
+
},
|
|
502
|
+
peek: () => progressSignal.currentProgress || initial,
|
|
503
|
+
subscribe: (callback: (progress: FindProgress<T>) => void) => {
|
|
504
|
+
progressSignal.subscribers.add(callback)
|
|
505
|
+
return () => progressSignal.subscribers.delete(callback)
|
|
506
|
+
},
|
|
507
|
+
}
|
|
486
508
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
509
|
+
progressSignal.notify(initial)
|
|
510
|
+
|
|
511
|
+
// Start the loading process
|
|
512
|
+
void this.#loadDocumentWithProgress(
|
|
513
|
+
id,
|
|
514
|
+
documentId,
|
|
515
|
+
handle,
|
|
516
|
+
progressSignal,
|
|
517
|
+
abortPromise
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
const result = {
|
|
521
|
+
...initial,
|
|
522
|
+
peek: progressSignal.peek,
|
|
523
|
+
subscribe: progressSignal.subscribe,
|
|
524
|
+
}
|
|
525
|
+
this.#progressCache[documentId] = result
|
|
526
|
+
return result
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
async #loadDocumentWithProgress<T>(
|
|
530
|
+
id: AnyDocumentId,
|
|
531
|
+
documentId: DocumentId,
|
|
532
|
+
handle: DocHandle<T>,
|
|
533
|
+
progressSignal: {
|
|
534
|
+
notify: (progress: FindProgress<T>) => void
|
|
535
|
+
},
|
|
536
|
+
abortPromise: Promise<never>
|
|
537
|
+
) {
|
|
538
|
+
try {
|
|
539
|
+
progressSignal.notify({
|
|
540
|
+
state: "loading" as const,
|
|
541
|
+
progress: 25,
|
|
542
|
+
handle,
|
|
543
|
+
})
|
|
493
544
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
545
|
+
const loadingPromise = await (this.storageSubsystem
|
|
546
|
+
? this.storageSubsystem.loadDoc(handle.documentId)
|
|
547
|
+
: Promise.resolve(null))
|
|
548
|
+
|
|
549
|
+
const loadedDoc = await Promise.race([loadingPromise, abortPromise])
|
|
550
|
+
|
|
551
|
+
if (loadedDoc) {
|
|
552
|
+
handle.update(() => loadedDoc as Automerge.Doc<T>)
|
|
553
|
+
handle.doneLoading()
|
|
554
|
+
progressSignal.notify({
|
|
555
|
+
state: "loading" as const,
|
|
556
|
+
progress: 50,
|
|
499
557
|
handle,
|
|
500
|
-
}
|
|
558
|
+
})
|
|
559
|
+
} else {
|
|
560
|
+
await Promise.race([this.networkSubsystem.whenReady(), abortPromise])
|
|
561
|
+
handle.request()
|
|
562
|
+
progressSignal.notify({
|
|
563
|
+
state: "loading" as const,
|
|
564
|
+
progress: 75,
|
|
565
|
+
handle,
|
|
566
|
+
})
|
|
501
567
|
}
|
|
502
|
-
}
|
|
503
568
|
|
|
504
|
-
|
|
569
|
+
this.#registerHandleWithSubsystems(handle)
|
|
505
570
|
|
|
506
|
-
|
|
507
|
-
const result = await iterator.next()
|
|
508
|
-
return { ...result.value, next }
|
|
509
|
-
}
|
|
571
|
+
await Promise.race([handle.whenReady([READY, UNAVAILABLE]), abortPromise])
|
|
510
572
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
}
|
|
516
|
-
if (state.state === "unavailable") {
|
|
517
|
-
throw new Error(`Document ${id} is unavailable`)
|
|
573
|
+
if (handle.state === UNAVAILABLE) {
|
|
574
|
+
const unavailableProgress = {
|
|
575
|
+
state: "unavailable" as const,
|
|
576
|
+
handle,
|
|
518
577
|
}
|
|
519
|
-
|
|
520
|
-
|
|
578
|
+
progressSignal.notify(unavailableProgress)
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
if (handle.state === DELETED) {
|
|
582
|
+
throw new Error(`Document ${id} was deleted`)
|
|
521
583
|
}
|
|
522
|
-
throw new Error("Iterator completed without reaching ready state")
|
|
523
|
-
}
|
|
524
584
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
585
|
+
progressSignal.notify({ state: "ready" as const, handle })
|
|
586
|
+
} catch (error) {
|
|
587
|
+
progressSignal.notify({
|
|
588
|
+
state: "failed" as const,
|
|
589
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
590
|
+
handle: this.#getHandle<T>({ documentId }),
|
|
591
|
+
})
|
|
592
|
+
}
|
|
528
593
|
}
|
|
529
594
|
|
|
530
595
|
async find<T>(
|
|
@@ -534,15 +599,28 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
534
599
|
const { allowableStates = ["ready"], signal } = options
|
|
535
600
|
const progress = this.findWithProgress<T>(id, { signal })
|
|
536
601
|
|
|
537
|
-
|
|
538
|
-
console.log("returning early")
|
|
539
|
-
return progress.handle
|
|
540
|
-
}*/
|
|
541
|
-
|
|
542
|
-
if ("untilReady" in progress) {
|
|
602
|
+
if ("subscribe" in progress) {
|
|
543
603
|
this.#registerHandleWithSubsystems(progress.handle)
|
|
544
|
-
return
|
|
604
|
+
return new Promise((resolve, reject) => {
|
|
605
|
+
const unsubscribe = progress.subscribe(state => {
|
|
606
|
+
if (allowableStates.includes(state.handle.state)) {
|
|
607
|
+
unsubscribe()
|
|
608
|
+
resolve(state.handle)
|
|
609
|
+
} else if (state.state === "unavailable") {
|
|
610
|
+
unsubscribe()
|
|
611
|
+
reject(new Error(`Document ${id} is unavailable`))
|
|
612
|
+
} else if (state.state === "failed") {
|
|
613
|
+
unsubscribe()
|
|
614
|
+
reject(state.error)
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
})
|
|
545
618
|
} else {
|
|
619
|
+
if (progress.handle.state === READY) {
|
|
620
|
+
return progress.handle
|
|
621
|
+
}
|
|
622
|
+
// If the handle isn't ready, wait for it and then return it
|
|
623
|
+
await progress.handle.whenReady([READY, UNAVAILABLE])
|
|
546
624
|
return progress.handle
|
|
547
625
|
}
|
|
548
626
|
}
|
|
@@ -616,6 +694,7 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
616
694
|
handle.delete()
|
|
617
695
|
|
|
618
696
|
delete this.#handleCache[documentId]
|
|
697
|
+
delete this.#progressCache[documentId]
|
|
619
698
|
this.emit("delete-document", { documentId })
|
|
620
699
|
}
|
|
621
700
|
|
|
@@ -6,7 +6,6 @@ import { type DocumentId } from "../types.js"
|
|
|
6
6
|
import { StorageAdapterInterface } from "./StorageAdapterInterface.js"
|
|
7
7
|
import { ChunkInfo, StorageKey, StorageId } from "./types.js"
|
|
8
8
|
import { keyHash, headsHash } from "./keyHash.js"
|
|
9
|
-
import { chunkTypeFromKey } from "./chunkTypeFromKey.js"
|
|
10
9
|
import * as Uuid from "uuid"
|
|
11
10
|
import { EventEmitter } from "eventemitter3"
|
|
12
11
|
import { encodeHeads } from "../AutomergeUrl.js"
|
|
@@ -113,33 +112,63 @@ export class StorageSubsystem extends EventEmitter<StorageSubsystemEvents> {
|
|
|
113
112
|
// AUTOMERGE DOCUMENT STORAGE
|
|
114
113
|
|
|
115
114
|
/**
|
|
116
|
-
* Loads
|
|
115
|
+
* Loads and combines document chunks from storage, with snapshots first.
|
|
117
116
|
*/
|
|
118
|
-
async
|
|
119
|
-
// Load
|
|
120
|
-
const
|
|
121
|
-
|
|
117
|
+
async loadDocData(documentId: DocumentId): Promise<Uint8Array | null> {
|
|
118
|
+
// Load snapshots first
|
|
119
|
+
const snapshotChunks = await this.#storageAdapter.loadRange([
|
|
120
|
+
documentId,
|
|
121
|
+
"snapshot",
|
|
122
|
+
])
|
|
123
|
+
const incrementalChunks = await this.#storageAdapter.loadRange([
|
|
124
|
+
documentId,
|
|
125
|
+
"incremental",
|
|
126
|
+
])
|
|
127
|
+
|
|
128
|
+
const binaries: Uint8Array[] = []
|
|
122
129
|
const chunkInfos: ChunkInfo[] = []
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
// Process snapshots first
|
|
132
|
+
for (const chunk of snapshotChunks) {
|
|
126
133
|
if (chunk.data === undefined) continue
|
|
134
|
+
chunkInfos.push({
|
|
135
|
+
key: chunk.key,
|
|
136
|
+
type: "snapshot",
|
|
137
|
+
size: chunk.data.length,
|
|
138
|
+
})
|
|
139
|
+
binaries.push(chunk.data)
|
|
140
|
+
}
|
|
127
141
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
// Then process incrementals
|
|
143
|
+
for (const chunk of incrementalChunks) {
|
|
144
|
+
if (chunk.data === undefined) continue
|
|
131
145
|
chunkInfos.push({
|
|
132
146
|
key: chunk.key,
|
|
133
|
-
type:
|
|
147
|
+
type: "incremental",
|
|
134
148
|
size: chunk.data.length,
|
|
135
149
|
})
|
|
136
150
|
binaries.push(chunk.data)
|
|
137
151
|
}
|
|
152
|
+
|
|
153
|
+
// Store chunk infos for future reference
|
|
138
154
|
this.#chunkInfos.set(documentId, chunkInfos)
|
|
139
155
|
|
|
156
|
+
// If no chunks were found, return null
|
|
157
|
+
if (binaries.length === 0) {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
|
|
140
161
|
// Merge the chunks into a single binary
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
return mergeArrays(binaries)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Loads the Automerge document with the given ID from storage.
|
|
167
|
+
*/
|
|
168
|
+
async loadDoc<T>(documentId: DocumentId): Promise<A.Doc<T> | null> {
|
|
169
|
+
// Load and combine chunks
|
|
170
|
+
const binary = await this.loadDocData(documentId)
|
|
171
|
+
if (!binary) return null
|
|
143
172
|
|
|
144
173
|
// Load into an Automerge document
|
|
145
174
|
const start = performance.now()
|
|
@@ -169,6 +198,7 @@ export class StorageSubsystem extends EventEmitter<StorageSubsystemEvents> {
|
|
|
169
198
|
if (!this.#shouldSave(documentId, doc)) return
|
|
170
199
|
|
|
171
200
|
const sourceChunks = this.#chunkInfos.get(documentId) ?? []
|
|
201
|
+
|
|
172
202
|
if (this.#shouldCompact(sourceChunks)) {
|
|
173
203
|
await this.#saveTotal(documentId, doc, sourceChunks)
|
|
174
204
|
} else {
|
package/test/DocHandle.test.ts
CHANGED
|
@@ -520,4 +520,71 @@ describe("DocHandle", () => {
|
|
|
520
520
|
assert.deepStrictEqual(decode(data), message)
|
|
521
521
|
})
|
|
522
522
|
})
|
|
523
|
+
|
|
524
|
+
it("should cache view handles based on heads", async () => {
|
|
525
|
+
// Create and setup a document with some data
|
|
526
|
+
const handle = setup()
|
|
527
|
+
handle.change(doc => {
|
|
528
|
+
doc.foo = "Hello"
|
|
529
|
+
})
|
|
530
|
+
const heads1 = handle.heads()
|
|
531
|
+
|
|
532
|
+
// Make another change to get a different set of heads
|
|
533
|
+
handle.change(doc => {
|
|
534
|
+
doc.foo = "Hello, World!"
|
|
535
|
+
})
|
|
536
|
+
|
|
537
|
+
// Create a view at the first set of heads
|
|
538
|
+
const view1 = handle.view(heads1)
|
|
539
|
+
|
|
540
|
+
// Request the same view again
|
|
541
|
+
const view2 = handle.view(heads1)
|
|
542
|
+
|
|
543
|
+
// Verify we got the same handle instance back (cached version)
|
|
544
|
+
expect(view1).toBe(view2)
|
|
545
|
+
|
|
546
|
+
// Verify the contents are correct
|
|
547
|
+
expect(view1.doc().foo).toBe("Hello")
|
|
548
|
+
|
|
549
|
+
// Test with a different set of heads
|
|
550
|
+
const view3 = handle.view(handle.heads())
|
|
551
|
+
expect(view3).not.toBe(view1)
|
|
552
|
+
expect(view3.doc().foo).toBe("Hello, World!")
|
|
553
|
+
})
|
|
554
|
+
|
|
555
|
+
it("should improve performance when requesting the same view multiple times", () => {
|
|
556
|
+
// Create and setup a document with some data
|
|
557
|
+
const handle = setup()
|
|
558
|
+
handle.change(doc => {
|
|
559
|
+
doc.foo = "Hello"
|
|
560
|
+
})
|
|
561
|
+
const heads = handle.heads()
|
|
562
|
+
|
|
563
|
+
// First, measure time without cache (first access)
|
|
564
|
+
const startTimeNoCached = performance.now()
|
|
565
|
+
const firstView = handle.view(heads)
|
|
566
|
+
const endTimeNoCached = performance.now()
|
|
567
|
+
|
|
568
|
+
// Now measure with cache (subsequent accesses)
|
|
569
|
+
const startTimeCached = performance.now()
|
|
570
|
+
for (let i = 0; i < 100; i++) {
|
|
571
|
+
handle.view(heads)
|
|
572
|
+
}
|
|
573
|
+
const endTimeCached = performance.now()
|
|
574
|
+
|
|
575
|
+
// Assert that all views are the same instance
|
|
576
|
+
for (let i = 0; i < 10; i++) {
|
|
577
|
+
expect(handle.view(heads)).toBe(firstView)
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Calculate average times
|
|
581
|
+
const timeForFirstAccess = endTimeNoCached - startTimeNoCached
|
|
582
|
+
const timeForCachedAccesses = (endTimeCached - startTimeCached) / 100
|
|
583
|
+
|
|
584
|
+
console.log(`Time for first view (no cache): ${timeForFirstAccess}ms`)
|
|
585
|
+
console.log(`Average time per cached view: ${timeForCachedAccesses}ms`)
|
|
586
|
+
|
|
587
|
+
// Cached access should be significantly faster
|
|
588
|
+
expect(timeForCachedAccesses).toBeLessThan(timeForFirstAccess / 10)
|
|
589
|
+
})
|
|
523
590
|
})
|
package/test/Repo.test.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
import { getRandomItem } from "./helpers/getRandomItem.js"
|
|
34
34
|
import { TestDoc } from "./types.js"
|
|
35
35
|
import { StorageId, StorageKey } from "../src/storage/types.js"
|
|
36
|
+
import { chunkTypeFromKey } from "../src/storage/chunkTypeFromKey.js"
|
|
36
37
|
|
|
37
38
|
describe("Repo", () => {
|
|
38
39
|
describe("constructor", () => {
|
|
@@ -430,6 +431,40 @@ describe("Repo", () => {
|
|
|
430
431
|
)
|
|
431
432
|
})
|
|
432
433
|
|
|
434
|
+
it("should not call loadDoc multiple times when find() is called in quick succession", async () => {
|
|
435
|
+
const { repo, storageAdapter } = setup()
|
|
436
|
+
const handle = repo.create<TestDoc>()
|
|
437
|
+
handle.change(d => {
|
|
438
|
+
d.foo = "bar"
|
|
439
|
+
})
|
|
440
|
+
await repo.flush()
|
|
441
|
+
|
|
442
|
+
// Create a new repo instance that will use the same storage
|
|
443
|
+
const repo2 = new Repo({
|
|
444
|
+
storage: storageAdapter,
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
// Track how many times loadDoc is called
|
|
448
|
+
let loadDocCallCount = 0
|
|
449
|
+
const originalLoadDoc = repo2.storageSubsystem!.loadDoc.bind(
|
|
450
|
+
repo2.storageSubsystem
|
|
451
|
+
)
|
|
452
|
+
repo2.storageSubsystem!.loadDoc = async documentId => {
|
|
453
|
+
loadDocCallCount++
|
|
454
|
+
return originalLoadDoc(documentId)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Call find() twice in quick succession
|
|
458
|
+
const find1 = repo2.find(handle.url)
|
|
459
|
+
const find2 = repo2.find(handle.url)
|
|
460
|
+
|
|
461
|
+
// Wait for both calls to complete
|
|
462
|
+
await Promise.all([find1, find2])
|
|
463
|
+
|
|
464
|
+
// Verify loadDoc was only called once
|
|
465
|
+
assert.equal(loadDocCallCount, 1, "loadDoc should only be called once")
|
|
466
|
+
})
|
|
467
|
+
|
|
433
468
|
it("can import an existing document", async () => {
|
|
434
469
|
const { repo } = setup()
|
|
435
470
|
const doc = A.init<TestDoc>()
|
|
@@ -4,13 +4,15 @@ import assert from "assert"
|
|
|
4
4
|
import fs from "fs"
|
|
5
5
|
import os from "os"
|
|
6
6
|
import path from "path"
|
|
7
|
-
import { describe, it } from "vitest"
|
|
7
|
+
import { describe, it, expect } from "vitest"
|
|
8
8
|
import { generateAutomergeUrl, parseAutomergeUrl } from "../src/AutomergeUrl.js"
|
|
9
9
|
import { PeerId, cbor } from "../src/index.js"
|
|
10
10
|
import { StorageSubsystem } from "../src/storage/StorageSubsystem.js"
|
|
11
11
|
import { StorageId } from "../src/storage/types.js"
|
|
12
12
|
import { DummyStorageAdapter } from "../src/helpers/DummyStorageAdapter.js"
|
|
13
13
|
import * as Uuid from "uuid"
|
|
14
|
+
import { chunkTypeFromKey } from "../src/storage/chunkTypeFromKey.js"
|
|
15
|
+
import { DocumentId } from "../src/types.js"
|
|
14
16
|
|
|
15
17
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "automerge-repo-tests"))
|
|
16
18
|
|
|
@@ -243,6 +245,83 @@ describe("StorageSubsystem", () => {
|
|
|
243
245
|
assert.strictEqual(id1, id2)
|
|
244
246
|
})
|
|
245
247
|
})
|
|
248
|
+
|
|
249
|
+
describe("loadDoc", () => {
|
|
250
|
+
it("maintains correct document state when loading chunks in order", async () => {
|
|
251
|
+
const storageAdapter = new DummyStorageAdapter()
|
|
252
|
+
const storage = new StorageSubsystem(storageAdapter)
|
|
253
|
+
|
|
254
|
+
// Create a document with multiple changes
|
|
255
|
+
const doc = A.init<{ foo: string }>()
|
|
256
|
+
const doc1 = A.change(doc, d => {
|
|
257
|
+
d.foo = "first"
|
|
258
|
+
})
|
|
259
|
+
const doc2 = A.change(doc1, d => {
|
|
260
|
+
d.foo = "second"
|
|
261
|
+
})
|
|
262
|
+
const doc3 = A.change(doc2, d => {
|
|
263
|
+
d.foo = "third"
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
// Save the document with multiple changes
|
|
267
|
+
const documentId = "test-doc" as DocumentId
|
|
268
|
+
await storage.saveDoc(documentId, doc3)
|
|
269
|
+
|
|
270
|
+
// Load the document
|
|
271
|
+
const loadedDoc = await storage.loadDoc<{ foo: string }>(documentId)
|
|
272
|
+
|
|
273
|
+
// Verify the document state is correct
|
|
274
|
+
expect(loadedDoc?.foo).toBe("third")
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it("combines chunks with snapshot first", async () => {
|
|
278
|
+
const storageAdapter = new DummyStorageAdapter()
|
|
279
|
+
const storage = new StorageSubsystem(storageAdapter)
|
|
280
|
+
|
|
281
|
+
// Create a document with multiple changes
|
|
282
|
+
const doc = A.init<{ foo: string }>()
|
|
283
|
+
const doc1 = A.change(doc, d => {
|
|
284
|
+
d.foo = "first"
|
|
285
|
+
})
|
|
286
|
+
const doc2 = A.change(doc1, d => {
|
|
287
|
+
d.foo = Array(10000)
|
|
288
|
+
.fill(0)
|
|
289
|
+
.map(() =>
|
|
290
|
+
String.fromCharCode(Math.floor(Math.random() * 26) + 97)
|
|
291
|
+
)
|
|
292
|
+
.join("")
|
|
293
|
+
})
|
|
294
|
+
|
|
295
|
+
// Save the document with multiple changes
|
|
296
|
+
const documentId = "test-doc" as DocumentId
|
|
297
|
+
await storage.saveDoc(documentId, doc2)
|
|
298
|
+
|
|
299
|
+
const doc3 = A.change(doc2, d => {
|
|
300
|
+
d.foo = "third"
|
|
301
|
+
})
|
|
302
|
+
await storage.saveDoc(documentId, doc3)
|
|
303
|
+
|
|
304
|
+
// Load the document
|
|
305
|
+
const loadedDoc = await storage.loadDoc<{ foo: string }>(documentId)
|
|
306
|
+
|
|
307
|
+
// Verify the document state is correct
|
|
308
|
+
expect(loadedDoc?.foo).toBe(doc3.foo)
|
|
309
|
+
|
|
310
|
+
// Get the raw binary data from storage
|
|
311
|
+
const binary = await storage.loadDocData(documentId)
|
|
312
|
+
expect(binary).not.toBeNull()
|
|
313
|
+
if (!binary) return
|
|
314
|
+
|
|
315
|
+
// Verify the binary starts with the Automerge magic value
|
|
316
|
+
expect(binary[0]).toBe(0x85)
|
|
317
|
+
expect(binary[1]).toBe(0x6f)
|
|
318
|
+
expect(binary[2]).toBe(0x4a)
|
|
319
|
+
expect(binary[3]).toBe(0x83)
|
|
320
|
+
|
|
321
|
+
// Verify the chunk type is CHUNK_TYPE_DOCUMENT (0x00)
|
|
322
|
+
expect(binary[8]).toBe(0x00)
|
|
323
|
+
})
|
|
324
|
+
})
|
|
246
325
|
})
|
|
247
326
|
}
|
|
248
327
|
})
|