midsummer-sol 0.1.2 → 0.1.3
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/index.js +177 -0
- package/package.json +1 -1
- package/sol.js +617 -119
package/index.js
CHANGED
|
@@ -1359,6 +1359,182 @@ function localTarget(r) {
|
|
|
1359
1359
|
}
|
|
1360
1360
|
};
|
|
1361
1361
|
}
|
|
1362
|
+
// src/converge.ts
|
|
1363
|
+
var EMPTY_TREE2 = { kind: "tree", entries: {} };
|
|
1364
|
+
function buildAncestry(ops) {
|
|
1365
|
+
const parents = new Map;
|
|
1366
|
+
const add = (child, parent) => {
|
|
1367
|
+
if (!child || !parent || parent === child)
|
|
1368
|
+
return;
|
|
1369
|
+
let s = parents.get(child);
|
|
1370
|
+
if (!s)
|
|
1371
|
+
parents.set(child, s = new Set);
|
|
1372
|
+
s.add(parent);
|
|
1373
|
+
};
|
|
1374
|
+
let prevRoot;
|
|
1375
|
+
for (const op of ops) {
|
|
1376
|
+
if (op.type === "checkpoint") {
|
|
1377
|
+
add(op.rootAfter, op.parent);
|
|
1378
|
+
add(op.rootAfter, op.parent2);
|
|
1379
|
+
} else {
|
|
1380
|
+
add(op.rootAfter, op.parent ?? prevRoot);
|
|
1381
|
+
}
|
|
1382
|
+
prevRoot = op.rootAfter;
|
|
1383
|
+
}
|
|
1384
|
+
return parents;
|
|
1385
|
+
}
|
|
1386
|
+
function ancestorsOf(head, parents) {
|
|
1387
|
+
const seen = new Set;
|
|
1388
|
+
const stack = [head];
|
|
1389
|
+
while (stack.length) {
|
|
1390
|
+
const h = stack.pop();
|
|
1391
|
+
if (seen.has(h))
|
|
1392
|
+
continue;
|
|
1393
|
+
seen.add(h);
|
|
1394
|
+
for (const p of parents.get(h) ?? [])
|
|
1395
|
+
stack.push(p);
|
|
1396
|
+
}
|
|
1397
|
+
return seen;
|
|
1398
|
+
}
|
|
1399
|
+
function mergeBase(a, b, parents) {
|
|
1400
|
+
if (a === b)
|
|
1401
|
+
return a;
|
|
1402
|
+
const bAnc = ancestorsOf(b, parents);
|
|
1403
|
+
const seen = new Set;
|
|
1404
|
+
let frontier = [a];
|
|
1405
|
+
while (frontier.length) {
|
|
1406
|
+
const next = [];
|
|
1407
|
+
for (const h of frontier) {
|
|
1408
|
+
if (seen.has(h))
|
|
1409
|
+
continue;
|
|
1410
|
+
seen.add(h);
|
|
1411
|
+
if (bAnc.has(h))
|
|
1412
|
+
return h;
|
|
1413
|
+
for (const p of parents.get(h) ?? [])
|
|
1414
|
+
next.push(p);
|
|
1415
|
+
}
|
|
1416
|
+
frontier = next;
|
|
1417
|
+
}
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
async function hydrate(src, dst, root) {
|
|
1421
|
+
if (!root)
|
|
1422
|
+
return dst.put(EMPTY_TREE2);
|
|
1423
|
+
const stack = [root];
|
|
1424
|
+
const seen = new Set;
|
|
1425
|
+
while (stack.length) {
|
|
1426
|
+
const h = stack.pop();
|
|
1427
|
+
if (seen.has(h))
|
|
1428
|
+
continue;
|
|
1429
|
+
seen.add(h);
|
|
1430
|
+
const node = await src.get(h);
|
|
1431
|
+
if (!node)
|
|
1432
|
+
continue;
|
|
1433
|
+
dst.put(node);
|
|
1434
|
+
if (node.kind === "tree")
|
|
1435
|
+
for (const e of Object.values(node.entries))
|
|
1436
|
+
stack.push(e.hash);
|
|
1437
|
+
}
|
|
1438
|
+
return root;
|
|
1439
|
+
}
|
|
1440
|
+
async function persist(sync, dst, root) {
|
|
1441
|
+
const stack = [root];
|
|
1442
|
+
const seen = new Set;
|
|
1443
|
+
while (stack.length) {
|
|
1444
|
+
const h = stack.pop();
|
|
1445
|
+
if (seen.has(h))
|
|
1446
|
+
continue;
|
|
1447
|
+
seen.add(h);
|
|
1448
|
+
const node = sync.get(h);
|
|
1449
|
+
if (!node)
|
|
1450
|
+
continue;
|
|
1451
|
+
if (!await dst.has(h))
|
|
1452
|
+
await dst.put(node);
|
|
1453
|
+
if (node.kind === "tree")
|
|
1454
|
+
for (const e of Object.values(node.entries))
|
|
1455
|
+
stack.push(e.hash);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
async function converge(local, input) {
|
|
1459
|
+
const at = input.at ?? Date.now();
|
|
1460
|
+
const actor = input.actor ?? "system";
|
|
1461
|
+
for (const node of input.nodes)
|
|
1462
|
+
await local.store.put(node);
|
|
1463
|
+
const priorHead = await local.log.head();
|
|
1464
|
+
const incomingHead = input.incomingHead ?? input.ops[input.ops.length - 1]?.rootAfter;
|
|
1465
|
+
const sinceSeq = await local.log.seq();
|
|
1466
|
+
const existing = await local.log.history();
|
|
1467
|
+
const priorAncestry = buildAncestry(existing);
|
|
1468
|
+
const knownByEntry = new Map(existing.filter((o) => o.entryHash).map((o) => [o.entryHash, o]));
|
|
1469
|
+
const knownByTuple = new Map(existing.map((o) => [`${o.type}\x00${o.path}\x00${o.rootAfter}`, o]));
|
|
1470
|
+
const incoming = [...input.ops].sort((a, b) => a.seq - b.seq);
|
|
1471
|
+
let forkBase = input.base ?? priorHead;
|
|
1472
|
+
if (input.base === undefined) {
|
|
1473
|
+
for (const op of incoming) {
|
|
1474
|
+
const k = op.entryHash ? knownByEntry.get(op.entryHash) : knownByTuple.get(`${op.type}\x00${op.path}\x00${op.rootAfter}`);
|
|
1475
|
+
if (k)
|
|
1476
|
+
forkBase = k.rootAfter;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
let applied = 0;
|
|
1480
|
+
for (const op of incoming) {
|
|
1481
|
+
if (op.entryHash && knownByEntry.has(op.entryHash))
|
|
1482
|
+
continue;
|
|
1483
|
+
if (!op.entryHash && knownByTuple.has(`${op.type}\x00${op.path}\x00${op.rootAfter}`))
|
|
1484
|
+
continue;
|
|
1485
|
+
const stamped = applied === 0 && op.type !== "checkpoint" && op.parent === undefined ? { ...op, parent: forkBase } : { ...op };
|
|
1486
|
+
await local.log.append({ ...stamped, seq: await local.log.seq() + 1 });
|
|
1487
|
+
applied++;
|
|
1488
|
+
}
|
|
1489
|
+
let head = await local.log.head() ?? priorHead ?? await local.store.put(EMPTY_TREE2);
|
|
1490
|
+
let merged = false;
|
|
1491
|
+
let conflicts = [];
|
|
1492
|
+
if (priorHead && incomingHead && priorHead !== incomingHead) {
|
|
1493
|
+
const parents = buildAncestry(await local.log.history());
|
|
1494
|
+
for (const [c, ps] of priorAncestry) {
|
|
1495
|
+
let s = parents.get(c);
|
|
1496
|
+
if (!s)
|
|
1497
|
+
parents.set(c, s = new Set);
|
|
1498
|
+
for (const p of ps)
|
|
1499
|
+
s.add(p);
|
|
1500
|
+
}
|
|
1501
|
+
const priorIsAncestor = ancestorsOf(incomingHead, parents).has(priorHead);
|
|
1502
|
+
const incomingIsAncestor = ancestorsOf(priorHead, parents).has(incomingHead);
|
|
1503
|
+
if (incomingIsAncestor) {
|
|
1504
|
+
if (await local.log.head() !== priorHead)
|
|
1505
|
+
await appendHeadMove(local, priorHead, priorHead, incomingHead, actor, at, "converge: keep ahead-of-incoming head");
|
|
1506
|
+
head = priorHead;
|
|
1507
|
+
} else if (!priorIsAncestor) {
|
|
1508
|
+
const base = input.base ?? mergeBase(priorHead, incomingHead, parents);
|
|
1509
|
+
const sync = new Store;
|
|
1510
|
+
const baseRoot = await hydrate(local.store, sync, base);
|
|
1511
|
+
const oursRoot = await hydrate(local.store, sync, priorHead);
|
|
1512
|
+
const theirsRoot = await hydrate(local.store, sync, incomingHead);
|
|
1513
|
+
const result = merge({ store: sync }, baseRoot, oursRoot, theirsRoot);
|
|
1514
|
+
conflicts = result.conflicts;
|
|
1515
|
+
await persist(sync, local.store, result.head);
|
|
1516
|
+
await local.log.append({
|
|
1517
|
+
seq: await local.log.seq() + 1,
|
|
1518
|
+
type: "checkpoint",
|
|
1519
|
+
path: "",
|
|
1520
|
+
rootAfter: result.head,
|
|
1521
|
+
at,
|
|
1522
|
+
by: actor,
|
|
1523
|
+
message: conflicts.length ? `converge merge (${conflicts.length} conflict(s) kept with markers)` : "converge merge",
|
|
1524
|
+
parent: priorHead,
|
|
1525
|
+
parent2: incomingHead
|
|
1526
|
+
});
|
|
1527
|
+
head = result.head;
|
|
1528
|
+
merged = true;
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
const finalOps = await local.log.history();
|
|
1532
|
+
const missingOps = finalOps.filter((o) => o.seq > sinceSeq);
|
|
1533
|
+
return { head, applied, merged, conflicts, missingOps };
|
|
1534
|
+
}
|
|
1535
|
+
async function appendHeadMove(local, head, parent, parent2, by, at, message) {
|
|
1536
|
+
await local.log.append({ seq: await local.log.seq() + 1, type: "checkpoint", path: "", rootAfter: head, at, by, message, parent, parent2 });
|
|
1537
|
+
}
|
|
1362
1538
|
// src/diff.ts
|
|
1363
1539
|
function diffTrees(store, aHead, bHead) {
|
|
1364
1540
|
const aPaths = new Set(listAll(store, aHead));
|
|
@@ -2004,6 +2180,7 @@ export {
|
|
|
2004
2180
|
diffTrees,
|
|
2005
2181
|
diffFile,
|
|
2006
2182
|
deleteFile,
|
|
2183
|
+
converge,
|
|
2007
2184
|
compact,
|
|
2008
2185
|
clone,
|
|
2009
2186
|
ciphertextOf,
|