@automerge/automerge-repo 1.1.4 → 1.1.5
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.map +1 -1
- package/dist/DocHandle.js +4 -2
- package/dist/Repo.d.ts +8 -0
- package/dist/Repo.d.ts.map +1 -1
- package/dist/Repo.js +24 -0
- package/dist/helpers/tests/network-adapter-tests.d.ts +1 -1
- package/dist/helpers/tests/network-adapter-tests.d.ts.map +1 -1
- package/dist/helpers/tests/network-adapter-tests.js +2 -2
- package/dist/helpers/tests/storage-adapter-tests.d.ts +7 -0
- package/dist/helpers/tests/storage-adapter-tests.d.ts.map +1 -0
- package/dist/helpers/tests/storage-adapter-tests.js +128 -0
- package/dist/storage/StorageSubsystem.d.ts +5 -0
- package/dist/storage/StorageSubsystem.d.ts.map +1 -1
- package/dist/storage/StorageSubsystem.js +27 -0
- package/dist/synchronizer/DocSynchronizer.js +1 -1
- package/package.json +2 -2
- package/src/DocHandle.ts +5 -3
- package/src/Repo.ts +34 -4
- package/src/helpers/tests/network-adapter-tests.ts +4 -2
- package/src/helpers/tests/storage-adapter-tests.ts +193 -0
- package/src/storage/StorageSubsystem.ts +32 -0
- package/src/synchronizer/DocSynchronizer.ts +1 -1
- package/test/DummyStorageAdapter.test.ts +11 -0
- package/test/Repo.test.ts +117 -2
- package/test/helpers/DummyNetworkAdapter.ts +20 -18
- package/test/helpers/DummyStorageAdapter.ts +5 -1
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,2BAA2B,CAAA;AAE9C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EASL,UAAU,EAEX,MAAM,QAAQ,CAAA;AAMf,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C;;;;;;;;;;;KAWK;AACL,qBAAa,SAAS,CAAC,CAAC,CAAE,EAAE;AAC1B,SAAQ,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;;IAmB/B,UAAU,EAAE,UAAU;IAX/B;;;;OAIG;IACH,IAAI,GAAG,IAAI,YAAY,CAEtB;IAED,cAAc;gBAEL,UAAU,EAAE,UAAU,EAC7B,OAAO,GAAE,gBAAgB,CAAC,CAAC,CAAM;
|
|
1
|
+
{"version":3,"file":"DocHandle.d.ts","sourceRoot":"","sources":["../src/DocHandle.ts"],"names":[],"mappings":"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"}
|
package/dist/DocHandle.js
CHANGED
|
@@ -201,8 +201,10 @@ export class DocHandle//
|
|
|
201
201
|
}
|
|
202
202
|
/** Returns a promise that resolves when the docHandle is in one of the given states */
|
|
203
203
|
#statePromise(awaitStates) {
|
|
204
|
-
const awaitStatesArray = Array.isArray(awaitStates)
|
|
205
|
-
|
|
204
|
+
const awaitStatesArray = Array.isArray(awaitStates)
|
|
205
|
+
? awaitStates
|
|
206
|
+
: [awaitStates];
|
|
207
|
+
return waitFor(this.#machine, s => awaitStatesArray.some(state => s.matches(state)),
|
|
206
208
|
// use a longer delay here so as not to race with other delays
|
|
207
209
|
{ timeout: this.#timeoutDelay * 2 });
|
|
208
210
|
}
|
package/dist/Repo.d.ts
CHANGED
|
@@ -82,6 +82,14 @@ export declare class Repo extends EventEmitter<RepoEvents> {
|
|
|
82
82
|
import<T>(binary: Uint8Array): DocHandle<T>;
|
|
83
83
|
subscribeToRemotes: (remotes: StorageId[]) => void;
|
|
84
84
|
storageId: () => Promise<StorageId | undefined>;
|
|
85
|
+
/**
|
|
86
|
+
* Waits for Repo to finish write changes to disk.
|
|
87
|
+
* @hidden this API is experimental and may change
|
|
88
|
+
* @param documents - if provided, only waits for the specified documents
|
|
89
|
+
* @param timeout - if provided, the maximum time to wait in milliseconds (rejects on timeout)
|
|
90
|
+
* @returns Promise<void>
|
|
91
|
+
*/
|
|
92
|
+
flush(documents?: DocumentId[], timeout?: number): Promise<void>;
|
|
85
93
|
}
|
|
86
94
|
export interface RepoConfig {
|
|
87
95
|
/** Our unique identifier */
|
package/dist/Repo.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAM5C,OAAO,EAAE,SAAS,EAAiC,MAAM,gBAAgB,CAAA;AAIzE,OAAO,
|
|
1
|
+
{"version":3,"file":"Repo.d.ts","sourceRoot":"","sources":["../src/Repo.ts"],"names":[],"mappings":"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"}
|
package/dist/Repo.js
CHANGED
|
@@ -383,4 +383,28 @@ export class Repo extends EventEmitter {
|
|
|
383
383
|
return this.storageSubsystem.id();
|
|
384
384
|
}
|
|
385
385
|
};
|
|
386
|
+
/**
|
|
387
|
+
* Waits for Repo to finish write changes to disk.
|
|
388
|
+
* @hidden this API is experimental and may change
|
|
389
|
+
* @param documents - if provided, only waits for the specified documents
|
|
390
|
+
* @param timeout - if provided, the maximum time to wait in milliseconds (rejects on timeout)
|
|
391
|
+
* @returns Promise<void>
|
|
392
|
+
*/
|
|
393
|
+
async flush(documents, timeout) {
|
|
394
|
+
if (!this.storageSubsystem) {
|
|
395
|
+
return Promise.resolve();
|
|
396
|
+
}
|
|
397
|
+
const handles = documents
|
|
398
|
+
? documents.map(id => this.#handleCache[id])
|
|
399
|
+
: Object.values(this.#handleCache);
|
|
400
|
+
return Promise.all(handles.map(async (handle) => {
|
|
401
|
+
const doc = handle.docSync();
|
|
402
|
+
if (!doc) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
return this.storageSubsystem.flush(handle.documentId, doc, timeout);
|
|
406
|
+
})).then(() => {
|
|
407
|
+
/* No-op. To return `voi`d and not `void[]` */
|
|
408
|
+
});
|
|
409
|
+
}
|
|
386
410
|
}
|
|
@@ -11,7 +11,7 @@ import type { NetworkAdapterInterface } from "../../network/NetworkAdapterInterf
|
|
|
11
11
|
* - `teardown`: An optional function that will be called after the tests have run. This can be used
|
|
12
12
|
* to clean up any resources that were created during the test.
|
|
13
13
|
*/
|
|
14
|
-
export declare function
|
|
14
|
+
export declare function runNetworkAdapterTests(_setup: SetupFn, title?: string): void;
|
|
15
15
|
type Network = NetworkAdapterInterface | NetworkAdapterInterface[];
|
|
16
16
|
export type SetupFn = () => Promise<{
|
|
17
17
|
adapters: [Network, Network, Network];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAIvF;;;;;;;;;;;GAWG;AACH,wBAAgB,
|
|
1
|
+
{"version":3,"file":"network-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/network-adapter-tests.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAIvF;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAyJ5E;AAID,KAAK,OAAO,GAAG,uBAAuB,GAAG,uBAAuB,EAAE,CAAA;AAElE,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;IACrC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAC,CAAA"}
|
|
@@ -15,7 +15,7 @@ import { pause } from "../pause.js";
|
|
|
15
15
|
* - `teardown`: An optional function that will be called after the tests have run. This can be used
|
|
16
16
|
* to clean up any resources that were created during the test.
|
|
17
17
|
*/
|
|
18
|
-
export function
|
|
18
|
+
export function runNetworkAdapterTests(_setup, title) {
|
|
19
19
|
// Wrap the provided setup function
|
|
20
20
|
const setup = async () => {
|
|
21
21
|
const { adapters, teardown = NO_OP } = await _setup();
|
|
@@ -23,7 +23,7 @@ export function runAdapterTests(_setup, title) {
|
|
|
23
23
|
const [a, b, c] = adapters.map(toArray);
|
|
24
24
|
return { adapters: [a, b, c], teardown };
|
|
25
25
|
};
|
|
26
|
-
describe(`
|
|
26
|
+
describe(`Network adapter acceptance tests ${title ? `(${title})` : ""}`, () => {
|
|
27
27
|
it("can sync 2 repos", async () => {
|
|
28
28
|
const doTest = async (a, b) => {
|
|
29
29
|
const aliceRepo = new Repo({ network: a, peerId: alice });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StorageAdapterInterface } from "../../storage/StorageAdapterInterface.js";
|
|
2
|
+
export declare function runStorageAdapterTests(_setup: SetupFn, title?: string): void;
|
|
3
|
+
export type SetupFn = () => Promise<{
|
|
4
|
+
adapter: StorageAdapterInterface;
|
|
5
|
+
teardown?: () => void;
|
|
6
|
+
}>;
|
|
7
|
+
//# sourceMappingURL=storage-adapter-tests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-adapter-tests.d.ts","sourceRoot":"","sources":["../../../src/helpers/tests/storage-adapter-tests.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAA;AAQvF,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CA+K5E;AAID,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC;IAClC,OAAO,EAAE,uBAAuB,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB,CAAC,CAAA"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
const PAYLOAD_A = () => new Uint8Array([0, 1, 127, 99, 154, 235]);
|
|
3
|
+
const PAYLOAD_B = () => new Uint8Array([1, 76, 160, 53, 57, 10, 230]);
|
|
4
|
+
const PAYLOAD_C = () => new Uint8Array([2, 111, 74, 131, 236, 96, 142, 193]);
|
|
5
|
+
const LARGE_PAYLOAD = new Uint8Array(100000).map(() => Math.random() * 256);
|
|
6
|
+
export function runStorageAdapterTests(_setup, title) {
|
|
7
|
+
const setup = async () => {
|
|
8
|
+
const { adapter, teardown = NO_OP } = await _setup();
|
|
9
|
+
return { adapter, teardown };
|
|
10
|
+
};
|
|
11
|
+
describe(`Storage adapter acceptance tests ${title ? `(${title})` : ""}`, () => {
|
|
12
|
+
describe("load", () => {
|
|
13
|
+
it("should return undefined if there is no data", async () => {
|
|
14
|
+
const { adapter, teardown } = await setup();
|
|
15
|
+
const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"]);
|
|
16
|
+
expect(actual).toBeUndefined();
|
|
17
|
+
teardown();
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
describe("save and load", () => {
|
|
21
|
+
it("should return data that was saved", async () => {
|
|
22
|
+
const { adapter, teardown } = await setup();
|
|
23
|
+
await adapter.save(["storage-adapter-id"], PAYLOAD_A());
|
|
24
|
+
const actual = await adapter.load(["storage-adapter-id"]);
|
|
25
|
+
expect(actual).toStrictEqual(PAYLOAD_A());
|
|
26
|
+
teardown();
|
|
27
|
+
});
|
|
28
|
+
it("should work with composite keys", async () => {
|
|
29
|
+
const { adapter, teardown } = await setup();
|
|
30
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
|
|
31
|
+
const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"]);
|
|
32
|
+
expect(actual).toStrictEqual(PAYLOAD_A());
|
|
33
|
+
teardown();
|
|
34
|
+
});
|
|
35
|
+
it("should work with a large payload", async () => {
|
|
36
|
+
const { adapter, teardown } = await setup();
|
|
37
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], LARGE_PAYLOAD);
|
|
38
|
+
const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"]);
|
|
39
|
+
expect(actual).toStrictEqual(LARGE_PAYLOAD);
|
|
40
|
+
teardown();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("loadRange", () => {
|
|
44
|
+
it("should return an empty array if there is no data", async () => {
|
|
45
|
+
const { adapter, teardown } = await setup();
|
|
46
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([]);
|
|
47
|
+
teardown();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("save and loadRange", () => {
|
|
51
|
+
it("should return all the data that matches the key", async () => {
|
|
52
|
+
const { adapter, teardown } = await setup();
|
|
53
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
|
|
54
|
+
await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B());
|
|
55
|
+
await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C());
|
|
56
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual(expect.arrayContaining([
|
|
57
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
|
|
58
|
+
{ key: ["AAAAA", "snapshot", "yyyyy"], data: PAYLOAD_B() },
|
|
59
|
+
{ key: ["AAAAA", "sync-state", "zzzzz"], data: PAYLOAD_C() },
|
|
60
|
+
]));
|
|
61
|
+
expect(await adapter.loadRange(["AAAAA", "sync-state"])).toStrictEqual(expect.arrayContaining([
|
|
62
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
|
|
63
|
+
{ key: ["AAAAA", "sync-state", "zzzzz"], data: PAYLOAD_C() },
|
|
64
|
+
]));
|
|
65
|
+
teardown();
|
|
66
|
+
});
|
|
67
|
+
it("should only load values that match they key", async () => {
|
|
68
|
+
const { adapter, teardown } = await setup();
|
|
69
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
|
|
70
|
+
await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_C());
|
|
71
|
+
const actual = await adapter.loadRange(["AAAAA"]);
|
|
72
|
+
expect(actual).toStrictEqual(expect.arrayContaining([
|
|
73
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
|
|
74
|
+
]));
|
|
75
|
+
expect(actual).toStrictEqual(expect.not.arrayContaining([
|
|
76
|
+
{ key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_C() },
|
|
77
|
+
]));
|
|
78
|
+
teardown();
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
describe("save and remove", () => {
|
|
82
|
+
it("after removing, should be empty", async () => {
|
|
83
|
+
const { adapter, teardown } = await setup();
|
|
84
|
+
await adapter.save(["AAAAA", "snapshot", "xxxxx"], PAYLOAD_A());
|
|
85
|
+
await adapter.remove(["AAAAA", "snapshot", "xxxxx"]);
|
|
86
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([]);
|
|
87
|
+
expect(await adapter.load(["AAAAA", "snapshot", "xxxxx"])).toBeUndefined();
|
|
88
|
+
teardown();
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe("save and save", () => {
|
|
92
|
+
it("should overwrite data saved with the same key", async () => {
|
|
93
|
+
const { adapter, teardown } = await setup();
|
|
94
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
|
|
95
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_B());
|
|
96
|
+
expect(await adapter.loadRange(["AAAAA", "sync-state"])).toStrictEqual([
|
|
97
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_B() },
|
|
98
|
+
]);
|
|
99
|
+
teardown();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe("removeRange", () => {
|
|
103
|
+
it("should remove a range of records", async () => {
|
|
104
|
+
const { adapter, teardown } = await setup();
|
|
105
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
|
|
106
|
+
await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B());
|
|
107
|
+
await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C());
|
|
108
|
+
await adapter.removeRange(["AAAAA", "sync-state"]);
|
|
109
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([
|
|
110
|
+
{ key: ["AAAAA", "snapshot", "yyyyy"], data: PAYLOAD_B() },
|
|
111
|
+
]);
|
|
112
|
+
teardown();
|
|
113
|
+
});
|
|
114
|
+
it("should not remove records that don't match", async () => {
|
|
115
|
+
const { adapter, teardown } = await setup();
|
|
116
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A());
|
|
117
|
+
await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_B());
|
|
118
|
+
await adapter.removeRange(["AAAAA"]);
|
|
119
|
+
const actual = await adapter.loadRange(["BBBBB"]);
|
|
120
|
+
expect(actual).toStrictEqual([
|
|
121
|
+
{ key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_B() },
|
|
122
|
+
]);
|
|
123
|
+
teardown();
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const NO_OP = () => { };
|
|
@@ -48,5 +48,10 @@ export declare class StorageSubsystem {
|
|
|
48
48
|
removeDoc(documentId: DocumentId): Promise<void>;
|
|
49
49
|
loadSyncState(documentId: DocumentId, storageId: StorageId): Promise<A.SyncState | undefined>;
|
|
50
50
|
saveSyncState(documentId: DocumentId, storageId: StorageId, syncState: A.SyncState): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Waiting for document state to be written to disk.
|
|
53
|
+
* @deprecated because it will be changed soon.
|
|
54
|
+
*/
|
|
55
|
+
flush(documentId: DocumentId, doc: A.Doc<unknown>, timeout?: number): Promise<void>;
|
|
51
56
|
}
|
|
52
57
|
//# sourceMappingURL=StorageSubsystem.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StorageSubsystem.d.ts","sourceRoot":"","sources":["../../src/storage/StorageSubsystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,2BAA2B,CAAA;AAI9C,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,2BAA2B,CAAA;AAI9C,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AACtE,OAAO,EAAyB,SAAS,EAAE,MAAM,YAAY,CAAA;AAM7D;;;GAGG;AACH,qBAAa,gBAAgB;;gBAiBf,cAAc,EAAE,uBAAuB;IAI7C,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,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAmClE;;;;;;OAMG;IACG,OAAO,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAczE;;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;IAM7B,aAAa,CACjB,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,CAAC,CAAC,SAAS,GACrB,OAAO,CAAC,IAAI,CAAC;IAKhB;;;OAGG;IACG,KAAK,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,MAAM;CAiE1E"}
|
|
@@ -5,6 +5,7 @@ import { mergeArrays } from "../helpers/mergeArrays.js";
|
|
|
5
5
|
import { keyHash, headsHash } from "./keyHash.js";
|
|
6
6
|
import { chunkTypeFromKey } from "./chunkTypeFromKey.js";
|
|
7
7
|
import * as Uuid from "uuid";
|
|
8
|
+
import { EventEmitter } from "eventemitter3";
|
|
8
9
|
/**
|
|
9
10
|
* The storage subsystem is responsible for saving and loading Automerge documents to and from
|
|
10
11
|
* storage adapter. It also provides a generic key/value storage interface for other uses.
|
|
@@ -19,6 +20,7 @@ export class StorageSubsystem {
|
|
|
19
20
|
/** Flag to avoid compacting when a compaction is already underway */
|
|
20
21
|
#compacting = false;
|
|
21
22
|
#log = debug(`automerge-repo:storage-subsystem`);
|
|
23
|
+
#saved = new EventEmitter();
|
|
22
24
|
constructor(storageAdapter) {
|
|
23
25
|
this.#storageAdapter = storageAdapter;
|
|
24
26
|
}
|
|
@@ -124,6 +126,7 @@ export class StorageSubsystem {
|
|
|
124
126
|
await this.#saveIncremental(documentId, doc);
|
|
125
127
|
}
|
|
126
128
|
this.#storedHeads.set(documentId, A.getHeads(doc));
|
|
129
|
+
this.#saved.emit("saved");
|
|
127
130
|
}
|
|
128
131
|
/**
|
|
129
132
|
* Removes the Automerge document with the given ID from storage
|
|
@@ -185,6 +188,30 @@ export class StorageSubsystem {
|
|
|
185
188
|
const key = [documentId, "sync-state", storageId];
|
|
186
189
|
await this.#storageAdapter.save(key, A.encodeSyncState(syncState));
|
|
187
190
|
}
|
|
191
|
+
/**
|
|
192
|
+
* Waiting for document state to be written to disk.
|
|
193
|
+
* @deprecated because it will be changed soon.
|
|
194
|
+
*/
|
|
195
|
+
async flush(documentId, doc, timeout) {
|
|
196
|
+
return new Promise((resolve, reject) => {
|
|
197
|
+
let timeoutId;
|
|
198
|
+
if (timeout) {
|
|
199
|
+
timeoutId = setTimeout(() => {
|
|
200
|
+
this.#saved.off("saved", checkIfSaved);
|
|
201
|
+
reject(new Error("Timed out waiting for save"));
|
|
202
|
+
}, timeout);
|
|
203
|
+
}
|
|
204
|
+
const checkIfSaved = () => {
|
|
205
|
+
if (!this.#shouldSave(documentId, doc)) {
|
|
206
|
+
this.#saved.off("saved", checkIfSaved);
|
|
207
|
+
clearTimeout(timeoutId);
|
|
208
|
+
resolve();
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
this.#saved.on("saved", checkIfSaved);
|
|
212
|
+
checkIfSaved();
|
|
213
|
+
});
|
|
214
|
+
}
|
|
188
215
|
/**
|
|
189
216
|
* Returns true if the document has changed since the last time it was saved.
|
|
190
217
|
*/
|
|
@@ -152,7 +152,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
152
152
|
return this.#peers.includes(peerId);
|
|
153
153
|
}
|
|
154
154
|
beginSync(peerIds) {
|
|
155
|
-
const noPeersWithDocument = peerIds.every(
|
|
155
|
+
const noPeersWithDocument = peerIds.every(peerId => this.#peerDocumentStatuses[peerId] in ["unavailable", "wants"]);
|
|
156
156
|
// At this point if we don't have anything in our storage, we need to use an empty doc to sync
|
|
157
157
|
// with; but we don't want to surface that state to the front end
|
|
158
158
|
const docPromise = this.#handle
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automerge/automerge-repo",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.5",
|
|
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>",
|
|
@@ -55,5 +55,5 @@
|
|
|
55
55
|
"publishConfig": {
|
|
56
56
|
"access": "public"
|
|
57
57
|
},
|
|
58
|
-
"gitHead": "
|
|
58
|
+
"gitHead": "64edfaea5e53e77cd9158fc1df52fea85801db71"
|
|
59
59
|
}
|
package/src/DocHandle.ts
CHANGED
|
@@ -244,12 +244,14 @@ export class DocHandle<T> //
|
|
|
244
244
|
|
|
245
245
|
/** Returns a promise that resolves when the docHandle is in one of the given states */
|
|
246
246
|
#statePromise(awaitStates: HandleState | HandleState[]) {
|
|
247
|
-
const awaitStatesArray = Array.isArray(awaitStates)
|
|
247
|
+
const awaitStatesArray = Array.isArray(awaitStates)
|
|
248
|
+
? awaitStates
|
|
249
|
+
: [awaitStates]
|
|
248
250
|
return waitFor(
|
|
249
251
|
this.#machine,
|
|
250
|
-
s => awaitStatesArray.some(
|
|
252
|
+
s => awaitStatesArray.some(state => s.matches(state)),
|
|
251
253
|
// use a longer delay here so as not to race with other delays
|
|
252
|
-
{timeout: this.#timeoutDelay * 2}
|
|
254
|
+
{ timeout: this.#timeoutDelay * 2 }
|
|
253
255
|
)
|
|
254
256
|
}
|
|
255
257
|
|
package/src/Repo.ts
CHANGED
|
@@ -2,15 +2,18 @@ import { next as Automerge } from "@automerge/automerge"
|
|
|
2
2
|
import debug from "debug"
|
|
3
3
|
import { EventEmitter } from "eventemitter3"
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
generateAutomergeUrl,
|
|
6
|
+
interpretAsDocumentId,
|
|
7
|
+
parseAutomergeUrl,
|
|
8
8
|
} from "./AutomergeUrl.js"
|
|
9
9
|
import { DocHandle, DocHandleEncodedChangePayload } from "./DocHandle.js"
|
|
10
10
|
import { RemoteHeadsSubscriptions } from "./RemoteHeadsSubscriptions.js"
|
|
11
11
|
import { headsAreSame } from "./helpers/headsAreSame.js"
|
|
12
12
|
import { throttle } from "./helpers/throttle.js"
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
NetworkAdapterInterface,
|
|
15
|
+
type PeerMetadata,
|
|
16
|
+
} from "./network/NetworkAdapterInterface.js"
|
|
14
17
|
import { NetworkSubsystem } from "./network/NetworkSubsystem.js"
|
|
15
18
|
import { RepoMessage } from "./network/messages.js"
|
|
16
19
|
import { StorageAdapterInterface } from "./storage/StorageAdapterInterface.js"
|
|
@@ -502,6 +505,33 @@ export class Repo extends EventEmitter<RepoEvents> {
|
|
|
502
505
|
return this.storageSubsystem.id()
|
|
503
506
|
}
|
|
504
507
|
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Waits for Repo to finish write changes to disk.
|
|
511
|
+
* @hidden this API is experimental and may change
|
|
512
|
+
* @param documents - if provided, only waits for the specified documents
|
|
513
|
+
* @param timeout - if provided, the maximum time to wait in milliseconds (rejects on timeout)
|
|
514
|
+
* @returns Promise<void>
|
|
515
|
+
*/
|
|
516
|
+
async flush(documents?: DocumentId[], timeout?: number): Promise<void> {
|
|
517
|
+
if (!this.storageSubsystem) {
|
|
518
|
+
return Promise.resolve()
|
|
519
|
+
}
|
|
520
|
+
const handles = documents
|
|
521
|
+
? documents.map(id => this.#handleCache[id])
|
|
522
|
+
: Object.values(this.#handleCache)
|
|
523
|
+
return Promise.all(
|
|
524
|
+
handles.map(async handle => {
|
|
525
|
+
const doc = handle.docSync()
|
|
526
|
+
if (!doc) {
|
|
527
|
+
return
|
|
528
|
+
}
|
|
529
|
+
return this.storageSubsystem!.flush(handle.documentId, doc, timeout)
|
|
530
|
+
})
|
|
531
|
+
).then(() => {
|
|
532
|
+
/* No-op. To return `voi`d and not `void[]` */
|
|
533
|
+
})
|
|
534
|
+
}
|
|
505
535
|
}
|
|
506
536
|
|
|
507
537
|
export interface RepoConfig {
|
|
@@ -17,7 +17,7 @@ import { pause } from "../pause.js"
|
|
|
17
17
|
* - `teardown`: An optional function that will be called after the tests have run. This can be used
|
|
18
18
|
* to clean up any resources that were created during the test.
|
|
19
19
|
*/
|
|
20
|
-
export function
|
|
20
|
+
export function runNetworkAdapterTests(_setup: SetupFn, title?: string): void {
|
|
21
21
|
// Wrap the provided setup function
|
|
22
22
|
const setup = async () => {
|
|
23
23
|
const { adapters, teardown = NO_OP } = await _setup()
|
|
@@ -28,7 +28,9 @@ export function runAdapterTests(_setup: SetupFn, title?: string): void {
|
|
|
28
28
|
return { adapters: [a, b, c], teardown }
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
describe(`
|
|
31
|
+
describe(`Network adapter acceptance tests ${
|
|
32
|
+
title ? `(${title})` : ""
|
|
33
|
+
}`, () => {
|
|
32
34
|
it("can sync 2 repos", async () => {
|
|
33
35
|
const doTest = async (
|
|
34
36
|
a: NetworkAdapterInterface[],
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
|
|
3
|
+
import type { StorageAdapterInterface } from "../../storage/StorageAdapterInterface.js"
|
|
4
|
+
|
|
5
|
+
const PAYLOAD_A = () => new Uint8Array([0, 1, 127, 99, 154, 235])
|
|
6
|
+
const PAYLOAD_B = () => new Uint8Array([1, 76, 160, 53, 57, 10, 230])
|
|
7
|
+
const PAYLOAD_C = () => new Uint8Array([2, 111, 74, 131, 236, 96, 142, 193])
|
|
8
|
+
|
|
9
|
+
const LARGE_PAYLOAD = new Uint8Array(100000).map(() => Math.random() * 256)
|
|
10
|
+
|
|
11
|
+
export function runStorageAdapterTests(_setup: SetupFn, title?: string): void {
|
|
12
|
+
const setup = async () => {
|
|
13
|
+
const { adapter, teardown = NO_OP } = await _setup()
|
|
14
|
+
return { adapter, teardown }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe(`Storage adapter acceptance tests ${
|
|
18
|
+
title ? `(${title})` : ""
|
|
19
|
+
}`, () => {
|
|
20
|
+
describe("load", () => {
|
|
21
|
+
it("should return undefined if there is no data", async () => {
|
|
22
|
+
const { adapter, teardown } = await setup()
|
|
23
|
+
|
|
24
|
+
const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"])
|
|
25
|
+
expect(actual).toBeUndefined()
|
|
26
|
+
|
|
27
|
+
teardown()
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe("save and load", () => {
|
|
32
|
+
it("should return data that was saved", async () => {
|
|
33
|
+
const { adapter, teardown } = await setup()
|
|
34
|
+
|
|
35
|
+
await adapter.save(["storage-adapter-id"], PAYLOAD_A())
|
|
36
|
+
const actual = await adapter.load(["storage-adapter-id"])
|
|
37
|
+
expect(actual).toStrictEqual(PAYLOAD_A())
|
|
38
|
+
|
|
39
|
+
teardown()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it("should work with composite keys", async () => {
|
|
43
|
+
const { adapter, teardown } = await setup()
|
|
44
|
+
|
|
45
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
|
|
46
|
+
const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"])
|
|
47
|
+
expect(actual).toStrictEqual(PAYLOAD_A())
|
|
48
|
+
|
|
49
|
+
teardown()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it("should work with a large payload", async () => {
|
|
53
|
+
const { adapter, teardown } = await setup()
|
|
54
|
+
|
|
55
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], LARGE_PAYLOAD)
|
|
56
|
+
const actual = await adapter.load(["AAAAA", "sync-state", "xxxxx"])
|
|
57
|
+
expect(actual).toStrictEqual(LARGE_PAYLOAD)
|
|
58
|
+
|
|
59
|
+
teardown()
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe("loadRange", () => {
|
|
64
|
+
it("should return an empty array if there is no data", async () => {
|
|
65
|
+
const { adapter, teardown } = await setup()
|
|
66
|
+
|
|
67
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([])
|
|
68
|
+
|
|
69
|
+
teardown()
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
describe("save and loadRange", () => {
|
|
74
|
+
it("should return all the data that matches the key", async () => {
|
|
75
|
+
const { adapter, teardown } = await setup()
|
|
76
|
+
|
|
77
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
|
|
78
|
+
await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B())
|
|
79
|
+
await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C())
|
|
80
|
+
|
|
81
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual(
|
|
82
|
+
expect.arrayContaining([
|
|
83
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
|
|
84
|
+
{ key: ["AAAAA", "snapshot", "yyyyy"], data: PAYLOAD_B() },
|
|
85
|
+
{ key: ["AAAAA", "sync-state", "zzzzz"], data: PAYLOAD_C() },
|
|
86
|
+
])
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
expect(await adapter.loadRange(["AAAAA", "sync-state"])).toStrictEqual(
|
|
90
|
+
expect.arrayContaining([
|
|
91
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
|
|
92
|
+
{ key: ["AAAAA", "sync-state", "zzzzz"], data: PAYLOAD_C() },
|
|
93
|
+
])
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
teardown()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it("should only load values that match they key", async () => {
|
|
100
|
+
const { adapter, teardown } = await setup()
|
|
101
|
+
|
|
102
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
|
|
103
|
+
await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_C())
|
|
104
|
+
|
|
105
|
+
const actual = await adapter.loadRange(["AAAAA"])
|
|
106
|
+
expect(actual).toStrictEqual(
|
|
107
|
+
expect.arrayContaining([
|
|
108
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_A() },
|
|
109
|
+
])
|
|
110
|
+
)
|
|
111
|
+
expect(actual).toStrictEqual(
|
|
112
|
+
expect.not.arrayContaining([
|
|
113
|
+
{ key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_C() },
|
|
114
|
+
])
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
teardown()
|
|
118
|
+
})
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
describe("save and remove", () => {
|
|
122
|
+
it("after removing, should be empty", async () => {
|
|
123
|
+
const { adapter, teardown } = await setup()
|
|
124
|
+
|
|
125
|
+
await adapter.save(["AAAAA", "snapshot", "xxxxx"], PAYLOAD_A())
|
|
126
|
+
await adapter.remove(["AAAAA", "snapshot", "xxxxx"])
|
|
127
|
+
|
|
128
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([])
|
|
129
|
+
expect(
|
|
130
|
+
await adapter.load(["AAAAA", "snapshot", "xxxxx"])
|
|
131
|
+
).toBeUndefined()
|
|
132
|
+
|
|
133
|
+
teardown()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe("save and save", () => {
|
|
138
|
+
it("should overwrite data saved with the same key", async () => {
|
|
139
|
+
const { adapter, teardown } = await setup()
|
|
140
|
+
|
|
141
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
|
|
142
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_B())
|
|
143
|
+
|
|
144
|
+
expect(await adapter.loadRange(["AAAAA", "sync-state"])).toStrictEqual([
|
|
145
|
+
{ key: ["AAAAA", "sync-state", "xxxxx"], data: PAYLOAD_B() },
|
|
146
|
+
])
|
|
147
|
+
|
|
148
|
+
teardown()
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
describe("removeRange", () => {
|
|
153
|
+
it("should remove a range of records", async () => {
|
|
154
|
+
const { adapter, teardown } = await setup()
|
|
155
|
+
|
|
156
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
|
|
157
|
+
await adapter.save(["AAAAA", "snapshot", "yyyyy"], PAYLOAD_B())
|
|
158
|
+
await adapter.save(["AAAAA", "sync-state", "zzzzz"], PAYLOAD_C())
|
|
159
|
+
|
|
160
|
+
await adapter.removeRange(["AAAAA", "sync-state"])
|
|
161
|
+
|
|
162
|
+
expect(await adapter.loadRange(["AAAAA"])).toStrictEqual([
|
|
163
|
+
{ key: ["AAAAA", "snapshot", "yyyyy"], data: PAYLOAD_B() },
|
|
164
|
+
])
|
|
165
|
+
|
|
166
|
+
teardown()
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it("should not remove records that don't match", async () => {
|
|
170
|
+
const { adapter, teardown } = await setup()
|
|
171
|
+
|
|
172
|
+
await adapter.save(["AAAAA", "sync-state", "xxxxx"], PAYLOAD_A())
|
|
173
|
+
await adapter.save(["BBBBB", "sync-state", "zzzzz"], PAYLOAD_B())
|
|
174
|
+
|
|
175
|
+
await adapter.removeRange(["AAAAA"])
|
|
176
|
+
|
|
177
|
+
const actual = await adapter.loadRange(["BBBBB"])
|
|
178
|
+
expect(actual).toStrictEqual([
|
|
179
|
+
{ key: ["BBBBB", "sync-state", "zzzzz"], data: PAYLOAD_B() },
|
|
180
|
+
])
|
|
181
|
+
|
|
182
|
+
teardown()
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const NO_OP = () => {}
|
|
189
|
+
|
|
190
|
+
export type SetupFn = () => Promise<{
|
|
191
|
+
adapter: StorageAdapterInterface
|
|
192
|
+
teardown?: () => void
|
|
193
|
+
}>
|
|
@@ -8,6 +8,7 @@ import { ChunkInfo, StorageKey, StorageId } from "./types.js"
|
|
|
8
8
|
import { keyHash, headsHash } from "./keyHash.js"
|
|
9
9
|
import { chunkTypeFromKey } from "./chunkTypeFromKey.js"
|
|
10
10
|
import * as Uuid from "uuid"
|
|
11
|
+
import { EventEmitter } from "eventemitter3"
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* The storage subsystem is responsible for saving and loading Automerge documents to and from
|
|
@@ -28,6 +29,8 @@ export class StorageSubsystem {
|
|
|
28
29
|
|
|
29
30
|
#log = debug(`automerge-repo:storage-subsystem`)
|
|
30
31
|
|
|
32
|
+
#saved = new EventEmitter<{ saved: () => void }>()
|
|
33
|
+
|
|
31
34
|
constructor(storageAdapter: StorageAdapterInterface) {
|
|
32
35
|
this.#storageAdapter = storageAdapter
|
|
33
36
|
}
|
|
@@ -156,6 +159,7 @@ export class StorageSubsystem {
|
|
|
156
159
|
await this.#saveIncremental(documentId, doc)
|
|
157
160
|
}
|
|
158
161
|
this.#storedHeads.set(documentId, A.getHeads(doc))
|
|
162
|
+
this.#saved.emit("saved")
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
/**
|
|
@@ -245,6 +249,34 @@ export class StorageSubsystem {
|
|
|
245
249
|
await this.#storageAdapter.save(key, A.encodeSyncState(syncState))
|
|
246
250
|
}
|
|
247
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Waiting for document state to be written to disk.
|
|
254
|
+
* @deprecated because it will be changed soon.
|
|
255
|
+
*/
|
|
256
|
+
async flush(documentId: DocumentId, doc: A.Doc<unknown>, timeout?: number) {
|
|
257
|
+
return new Promise<void>((resolve, reject) => {
|
|
258
|
+
let timeoutId: NodeJS.Timeout
|
|
259
|
+
if (timeout) {
|
|
260
|
+
timeoutId = setTimeout(() => {
|
|
261
|
+
this.#saved.off("saved", checkIfSaved)
|
|
262
|
+
reject(new Error("Timed out waiting for save"))
|
|
263
|
+
}, timeout)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const checkIfSaved = () => {
|
|
267
|
+
if (!this.#shouldSave(documentId, doc)) {
|
|
268
|
+
this.#saved.off("saved", checkIfSaved)
|
|
269
|
+
clearTimeout(timeoutId)
|
|
270
|
+
resolve()
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
this.#saved.on("saved", checkIfSaved)
|
|
275
|
+
|
|
276
|
+
checkIfSaved()
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
248
280
|
/**
|
|
249
281
|
* Returns true if the document has changed since the last time it was saved.
|
|
250
282
|
*/
|
|
@@ -228,7 +228,7 @@ export class DocSynchronizer extends Synchronizer {
|
|
|
228
228
|
|
|
229
229
|
beginSync(peerIds: PeerId[]) {
|
|
230
230
|
const noPeersWithDocument = peerIds.every(
|
|
231
|
-
|
|
231
|
+
peerId => this.#peerDocumentStatuses[peerId] in ["unavailable", "wants"]
|
|
232
232
|
)
|
|
233
233
|
|
|
234
234
|
// At this point if we don't have anything in our storage, we need to use an empty doc to sync
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { beforeEach, describe } from "vitest"
|
|
2
|
+
import { DummyStorageAdapter } from "./helpers/DummyStorageAdapter.js"
|
|
3
|
+
import { runStorageAdapterTests } from "../src/helpers/tests/storage-adapter-tests.js"
|
|
4
|
+
|
|
5
|
+
describe("DummyStorageAdapter", () => {
|
|
6
|
+
const setup = async () => ({
|
|
7
|
+
adapter: new DummyStorageAdapter(),
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
runStorageAdapterTests(setup, "DummyStorageAdapter")
|
|
11
|
+
})
|
package/test/Repo.test.ts
CHANGED
|
@@ -376,8 +376,7 @@ describe("Repo", () => {
|
|
|
376
376
|
d.count = 1
|
|
377
377
|
})
|
|
378
378
|
|
|
379
|
-
|
|
380
|
-
await pause()
|
|
379
|
+
await repo.flush()
|
|
381
380
|
|
|
382
381
|
const initialKeys = storage.keys()
|
|
383
382
|
|
|
@@ -460,6 +459,122 @@ describe("Repo", () => {
|
|
|
460
459
|
})
|
|
461
460
|
})
|
|
462
461
|
|
|
462
|
+
describe("flush behaviour", () => {
|
|
463
|
+
const setup = () => {
|
|
464
|
+
let blockedSaves = []
|
|
465
|
+
let resume = () => {
|
|
466
|
+
blockedSaves.forEach(resolve => resolve())
|
|
467
|
+
blockedSaves = []
|
|
468
|
+
}
|
|
469
|
+
const pausedStorage = new DummyStorageAdapter()
|
|
470
|
+
{
|
|
471
|
+
const originalSave = pausedStorage.save.bind(pausedStorage)
|
|
472
|
+
pausedStorage.save = async (...args) => {
|
|
473
|
+
await new Promise(resolve => {
|
|
474
|
+
console.log("made a promise", ...args[0])
|
|
475
|
+
blockedSaves.push(resolve)
|
|
476
|
+
})
|
|
477
|
+
await pause(0)
|
|
478
|
+
// otherwise all the save promises resolve together
|
|
479
|
+
// which prevents testing flushing a single docID
|
|
480
|
+
console.log("resuming save", ...args[0])
|
|
481
|
+
return originalSave(...args)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const repo = new Repo({
|
|
486
|
+
storage: pausedStorage,
|
|
487
|
+
network: [],
|
|
488
|
+
})
|
|
489
|
+
|
|
490
|
+
// Create a pair of handles
|
|
491
|
+
const handle = repo.create<{ foo: string }>({ foo: "first" })
|
|
492
|
+
const handle2 = repo.create<{ foo: string }>({ foo: "second" })
|
|
493
|
+
return { resume, pausedStorage, repo, handle, handle2 }
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
it("should not be in a new repo yet because the storage is slow", async () => {
|
|
497
|
+
const { pausedStorage, repo, handle, handle2 } = setup()
|
|
498
|
+
expect((await handle.doc()).foo).toEqual("first")
|
|
499
|
+
expect((await handle2.doc()).foo).toEqual("second")
|
|
500
|
+
|
|
501
|
+
// Reload repo
|
|
502
|
+
const repo2 = new Repo({
|
|
503
|
+
storage: pausedStorage,
|
|
504
|
+
network: [],
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
// Could not find the document that is not yet saved because of slow storage.
|
|
508
|
+
const reloadedHandle = repo2.find<{ foo: string }>(handle.url)
|
|
509
|
+
expect(pausedStorage.keys()).to.deep.equal([])
|
|
510
|
+
expect(await reloadedHandle.doc()).toEqual(undefined)
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it("should be visible to a new repo after flush()", async () => {
|
|
514
|
+
const { resume, pausedStorage, repo, handle, handle2 } = setup()
|
|
515
|
+
|
|
516
|
+
const flushPromise = repo.flush()
|
|
517
|
+
resume()
|
|
518
|
+
await flushPromise
|
|
519
|
+
|
|
520
|
+
// Check that the data is now saved.
|
|
521
|
+
expect(pausedStorage.keys().length).toBeGreaterThan(0)
|
|
522
|
+
|
|
523
|
+
{
|
|
524
|
+
// Reload repo
|
|
525
|
+
const repo = new Repo({
|
|
526
|
+
storage: pausedStorage,
|
|
527
|
+
network: [],
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
expect(
|
|
531
|
+
(await repo.find<{ foo: string }>(handle.documentId).doc()).foo
|
|
532
|
+
).toEqual("first")
|
|
533
|
+
expect(
|
|
534
|
+
(await repo.find<{ foo: string }>(handle2.documentId).doc()).foo
|
|
535
|
+
).toEqual("second")
|
|
536
|
+
}
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
it("should only block on flushing requested documents", async () => {
|
|
540
|
+
const { resume, pausedStorage, repo, handle, handle2 } = setup()
|
|
541
|
+
|
|
542
|
+
const flushPromise = repo.flush([handle.documentId])
|
|
543
|
+
resume()
|
|
544
|
+
await flushPromise
|
|
545
|
+
|
|
546
|
+
// Check that the data is now saved.
|
|
547
|
+
expect(pausedStorage.keys().length).toBeGreaterThan(0)
|
|
548
|
+
|
|
549
|
+
{
|
|
550
|
+
// Reload repo
|
|
551
|
+
const repo = new Repo({
|
|
552
|
+
storage: pausedStorage,
|
|
553
|
+
network: [],
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
expect(
|
|
557
|
+
(await repo.find<{ foo: string }>(handle.documentId).doc()).foo
|
|
558
|
+
).toEqual("first")
|
|
559
|
+
// Really, it's okay if the second one is also flushed but I'm forcing the issue
|
|
560
|
+
// in the test storage engine above to make sure the behaviour is as documented
|
|
561
|
+
expect(
|
|
562
|
+
await repo.find<{ foo: string }>(handle2.documentId).doc()
|
|
563
|
+
).toEqual(undefined)
|
|
564
|
+
}
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
it("should time out with failure after a specified delay", async () => {
|
|
568
|
+
const { resume, pausedStorage, repo, handle, handle2 } = setup()
|
|
569
|
+
|
|
570
|
+
const flushPromise = repo.flush([handle.documentId], 10)
|
|
571
|
+
expect(flushPromise).rejects.toThrowError("Timed out waiting for save")
|
|
572
|
+
|
|
573
|
+
// Check that the data is now saved.
|
|
574
|
+
expect(pausedStorage.keys().length).toBe(0)
|
|
575
|
+
})
|
|
576
|
+
})
|
|
577
|
+
|
|
463
578
|
describe("with peers (linear network)", async () => {
|
|
464
579
|
it("n-peers connected in a line", async () => {
|
|
465
580
|
const createNConnectedRepos = async (
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { pause } from "../../src/helpers/pause.js"
|
|
1
|
+
import { pause } from "../../src/helpers/pause.js"
|
|
2
2
|
import { Message, NetworkAdapter, PeerId } from "../../src/index.js"
|
|
3
3
|
|
|
4
4
|
export class DummyNetworkAdapter extends NetworkAdapter {
|
|
5
5
|
#startReady: boolean
|
|
6
|
-
#sendMessage?: SendMessageFn
|
|
6
|
+
#sendMessage?: SendMessageFn
|
|
7
7
|
|
|
8
|
-
constructor(opts: Options = {startReady: true}) {
|
|
8
|
+
constructor(opts: Options = { startReady: true }) {
|
|
9
9
|
super()
|
|
10
|
-
this.#startReady = opts.startReady
|
|
11
|
-
this.#sendMessage = opts.sendMessage
|
|
10
|
+
this.#startReady = opts.startReady
|
|
11
|
+
this.#sendMessage = opts.sendMessage
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
connect(peerId: PeerId) {
|
|
15
|
-
this.peerId = peerId
|
|
15
|
+
this.peerId = peerId
|
|
16
16
|
if (this.#startReady) {
|
|
17
17
|
this.emit("ready", { network: this })
|
|
18
18
|
}
|
|
@@ -21,34 +21,36 @@ export class DummyNetworkAdapter extends NetworkAdapter {
|
|
|
21
21
|
disconnect() {}
|
|
22
22
|
|
|
23
23
|
peerCandidate(peerId: PeerId) {
|
|
24
|
-
this.emit(
|
|
24
|
+
this.emit("peer-candidate", { peerId, peerMetadata: {} })
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
override send(message: Message) {
|
|
28
|
-
this.#sendMessage?.(message)
|
|
28
|
+
this.#sendMessage?.(message)
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
receive(message: Message) {
|
|
32
|
-
this.emit(
|
|
32
|
+
this.emit("message", message)
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
static createConnectedPair({ latency = 10 }: { latency?: number} = {}) {
|
|
35
|
+
static createConnectedPair({ latency = 10 }: { latency?: number } = {}) {
|
|
36
36
|
const adapter1: DummyNetworkAdapter = new DummyNetworkAdapter({
|
|
37
37
|
startReady: true,
|
|
38
|
-
sendMessage: (message: Message) =>
|
|
39
|
-
|
|
38
|
+
sendMessage: (message: Message) =>
|
|
39
|
+
pause(latency).then(() => adapter2.receive(message)),
|
|
40
|
+
})
|
|
40
41
|
const adapter2: DummyNetworkAdapter = new DummyNetworkAdapter({
|
|
41
42
|
startReady: true,
|
|
42
|
-
sendMessage: (message: Message) =>
|
|
43
|
-
|
|
43
|
+
sendMessage: (message: Message) =>
|
|
44
|
+
pause(latency).then(() => adapter1.receive(message)),
|
|
45
|
+
})
|
|
44
46
|
|
|
45
|
-
return [adapter1, adapter2]
|
|
47
|
+
return [adapter1, adapter2]
|
|
46
48
|
}
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
type SendMessageFn = (message: Message) => void
|
|
51
|
+
type SendMessageFn = (message: Message) => void
|
|
50
52
|
|
|
51
53
|
type Options = {
|
|
52
|
-
startReady?: boolean
|
|
53
|
-
sendMessage?: SendMessageFn
|
|
54
|
+
startReady?: boolean
|
|
55
|
+
sendMessage?: SendMessageFn
|
|
54
56
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Chunk,
|
|
3
|
+
StorageAdapterInterface,
|
|
4
|
+
type StorageKey,
|
|
5
|
+
} from "../../src/index.js"
|
|
2
6
|
|
|
3
7
|
export class DummyStorageAdapter implements StorageAdapterInterface {
|
|
4
8
|
#data: Record<string, Uint8Array> = {}
|