pushwork 1.0.11 → 1.0.15
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/commands.d.ts.map +1 -1
- package/dist/commands.js +7 -0
- package/dist/commands.js.map +1 -1
- package/dist/core/sync-engine.d.ts +8 -3
- package/dist/core/sync-engine.d.ts.map +1 -1
- package/dist/core/sync-engine.js +113 -159
- package/dist/core/sync-engine.js.map +1 -1
- package/dist/utils/network-sync.d.ts.map +1 -1
- package/dist/utils/network-sync.js +1 -1
- package/dist/utils/network-sync.js.map +1 -1
- package/dist/utils/text-diff.d.ts +37 -0
- package/dist/utils/text-diff.d.ts.map +1 -0
- package/dist/utils/text-diff.js +131 -0
- package/dist/utils/text-diff.js.map +1 -0
- package/package.json +1 -1
- package/src/commands.ts +11 -0
- package/src/core/sync-engine.ts +134 -180
- package/src/utils/network-sync.ts +15 -10
package/src/core/sync-engine.ts
CHANGED
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
AutomergeUrl,
|
|
3
3
|
Repo,
|
|
4
4
|
DocHandle,
|
|
5
|
+
UrlHeads,
|
|
5
6
|
parseAutomergeUrl,
|
|
6
7
|
stringifyAutomergeUrl,
|
|
7
8
|
} from "@automerge/automerge-repo";
|
|
@@ -33,6 +34,22 @@ import { ChangeDetector } from "./change-detection";
|
|
|
33
34
|
import { MoveDetector } from "./move-detection";
|
|
34
35
|
import { out } from "../utils/output";
|
|
35
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Apply a change to a document handle, using changeAt when heads are available
|
|
39
|
+
* to branch from a known version, otherwise falling back to change.
|
|
40
|
+
*/
|
|
41
|
+
function changeWithOptionalHeads<T>(
|
|
42
|
+
handle: DocHandle<T>,
|
|
43
|
+
heads: UrlHeads | undefined,
|
|
44
|
+
callback: A.ChangeFn<T>
|
|
45
|
+
): void {
|
|
46
|
+
if (heads && heads.length > 0) {
|
|
47
|
+
handle.changeAt(heads, callback);
|
|
48
|
+
} else {
|
|
49
|
+
handle.change(callback);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
36
53
|
/**
|
|
37
54
|
* Sync configuration constants
|
|
38
55
|
*/
|
|
@@ -242,12 +259,17 @@ export class SyncEngine {
|
|
|
242
259
|
}
|
|
243
260
|
|
|
244
261
|
if (this.handlesByPath.size > 0) {
|
|
245
|
-
//
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
)
|
|
262
|
+
// Sync level-by-level: deepest paths first, then shallower.
|
|
263
|
+
// Within each level, documents sync in parallel. But we wait
|
|
264
|
+
// for each level to complete before starting the next, ensuring
|
|
265
|
+
// children are on the server before their parent directories.
|
|
266
|
+
const levels = this.groupHandlesByDepthLevel();
|
|
267
|
+
for (const handlesAtLevel of levels) {
|
|
268
|
+
await waitForSync(
|
|
269
|
+
handlesAtLevel,
|
|
270
|
+
this.config.sync_server_storage_id
|
|
271
|
+
);
|
|
272
|
+
}
|
|
251
273
|
}
|
|
252
274
|
|
|
253
275
|
// Wait for bidirectional sync to stabilize.
|
|
@@ -587,33 +609,18 @@ export class SyncEngine {
|
|
|
587
609
|
const heads = fromEntry.head;
|
|
588
610
|
|
|
589
611
|
// Update both name and content (if content changed during move)
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
if (move.newContent
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
doc.content = move.newContent;
|
|
600
|
-
}
|
|
612
|
+
changeWithOptionalHeads(handle, heads, (doc: FileDocument) => {
|
|
613
|
+
doc.name = toFileName;
|
|
614
|
+
|
|
615
|
+
// If new content is provided, update it (handles move + modification case)
|
|
616
|
+
if (move.newContent !== undefined) {
|
|
617
|
+
if (typeof move.newContent === "string") {
|
|
618
|
+
doc.content = new A.ImmutableString(move.newContent);
|
|
619
|
+
} else {
|
|
620
|
+
doc.content = move.newContent;
|
|
601
621
|
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
handle.change((doc: FileDocument) => {
|
|
605
|
-
doc.name = toFileName;
|
|
606
|
-
|
|
607
|
-
// If new content is provided, update it (handles move + modification case)
|
|
608
|
-
if (move.newContent !== undefined) {
|
|
609
|
-
if (typeof move.newContent === "string") {
|
|
610
|
-
doc.content = new A.ImmutableString(move.newContent);
|
|
611
|
-
} else {
|
|
612
|
-
doc.content = move.newContent;
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
}
|
|
622
|
+
}
|
|
623
|
+
});
|
|
617
624
|
|
|
618
625
|
// Get versioned URL after changes (includes current heads)
|
|
619
626
|
const versionedUrl = this.getVersionedUrl(handle);
|
|
@@ -760,15 +767,9 @@ export class SyncEngine {
|
|
|
760
767
|
if (snapshot && filePath) {
|
|
761
768
|
heads = snapshot.files.get(filePath)?.head;
|
|
762
769
|
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
});
|
|
767
|
-
} else {
|
|
768
|
-
handle.change((doc: FileDocument) => {
|
|
769
|
-
doc.content = new A.ImmutableString("");
|
|
770
|
-
});
|
|
771
|
-
}
|
|
770
|
+
changeWithOptionalHeads(handle, heads, (doc: FileDocument) => {
|
|
771
|
+
doc.content = new A.ImmutableString("");
|
|
772
|
+
});
|
|
772
773
|
}
|
|
773
774
|
|
|
774
775
|
/**
|
|
@@ -799,35 +800,19 @@ export class SyncEngine {
|
|
|
799
800
|
let didChange = false;
|
|
800
801
|
const snapshotEntry = snapshot.directories.get(directoryPath);
|
|
801
802
|
const heads = snapshotEntry?.head;
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
});
|
|
816
|
-
} else {
|
|
817
|
-
dirHandle.change((doc: DirectoryDocument) => {
|
|
818
|
-
const existingIndex = doc.docs.findIndex(
|
|
819
|
-
(entry) => entry.name === fileName && entry.type === "file"
|
|
820
|
-
);
|
|
821
|
-
if (existingIndex === -1) {
|
|
822
|
-
doc.docs.push({
|
|
823
|
-
name: fileName,
|
|
824
|
-
type: "file",
|
|
825
|
-
url: fileUrl,
|
|
826
|
-
});
|
|
827
|
-
didChange = true;
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
}
|
|
803
|
+
changeWithOptionalHeads(dirHandle, heads, (doc: DirectoryDocument) => {
|
|
804
|
+
const existingIndex = doc.docs.findIndex(
|
|
805
|
+
(entry) => entry.name === fileName && entry.type === "file"
|
|
806
|
+
);
|
|
807
|
+
if (existingIndex === -1) {
|
|
808
|
+
doc.docs.push({
|
|
809
|
+
name: fileName,
|
|
810
|
+
type: "file",
|
|
811
|
+
url: fileUrl,
|
|
812
|
+
});
|
|
813
|
+
didChange = true;
|
|
814
|
+
}
|
|
815
|
+
});
|
|
831
816
|
// Always track the directory (even if unchanged) for proper leaf-first sync ordering
|
|
832
817
|
this.handlesByPath.set(directoryPath, dirHandle);
|
|
833
818
|
|
|
@@ -1007,37 +992,20 @@ export class SyncEngine {
|
|
|
1007
992
|
const heads = snapshotEntry?.head;
|
|
1008
993
|
let didChange = false;
|
|
1009
994
|
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
}`
|
|
1022
|
-
);
|
|
1023
|
-
}
|
|
1024
|
-
});
|
|
1025
|
-
} else {
|
|
1026
|
-
dirHandle.change((doc: DirectoryDocument) => {
|
|
1027
|
-
const indexToRemove = doc.docs.findIndex(
|
|
1028
|
-
(entry) => entry.name === fileName && entry.type === "file"
|
|
995
|
+
changeWithOptionalHeads(dirHandle, heads, (doc: DirectoryDocument) => {
|
|
996
|
+
const indexToRemove = doc.docs.findIndex(
|
|
997
|
+
(entry) => entry.name === fileName && entry.type === "file"
|
|
998
|
+
);
|
|
999
|
+
if (indexToRemove !== -1) {
|
|
1000
|
+
doc.docs.splice(indexToRemove, 1);
|
|
1001
|
+
didChange = true;
|
|
1002
|
+
out.taskLine(
|
|
1003
|
+
`Removed ${fileName} from ${
|
|
1004
|
+
formatRelativePath(directoryPath) || "root"
|
|
1005
|
+
}`
|
|
1029
1006
|
);
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
didChange = true;
|
|
1033
|
-
out.taskLine(
|
|
1034
|
-
`Removed ${fileName} from ${
|
|
1035
|
-
formatRelativePath(directoryPath) || "root"
|
|
1036
|
-
}`
|
|
1037
|
-
);
|
|
1038
|
-
}
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1041
1009
|
|
|
1042
1010
|
if (didChange && snapshotEntry) {
|
|
1043
1011
|
snapshotEntry.head = dirHandle.heads();
|
|
@@ -1184,15 +1152,9 @@ export class SyncEngine {
|
|
|
1184
1152
|
|
|
1185
1153
|
const timestamp = Date.now();
|
|
1186
1154
|
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
});
|
|
1191
|
-
} else {
|
|
1192
|
-
rootHandle.change((doc: DirectoryDocument) => {
|
|
1193
|
-
doc.lastSyncAt = timestamp;
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1155
|
+
changeWithOptionalHeads(rootHandle, heads, (doc: DirectoryDocument) => {
|
|
1156
|
+
doc.lastSyncAt = timestamp;
|
|
1157
|
+
});
|
|
1196
1158
|
|
|
1197
1159
|
// Track root directory for network sync
|
|
1198
1160
|
this.handlesByPath.set("", rootHandle);
|
|
@@ -1206,34 +1168,45 @@ export class SyncEngine {
|
|
|
1206
1168
|
}
|
|
1207
1169
|
|
|
1208
1170
|
/**
|
|
1209
|
-
*
|
|
1210
|
-
* Returns
|
|
1171
|
+
* Group tracked handles by depth level, ordered leaf-first (deepest level first).
|
|
1172
|
+
* Returns an array of arrays, where each inner array contains all handles at the
|
|
1173
|
+
* same depth level. Levels are ordered from deepest to shallowest (root).
|
|
1174
|
+
*
|
|
1175
|
+
* This grouping enables level-by-level network sync: all documents at the deepest
|
|
1176
|
+
* level sync in parallel first, then the next level up, etc. This ensures children
|
|
1177
|
+
* are fully synced to the server before their parent directories.
|
|
1211
1178
|
*/
|
|
1212
|
-
private
|
|
1213
|
-
//
|
|
1214
|
-
const
|
|
1215
|
-
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
if (depthA !== depthB) {
|
|
1220
|
-
return depthB - depthA;
|
|
1179
|
+
private groupHandlesByDepthLevel(): DocHandle<unknown>[][] {
|
|
1180
|
+
// Group paths by depth
|
|
1181
|
+
const pathsByDepth = new Map<number, string[]>();
|
|
1182
|
+
for (const path of this.handlesByPath.keys()) {
|
|
1183
|
+
const depth = path ? path.split("/").length : 0;
|
|
1184
|
+
if (!pathsByDepth.has(depth)) {
|
|
1185
|
+
pathsByDepth.set(depth, []);
|
|
1221
1186
|
}
|
|
1187
|
+
pathsByDepth.get(depth)!.push(path);
|
|
1188
|
+
}
|
|
1222
1189
|
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
});
|
|
1190
|
+
// Sort depths descending (deepest first) to get leaf-first ordering
|
|
1191
|
+
const sortedDepths = Array.from(pathsByDepth.keys()).sort((a, b) => b - a);
|
|
1226
1192
|
|
|
1227
|
-
//
|
|
1228
|
-
const
|
|
1229
|
-
for (const
|
|
1230
|
-
const
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1193
|
+
// Build level groups, logging sync order for debugging
|
|
1194
|
+
const levels: DocHandle<unknown>[][] = [];
|
|
1195
|
+
for (const depth of sortedDepths) {
|
|
1196
|
+
const paths = pathsByDepth.get(depth)!;
|
|
1197
|
+
paths.sort((a, b) => a.localeCompare(b)); // Alphabetical within level
|
|
1198
|
+
|
|
1199
|
+
const handlesAtLevel: DocHandle<unknown>[] = [];
|
|
1200
|
+
for (const path of paths) {
|
|
1201
|
+
const handle = this.handlesByPath.get(path)!;
|
|
1202
|
+
const versionedUrl = this.getVersionedUrl(handle);
|
|
1203
|
+
out.taskLine(`Sync: ${path || "(root)"} -> ${versionedUrl}`, true);
|
|
1204
|
+
handlesAtLevel.push(handle);
|
|
1205
|
+
}
|
|
1206
|
+
levels.push(handlesAtLevel);
|
|
1234
1207
|
}
|
|
1235
1208
|
|
|
1236
|
-
return
|
|
1209
|
+
return levels;
|
|
1237
1210
|
}
|
|
1238
1211
|
|
|
1239
1212
|
/**
|
|
@@ -1281,8 +1254,17 @@ export class SyncEngine {
|
|
|
1281
1254
|
filesByDir.get(dirPath)!.push(filePath);
|
|
1282
1255
|
}
|
|
1283
1256
|
|
|
1284
|
-
// Process
|
|
1285
|
-
|
|
1257
|
+
// Process directories leaf-first (deepest first) so children are
|
|
1258
|
+
// up-to-date before their parents are processed
|
|
1259
|
+
const sortedDirPaths = Array.from(filesByDir.keys()).sort((a, b) => {
|
|
1260
|
+
const depthA = a ? a.split("/").length : 0;
|
|
1261
|
+
const depthB = b ? b.split("/").length : 0;
|
|
1262
|
+
if (depthA !== depthB) return depthB - depthA;
|
|
1263
|
+
return a.localeCompare(b);
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
for (const dirPath of sortedDirPaths) {
|
|
1267
|
+
const filePaths = filesByDir.get(dirPath)!;
|
|
1286
1268
|
try {
|
|
1287
1269
|
// Get the directory URL
|
|
1288
1270
|
let dirUrl: AutomergeUrl;
|
|
@@ -1333,31 +1315,17 @@ export class SyncEngine {
|
|
|
1333
1315
|
|
|
1334
1316
|
// Update all file entries in the directory document
|
|
1335
1317
|
let didChange = false;
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
didChange = true;
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
});
|
|
1348
|
-
} else {
|
|
1349
|
-
dirHandle.change((doc: DirectoryDocument) => {
|
|
1350
|
-
for (const [fileName, newUrl] of fileUrlUpdates) {
|
|
1351
|
-
const existingIndex = doc.docs.findIndex(
|
|
1352
|
-
(entry) => entry.name === fileName && entry.type === "file"
|
|
1353
|
-
);
|
|
1354
|
-
if (existingIndex !== -1 && doc.docs[existingIndex].url !== newUrl) {
|
|
1355
|
-
doc.docs[existingIndex].url = newUrl;
|
|
1356
|
-
didChange = true;
|
|
1357
|
-
}
|
|
1318
|
+
changeWithOptionalHeads(dirHandle, heads, (doc: DirectoryDocument) => {
|
|
1319
|
+
for (const [fileName, newUrl] of fileUrlUpdates) {
|
|
1320
|
+
const existingIndex = doc.docs.findIndex(
|
|
1321
|
+
(entry) => entry.name === fileName && entry.type === "file"
|
|
1322
|
+
);
|
|
1323
|
+
if (existingIndex !== -1 && doc.docs[existingIndex].url !== newUrl) {
|
|
1324
|
+
doc.docs[existingIndex].url = newUrl;
|
|
1325
|
+
didChange = true;
|
|
1358
1326
|
}
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1361
1329
|
|
|
1362
1330
|
// Track directory and update heads
|
|
1363
1331
|
if (didChange) {
|
|
@@ -1452,29 +1420,15 @@ export class SyncEngine {
|
|
|
1452
1420
|
const parentHeads = parentSnapshotEntry?.head;
|
|
1453
1421
|
|
|
1454
1422
|
let didChange = false;
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
|
-
} else {
|
|
1467
|
-
parentHandle.change((doc: DirectoryDocument) => {
|
|
1468
|
-
const existingIndex = doc.docs.findIndex(
|
|
1469
|
-
(entry) => entry.name === dirName && entry.type === "folder"
|
|
1470
|
-
);
|
|
1471
|
-
if (existingIndex !== -1) {
|
|
1472
|
-
// Update the URL with current versioned URL
|
|
1473
|
-
doc.docs[existingIndex].url = currentVersionedUrl;
|
|
1474
|
-
didChange = true;
|
|
1475
|
-
}
|
|
1476
|
-
});
|
|
1477
|
-
}
|
|
1423
|
+
changeWithOptionalHeads(parentHandle, parentHeads, (doc: DirectoryDocument) => {
|
|
1424
|
+
const existingIndex = doc.docs.findIndex(
|
|
1425
|
+
(entry) => entry.name === dirName && entry.type === "folder"
|
|
1426
|
+
);
|
|
1427
|
+
if (existingIndex !== -1) {
|
|
1428
|
+
doc.docs[existingIndex].url = currentVersionedUrl;
|
|
1429
|
+
didChange = true;
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1478
1432
|
|
|
1479
1433
|
// Track parent for sync and update its heads in snapshot
|
|
1480
1434
|
if (didChange) {
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
DocHandle,
|
|
3
|
+
StorageId,
|
|
4
|
+
Repo,
|
|
5
|
+
AutomergeUrl,
|
|
6
|
+
} from "@automerge/automerge-repo";
|
|
2
7
|
import * as A from "@automerge/automerge";
|
|
3
8
|
import { out } from "./output";
|
|
4
9
|
import { DirectoryDocument } from "../types";
|
|
@@ -8,7 +13,7 @@ import { getPlainUrl } from "./directory";
|
|
|
8
13
|
* Wait for bidirectional sync to stabilize.
|
|
9
14
|
* This function waits until document heads stop changing, indicating that
|
|
10
15
|
* both outgoing and incoming sync has completed.
|
|
11
|
-
*
|
|
16
|
+
*
|
|
12
17
|
* @param repo - The Automerge repository
|
|
13
18
|
* @param rootDirectoryUrl - The root directory URL to start traversal from
|
|
14
19
|
* @param syncServerStorageId - The sync server storage ID
|
|
@@ -22,7 +27,7 @@ export async function waitForBidirectionalSync(
|
|
|
22
27
|
timeoutMs?: number;
|
|
23
28
|
pollIntervalMs?: number;
|
|
24
29
|
stableChecksRequired?: number;
|
|
25
|
-
} = {}
|
|
30
|
+
} = {},
|
|
26
31
|
): Promise<void> {
|
|
27
32
|
const {
|
|
28
33
|
timeoutMs = 10000,
|
|
@@ -70,7 +75,7 @@ export async function waitForBidirectionalSync(
|
|
|
70
75
|
*/
|
|
71
76
|
async function getAllDocumentHeads(
|
|
72
77
|
repo: Repo,
|
|
73
|
-
rootDirectoryUrl: AutomergeUrl
|
|
78
|
+
rootDirectoryUrl: AutomergeUrl,
|
|
74
79
|
): Promise<Map<string, string>> {
|
|
75
80
|
const heads = new Map<string, string>();
|
|
76
81
|
// Pass URL as-is; collectHeadsRecursive will strip heads
|
|
@@ -85,13 +90,13 @@ async function getAllDocumentHeads(
|
|
|
85
90
|
async function collectHeadsRecursive(
|
|
86
91
|
repo: Repo,
|
|
87
92
|
directoryUrl: AutomergeUrl,
|
|
88
|
-
heads: Map<string, string
|
|
93
|
+
heads: Map<string, string>,
|
|
89
94
|
): Promise<void> {
|
|
90
95
|
try {
|
|
91
96
|
const plainUrl = getPlainUrl(directoryUrl);
|
|
92
97
|
const handle = await repo.find<DirectoryDocument>(plainUrl);
|
|
93
98
|
const doc = await handle.doc();
|
|
94
|
-
|
|
99
|
+
|
|
95
100
|
// Record this directory's heads (use plain URL as key for consistency)
|
|
96
101
|
heads.set(plainUrl, JSON.stringify(handle.heads()));
|
|
97
102
|
|
|
@@ -125,7 +130,7 @@ async function collectHeadsRecursive(
|
|
|
125
130
|
*/
|
|
126
131
|
function headsMapEqual(
|
|
127
132
|
a: Map<string, string>,
|
|
128
|
-
b: Map<string, string
|
|
133
|
+
b: Map<string, string>,
|
|
129
134
|
): boolean {
|
|
130
135
|
if (a.size !== b.size) {
|
|
131
136
|
return false;
|
|
@@ -144,7 +149,7 @@ function headsMapEqual(
|
|
|
144
149
|
export async function waitForSync(
|
|
145
150
|
handlesToWaitOn: DocHandle<unknown>[],
|
|
146
151
|
syncServerStorageId?: StorageId,
|
|
147
|
-
timeoutMs: number =
|
|
152
|
+
timeoutMs: number = 1000000,
|
|
148
153
|
): Promise<void> {
|
|
149
154
|
const startTime = Date.now();
|
|
150
155
|
|
|
@@ -192,8 +197,8 @@ export async function waitForSync(
|
|
|
192
197
|
cleanup();
|
|
193
198
|
reject(
|
|
194
199
|
new Error(
|
|
195
|
-
`Sync timeout after ${timeoutMs}ms for document ${handle.url}
|
|
196
|
-
)
|
|
200
|
+
`Sync timeout after ${timeoutMs}ms for document ${handle.url}`,
|
|
201
|
+
),
|
|
197
202
|
);
|
|
198
203
|
}, timeoutMs);
|
|
199
204
|
|