@ricsam/r5d-worker 0.0.97 → 0.0.100
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/cjs/main.cjs +37 -5
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/working-tree-mirror.cjs +256 -4
- package/dist/cjs/workspace-git-sync.cjs +179 -87
- package/dist/mjs/main.mjs +37 -5
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/working-tree-mirror.mjs +250 -3
- package/dist/mjs/workspace-git-sync.mjs +188 -88
- package/dist/types/main.d.ts +181 -0
- package/dist/types/working-tree-mirror.d.ts +80 -1
- package/dist/types/workspace-git-sync.d.ts +25 -0
- package/package.json +1 -1
|
@@ -954,7 +954,7 @@ function parseHydrationTransactionManifest(transactionPath) {
|
|
|
954
954
|
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
955
955
|
}
|
|
956
956
|
const candidate = parsed;
|
|
957
|
-
if (candidate.version !== 2 && candidate.version !== 3 || !Array.isArray(candidate.mounts)) {
|
|
957
|
+
if (candidate.version !== 2 && candidate.version !== 3 && candidate.version !== 4 || !Array.isArray(candidate.mounts)) {
|
|
958
958
|
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
959
959
|
}
|
|
960
960
|
const parseReceipt = (rawReceipt) => {
|
|
@@ -967,6 +967,25 @@ function parseHydrationTransactionManifest(transactionPath) {
|
|
|
967
967
|
};
|
|
968
968
|
const targetReceipt = parseReceipt(candidate.targetReceipt);
|
|
969
969
|
const receiptBefore = parseReceipt(candidate.receiptBefore);
|
|
970
|
+
const parseScope = (rawScope, existed) => {
|
|
971
|
+
if (rawScope === void 0) return void 0;
|
|
972
|
+
if (candidate.version !== 4 || !existed || !rawScope || typeof rawScope !== "object") {
|
|
973
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
974
|
+
}
|
|
975
|
+
const parsePaths = (rawPaths) => {
|
|
976
|
+
if (!Array.isArray(rawPaths)) throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
977
|
+
const seen = /* @__PURE__ */ new Set();
|
|
978
|
+
return rawPaths.map((rawPath) => {
|
|
979
|
+
if (typeof rawPath !== "string" || (0, import_working_tree_mirror.normalizeWorkingTreeRelativePath)(rawPath) !== rawPath || seen.has(rawPath)) {
|
|
980
|
+
throw new Error(`Workspace hydration transaction manifest is invalid: ${manifestPath}`);
|
|
981
|
+
}
|
|
982
|
+
seen.add(rawPath);
|
|
983
|
+
return rawPath;
|
|
984
|
+
});
|
|
985
|
+
};
|
|
986
|
+
const { absent, trees } = rawScope;
|
|
987
|
+
return { absent: parsePaths(absent), trees: parsePaths(trees) };
|
|
988
|
+
};
|
|
970
989
|
const seenIds = /* @__PURE__ */ new Set();
|
|
971
990
|
const seenSnapshots = /* @__PURE__ */ new Set();
|
|
972
991
|
const mounts = candidate.mounts.map((rawMount) => {
|
|
@@ -983,10 +1002,20 @@ function parseHydrationTransactionManifest(transactionPath) {
|
|
|
983
1002
|
}
|
|
984
1003
|
seenIds.add(mount.id);
|
|
985
1004
|
seenSnapshots.add(mount.snapshotName);
|
|
986
|
-
|
|
1005
|
+
const scope = parseScope(rawMount.scope, mount.existed);
|
|
1006
|
+
return {
|
|
1007
|
+
id: mount.id,
|
|
1008
|
+
incarnationKey: mount.incarnationKey,
|
|
1009
|
+
sourcePath: mount.sourcePath,
|
|
1010
|
+
durabilityRootPath: mount.durabilityRootPath,
|
|
1011
|
+
workspaceRelativePath: mount.workspaceRelativePath,
|
|
1012
|
+
existed: mount.existed,
|
|
1013
|
+
snapshotName: mount.snapshotName,
|
|
1014
|
+
...scope ? { scope } : {}
|
|
1015
|
+
};
|
|
987
1016
|
});
|
|
988
1017
|
return {
|
|
989
|
-
version: 3,
|
|
1018
|
+
version: candidate.version === 4 ? 4 : 3,
|
|
990
1019
|
targetReceipt,
|
|
991
1020
|
receiptBefore,
|
|
992
1021
|
mounts
|
|
@@ -1008,6 +1037,7 @@ function hydrationTransactionCommitted(transactionPath) {
|
|
|
1008
1037
|
}
|
|
1009
1038
|
function restoreHydrationTransaction(workspacePath, manifest) {
|
|
1010
1039
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1040
|
+
const durabilityScopes = /* @__PURE__ */ new Map();
|
|
1011
1041
|
for (const mount of manifest.mounts) {
|
|
1012
1042
|
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
1013
1043
|
trustedRoot: mount.durabilityRootPath,
|
|
@@ -1023,6 +1053,11 @@ function restoreHydrationTransaction(workspacePath, manifest) {
|
|
|
1023
1053
|
if (!snapshotStatus.isDirectory() || snapshotStatus.isSymbolicLink()) {
|
|
1024
1054
|
throw new Error(`Workspace hydration snapshot is not a regular directory: ${snapshotPath}`);
|
|
1025
1055
|
}
|
|
1056
|
+
if (mount.scope) {
|
|
1057
|
+
const restored = (0, import_working_tree_mirror.restoreWorkingTreeMirrorScope)({ snapshotRoot: snapshotPath, targetRoot: mount.sourcePath, scope: mount.scope });
|
|
1058
|
+
durabilityScopes.set(mount.id, restored.paths);
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1026
1061
|
(0, import_working_tree_mirror.mirrorWorkingTree)({ sourceRoot: snapshotPath, targetRoot: mount.sourcePath, sourceMode: "all", deletionMode: "all" });
|
|
1027
1062
|
}
|
|
1028
1063
|
fsyncHydratedWorkspaceMounts(
|
|
@@ -1031,7 +1066,8 @@ function restoreHydrationTransaction(workspacePath, manifest) {
|
|
|
1031
1066
|
hydrationIncarnationKey: mount.incarnationKey,
|
|
1032
1067
|
sourceMode: "all",
|
|
1033
1068
|
hydrateDeletionMode: "all"
|
|
1034
|
-
}))
|
|
1069
|
+
})),
|
|
1070
|
+
durabilityScopes
|
|
1035
1071
|
);
|
|
1036
1072
|
}
|
|
1037
1073
|
function recoverPendingHydrationTransaction(workspacePath) {
|
|
@@ -1075,57 +1111,80 @@ function beginHydrationTransaction(workspacePath, mounts, targetReceipt) {
|
|
|
1075
1111
|
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1076
1112
|
const stagingPath = import_node_path.default.join(stateDirectory, `.workspace-hydration-transaction.${process.pid}.${crypto.randomUUID()}.tmp`);
|
|
1077
1113
|
import_node_fs.default.mkdirSync(stagingPath, { mode: 448 });
|
|
1114
|
+
const durabilityScopes = /* @__PURE__ */ new Map();
|
|
1078
1115
|
try {
|
|
1079
1116
|
const snapshotRoot = import_node_path.default.join(stagingPath, "mounts");
|
|
1080
1117
|
import_node_fs.default.mkdirSync(snapshotRoot, { mode: 448 });
|
|
1081
|
-
const
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1118
|
+
const receiptBefore = readHydratedWorkspaceReceipt(workspacePath);
|
|
1119
|
+
const manifestMounts = mounts.map((mount, index) => {
|
|
1120
|
+
const sourcePath = import_node_path.default.resolve(mount.sourcePath);
|
|
1121
|
+
const durabilityRootPath = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
1122
|
+
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
1123
|
+
trustedRoot: durabilityRootPath,
|
|
1124
|
+
candidate: sourcePath,
|
|
1125
|
+
label: `Workspace hydration snapshot mount ${mount.id}`
|
|
1126
|
+
});
|
|
1127
|
+
const sourceStatus = lstatIfExists(sourcePath);
|
|
1128
|
+
if (sourceStatus && (!sourceStatus.isDirectory() || sourceStatus.isSymbolicLink())) {
|
|
1129
|
+
throw new Error(`Workspace hydration snapshot source is not a regular directory: ${sourcePath}`);
|
|
1130
|
+
}
|
|
1131
|
+
const snapshotName = String(index);
|
|
1132
|
+
const snapshotPath = import_node_path.default.join(snapshotRoot, snapshotName);
|
|
1133
|
+
import_node_fs.default.mkdirSync(snapshotPath, { mode: 448 });
|
|
1134
|
+
let scope;
|
|
1135
|
+
if (sourceStatus && mount.hydrateDeletionMode === "git") {
|
|
1136
|
+
const outerPath = workspaceMountOuterPath(workspacePath, mount);
|
|
1088
1137
|
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
1089
|
-
trustedRoot:
|
|
1090
|
-
candidate:
|
|
1091
|
-
label: `Workspace
|
|
1138
|
+
trustedRoot: workspacePath,
|
|
1139
|
+
candidate: outerPath,
|
|
1140
|
+
label: `Workspace mount ${mount.id} outer source`
|
|
1092
1141
|
});
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1142
|
+
const plan = (0, import_working_tree_mirror.planWorkingTreeMirror)({ sourceRoot: outerPath, targetRoot: sourcePath, sourceMode: "all", deletionMode: "git" });
|
|
1143
|
+
scope = (0, import_working_tree_mirror.captureWorkingTreeMirrorPlan)({
|
|
1144
|
+
plan,
|
|
1145
|
+
targetRoot: sourcePath,
|
|
1146
|
+
snapshotRoot: snapshotPath,
|
|
1147
|
+
// Same private-staging deferral as the complete snapshot below.
|
|
1148
|
+
durability: "deferred_private_staging"
|
|
1149
|
+
});
|
|
1150
|
+
durabilityScopes.set(mount.id, [...plan.desired.keys()]);
|
|
1151
|
+
} else if (sourceStatus) {
|
|
1152
|
+
(0, import_working_tree_mirror.mirrorWorkingTree)({
|
|
1153
|
+
sourceRoot: sourcePath,
|
|
1154
|
+
targetRoot: snapshotPath,
|
|
1155
|
+
sourceMode: "all",
|
|
1156
|
+
deletionMode: "all",
|
|
1157
|
+
// This tree is still private and the source authority is untouched.
|
|
1158
|
+
// The single recursive barrier below makes the complete snapshot
|
|
1159
|
+
// durable before its directory entry is published or hydration starts.
|
|
1160
|
+
durability: "deferred_private_staging"
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
return {
|
|
1164
|
+
id: mount.id,
|
|
1165
|
+
incarnationKey: mount.hydrationIncarnationKey,
|
|
1166
|
+
sourcePath,
|
|
1167
|
+
durabilityRootPath,
|
|
1168
|
+
workspaceRelativePath: mount.workspaceRelativePath,
|
|
1169
|
+
existed: Boolean(sourceStatus),
|
|
1170
|
+
snapshotName,
|
|
1171
|
+
...scope ? { scope } : {}
|
|
1172
|
+
};
|
|
1173
|
+
});
|
|
1174
|
+
const manifest = {
|
|
1175
|
+
// Older workers reject version 4, so a scoped snapshot can never be
|
|
1176
|
+
// replayed as a complete copy after a downgrade.
|
|
1177
|
+
version: manifestMounts.some((mount) => mount.scope) ? 4 : 3,
|
|
1178
|
+
targetReceipt,
|
|
1179
|
+
receiptBefore,
|
|
1180
|
+
mounts: manifestMounts
|
|
1122
1181
|
};
|
|
1123
1182
|
writePrivateFileDurably(import_node_path.default.join(stagingPath, "manifest.json"), `${JSON.stringify(manifest)}
|
|
1124
1183
|
`);
|
|
1125
1184
|
fsyncTree(stagingPath);
|
|
1126
1185
|
import_node_fs.default.renameSync(stagingPath, transactionPath);
|
|
1127
1186
|
fsyncDirectory(stateDirectory);
|
|
1128
|
-
return manifest;
|
|
1187
|
+
return { manifest, durabilityScopes };
|
|
1129
1188
|
} catch (error) {
|
|
1130
1189
|
import_node_fs.default.rmSync(stagingPath, { recursive: true, force: true });
|
|
1131
1190
|
fsyncDirectory(stateDirectory);
|
|
@@ -1137,7 +1196,11 @@ function markHydrationTransactionDurable(workspacePath) {
|
|
|
1137
1196
|
writePrivateFileDurably(import_node_path.default.join(transactionPath, "committed"), "committed\n");
|
|
1138
1197
|
fsyncDirectory(transactionPath);
|
|
1139
1198
|
}
|
|
1140
|
-
function
|
|
1199
|
+
function gitVisibleDurabilityPaths(mount, sourcePath, sourceStatus) {
|
|
1200
|
+
if (mount.hydrateDeletionMode !== "git" || !sourceStatus.isDirectory() || !lstatIfExists(import_node_path.default.join(sourcePath, ".git"))) return null;
|
|
1201
|
+
return [...(0, import_working_tree_mirror.inspectWorkingTree)(sourcePath, "git").keys()];
|
|
1202
|
+
}
|
|
1203
|
+
function fsyncHydratedWorkspaceMount(mount, scope) {
|
|
1141
1204
|
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
1142
1205
|
trustedRoot: mount.durabilityRootPath,
|
|
1143
1206
|
candidate: mount.sourcePath,
|
|
@@ -1162,7 +1225,17 @@ function fsyncHydratedWorkspaceMount(mount) {
|
|
|
1162
1225
|
import_node_fs.default.closeSync(descriptor);
|
|
1163
1226
|
}
|
|
1164
1227
|
};
|
|
1165
|
-
|
|
1228
|
+
const sourcePath = import_node_path.default.resolve(mount.sourcePath);
|
|
1229
|
+
const sourceStatus = lstatIfExists(sourcePath);
|
|
1230
|
+
if (sourceStatus) {
|
|
1231
|
+
const paths = scope ?? gitVisibleDurabilityPaths(mount, sourcePath, sourceStatus);
|
|
1232
|
+
if (paths) {
|
|
1233
|
+
if (!sourceStatus.isDirectory()) throw new Error(`Workspace mount ${mount.id} source is not a regular directory: ${sourcePath}`);
|
|
1234
|
+
(0, import_working_tree_mirror.fsyncWorkingTreePaths)(sourcePath, paths);
|
|
1235
|
+
} else {
|
|
1236
|
+
syncEntry(sourcePath, true);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1166
1239
|
const durabilityRoot = import_node_path.default.resolve(mount.durabilityRootPath);
|
|
1167
1240
|
let boundaryParent = import_node_path.default.dirname(import_node_path.default.resolve(mount.sourcePath));
|
|
1168
1241
|
while (true) {
|
|
@@ -1181,8 +1254,8 @@ function fsyncHydratedWorkspaceMount(mount) {
|
|
|
1181
1254
|
boundaryParent = nextParent;
|
|
1182
1255
|
}
|
|
1183
1256
|
}
|
|
1184
|
-
function fsyncHydratedWorkspaceMounts(mounts) {
|
|
1185
|
-
for (const mount of mounts) fsyncHydratedWorkspaceMount(mount);
|
|
1257
|
+
function fsyncHydratedWorkspaceMounts(mounts, scopes = /* @__PURE__ */ new Map()) {
|
|
1258
|
+
for (const mount of mounts) fsyncHydratedWorkspaceMount(mount, scopes.get(mount.id));
|
|
1186
1259
|
}
|
|
1187
1260
|
function workspaceIsShallow(workspacePath) {
|
|
1188
1261
|
return gitResult(workspacePath, ["rev-parse", "--is-shallow-repository"]).stdout === "true";
|
|
@@ -1431,6 +1504,9 @@ function restoreDeferredWorkspaceMountsFromHead(workspacePath, mounts) {
|
|
|
1431
1504
|
}
|
|
1432
1505
|
}
|
|
1433
1506
|
}
|
|
1507
|
+
function workspaceMountOuterPath(workspacePath, mount) {
|
|
1508
|
+
return import_node_path.default.join(workspacePath, ...mount.workspaceRelativePath.replace(/\\/g, "/").split("/"));
|
|
1509
|
+
}
|
|
1434
1510
|
function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
1435
1511
|
const hydratedMountIds = [];
|
|
1436
1512
|
const skippedMountIds = [];
|
|
@@ -1439,7 +1515,7 @@ function hydrateWorkspaceGitMountsRaw(workspacePath, mounts, options = {}) {
|
|
|
1439
1515
|
skippedMountIds.push(mount.id);
|
|
1440
1516
|
continue;
|
|
1441
1517
|
}
|
|
1442
|
-
const sourceRoot =
|
|
1518
|
+
const sourceRoot = workspaceMountOuterPath(workspacePath, mount);
|
|
1443
1519
|
(0, import_workspace_mount_boundary.assertManagedDirectoryPath)({
|
|
1444
1520
|
trustedRoot: workspacePath,
|
|
1445
1521
|
candidate: sourceRoot,
|
|
@@ -1525,56 +1601,67 @@ async function hydrateWorkspaceGitMountsTransactionally(input) {
|
|
|
1525
1601
|
const busyMountIds = new Set(input.ignoreBusy ? [] : candidateMounts.filter((mount) => mount.busy?.()).map(({ id }) => id));
|
|
1526
1602
|
const requestedIds = new Set(input.mounts.map(({ id }) => id));
|
|
1527
1603
|
const requiredIds = new Set(requiredMounts.map(({ id }) => id));
|
|
1528
|
-
const
|
|
1529
|
-
const
|
|
1604
|
+
const hydrationMounts = input.mounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1605
|
+
const advancedDurabilityMounts = durabilityMounts.filter(
|
|
1530
1606
|
({ id }) => !busyMountIds.has(id) && (requestedIds.has(id) || !requiredIds.has(id))
|
|
1531
1607
|
);
|
|
1532
1608
|
const unavailableRequiredIds = requiredMounts.filter(({ id }) => busyMountIds.has(id) || !requestedIds.has(id)).map(({ id }) => id);
|
|
1533
1609
|
const carriedMounts = carryMounts.filter(({ id }) => !busyMountIds.has(id));
|
|
1534
1610
|
return {
|
|
1535
1611
|
busyMountIds,
|
|
1536
|
-
hydrationMounts
|
|
1537
|
-
advancedDurabilityMounts
|
|
1612
|
+
hydrationMounts,
|
|
1613
|
+
advancedDurabilityMounts,
|
|
1538
1614
|
skippedMountIds: [.../* @__PURE__ */ new Set([...busyMountIds, ...unavailableRequiredIds])].sort(),
|
|
1539
|
-
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, [...
|
|
1615
|
+
targetReceipt: targetHead ? receiptWithMountsAtHead(receiptBeforeTransaction, targetHead, [...advancedDurabilityMounts, ...carriedMounts], receiptMounts) : null
|
|
1540
1616
|
};
|
|
1541
1617
|
};
|
|
1542
1618
|
const noopCandidate = computeSelection();
|
|
1543
1619
|
if (noopCandidate.hydrationMounts.length === 0 && noopCandidate.advancedDurabilityMounts.length === 0 && noopCandidate.targetReceipt && lstatIfExists(hydrationTransactionPath(workspacePath)) === null && workspaceHydrationReceiptsEqual(receiptBeforeTransaction, noopCandidate.targetReceipt) && hydratedWorkspaceReceiptFileIsV3(workspacePath) && await workspaceBasisRefsAreCurrentAsync(workspacePath, noopCandidate.targetReceipt)) {
|
|
1544
1620
|
return { hydratedMountIds: [], skippedMountIds: noopCandidate.skippedMountIds };
|
|
1545
1621
|
}
|
|
1546
|
-
const
|
|
1547
|
-
const
|
|
1548
|
-
|
|
1622
|
+
const hooks = input.hydrationHooks;
|
|
1623
|
+
const context = {
|
|
1624
|
+
transactionId: crypto.randomUUID(),
|
|
1625
|
+
workspacePath,
|
|
1626
|
+
mountIds: [...new Set([...noopCandidate.hydrationMounts, ...noopCandidate.advancedDurabilityMounts].map(({ id }) => id))].sort()
|
|
1627
|
+
};
|
|
1628
|
+
if (hooks) await hooks.beforeTransaction(context);
|
|
1549
1629
|
try {
|
|
1550
|
-
const
|
|
1551
|
-
const
|
|
1552
|
-
|
|
1553
|
-
throw new Error("Workspace hydration did not include every required mount");
|
|
1554
|
-
}
|
|
1555
|
-
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts);
|
|
1556
|
-
markHydrationTransactionDurable(workspacePath);
|
|
1557
|
-
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1558
|
-
removeHydrationTransaction(workspacePath);
|
|
1559
|
-
return {
|
|
1560
|
-
hydratedMountIds: hydration.hydratedMountIds,
|
|
1561
|
-
skippedMountIds: selection.skippedMountIds
|
|
1562
|
-
};
|
|
1563
|
-
} catch (error) {
|
|
1564
|
-
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1565
|
-
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1566
|
-
throw error;
|
|
1567
|
-
}
|
|
1630
|
+
const selection = computeSelection();
|
|
1631
|
+
const { hydrationMounts, advancedDurabilityMounts, targetReceipt } = selection;
|
|
1632
|
+
const { manifest, durabilityScopes } = beginHydrationTransaction(workspacePath, hydrationMounts, targetReceipt);
|
|
1568
1633
|
try {
|
|
1569
|
-
|
|
1634
|
+
const hydration = hydrateWorkspaceGitMountsRaw(workspacePath, hydrationMounts, { ignoreBusy: true });
|
|
1635
|
+
const expectedHydratedIds = hydrationMounts.map(({ id }) => id).sort();
|
|
1636
|
+
if (hydration.skippedMountIds.length > 0 || hydration.hydratedMountIds.length !== expectedHydratedIds.length || hydration.hydratedMountIds.some((id, index) => id !== expectedHydratedIds[index])) {
|
|
1637
|
+
throw new Error("Workspace hydration did not include every required mount");
|
|
1638
|
+
}
|
|
1639
|
+
fsyncHydratedWorkspaceMounts(advancedDurabilityMounts, durabilityScopes);
|
|
1640
|
+
markHydrationTransactionDurable(workspacePath);
|
|
1641
|
+
if (targetReceipt) updateHydratedWorkspaceReceipt(workspacePath, targetReceipt);
|
|
1570
1642
|
removeHydrationTransaction(workspacePath);
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1643
|
+
return {
|
|
1644
|
+
hydratedMountIds: hydration.hydratedMountIds,
|
|
1645
|
+
skippedMountIds: selection.skippedMountIds
|
|
1646
|
+
};
|
|
1647
|
+
} catch (error) {
|
|
1648
|
+
const transactionPath = hydrationTransactionPath(workspacePath);
|
|
1649
|
+
if (lstatIfExists(transactionPath) && hydrationTransactionCommitted(transactionPath)) {
|
|
1650
|
+
throw error;
|
|
1651
|
+
}
|
|
1652
|
+
try {
|
|
1653
|
+
restoreHydrationTransaction(workspacePath, manifest);
|
|
1654
|
+
removeHydrationTransaction(workspacePath);
|
|
1655
|
+
} catch (restoreError) {
|
|
1656
|
+
throw new AggregateError(
|
|
1657
|
+
[error, restoreError],
|
|
1658
|
+
`Workspace hydration failed and its durable snapshot could not be restored: ${error instanceof Error ? error.message : String(error)}`
|
|
1659
|
+
);
|
|
1660
|
+
}
|
|
1661
|
+
throw error;
|
|
1576
1662
|
}
|
|
1577
|
-
|
|
1663
|
+
} finally {
|
|
1664
|
+
hooks?.afterTransaction(context);
|
|
1578
1665
|
}
|
|
1579
1666
|
}
|
|
1580
1667
|
async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
@@ -1586,7 +1673,8 @@ async function hydrateWorkspaceGitMounts(workspacePath, mounts, options = {}) {
|
|
|
1586
1673
|
return hydrateWorkspaceGitMountsTransactionally({
|
|
1587
1674
|
workspacePath,
|
|
1588
1675
|
mounts,
|
|
1589
|
-
ignoreBusy: options.ignoreBusy
|
|
1676
|
+
ignoreBusy: options.ignoreBusy,
|
|
1677
|
+
hydrationHooks: options.hydrationHooks
|
|
1590
1678
|
});
|
|
1591
1679
|
}
|
|
1592
1680
|
async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {}) {
|
|
@@ -1626,7 +1714,8 @@ async function recoverWorkspaceGitHydration(workspacePath, mounts, options = {})
|
|
|
1626
1714
|
receiptMounts: configuredBasisMounts,
|
|
1627
1715
|
requiredMounts: hydrationTargets,
|
|
1628
1716
|
ignoreBusy: options.ignoreBusy,
|
|
1629
|
-
recordCurrentHead: true
|
|
1717
|
+
recordCurrentHead: true,
|
|
1718
|
+
hydrationHooks: options.hydrationHooks
|
|
1630
1719
|
});
|
|
1631
1720
|
void hydration;
|
|
1632
1721
|
}
|
|
@@ -1677,7 +1766,8 @@ async function resetWorkspaceGit(input) {
|
|
|
1677
1766
|
durabilityMounts: [...requiredLiveMounts, ...selected.tombstones, ...durablyAbsentMounts],
|
|
1678
1767
|
receiptMounts: input.mounts,
|
|
1679
1768
|
requiredMounts: requiredLiveMounts,
|
|
1680
|
-
recordCurrentHead: true
|
|
1769
|
+
recordCurrentHead: true,
|
|
1770
|
+
hydrationHooks: input.hydrationHooks
|
|
1681
1771
|
});
|
|
1682
1772
|
const requiredLiveIds = requiredLiveMounts.map(({ id }) => id).sort();
|
|
1683
1773
|
const resetHead = revParse(workspacePath, "HEAD");
|
|
@@ -1932,7 +2022,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
1932
2022
|
await recoverWorkspaceGitHydration(workspacePath, input.mounts, {
|
|
1933
2023
|
...deferredMountIds.size > 0 ? { deferMountIds: deferredMountIds } : {},
|
|
1934
2024
|
preserveResolutionInProgress,
|
|
1935
|
-
preserveStaleBases: true
|
|
2025
|
+
preserveStaleBases: true,
|
|
2026
|
+
hydrationHooks: input.hydrationHooks
|
|
1936
2027
|
});
|
|
1937
2028
|
const selected = activeMounts(input.mounts);
|
|
1938
2029
|
const projectionMutationTokens = new Map(
|
|
@@ -2058,7 +2149,8 @@ async function synchronizeWorkspaceGit(input) {
|
|
|
2058
2149
|
carryMounts: uncoveredCompletionMounts.filter(({ id }) => unchangedMountIds.has(id)),
|
|
2059
2150
|
receiptMounts: completionReceiptMounts,
|
|
2060
2151
|
requiredMounts: uncoveredRequiredLiveMounts.filter(({ id }) => !unchangedMountIds.has(id)),
|
|
2061
|
-
recordCurrentHead: true
|
|
2152
|
+
recordCurrentHead: true,
|
|
2153
|
+
hydrationHooks: input.hydrationHooks
|
|
2062
2154
|
});
|
|
2063
2155
|
for (const id of hydration.skippedMountIds) cycleSkippedMountIds.add(id);
|
|
2064
2156
|
refreshCycleSkippedMounts(completionReceiptMounts);
|
package/dist/mjs/main.mjs
CHANGED
|
@@ -104,9 +104,35 @@ function workerServerHeartbeatAction(input) {
|
|
|
104
104
|
if (input.watchdogSweepDelayed || input.probeElapsedMs === void 0) return "start_probe";
|
|
105
105
|
return input.probeElapsedMs >= input.timeoutMs ? "terminate" : "wait";
|
|
106
106
|
}
|
|
107
|
+
const WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX = "hydration:";
|
|
108
|
+
function createWorkspaceHydrationLeaseHooks(send, options = {}) {
|
|
109
|
+
const yieldToEventLoop = options.yieldToEventLoop ?? (() => new Promise((resolve) => setImmediate(resolve)));
|
|
110
|
+
const log = options.log ?? ((line) => process.stdout.write(`${line}
|
|
111
|
+
`));
|
|
112
|
+
const now = options.now ?? (() => performance.now());
|
|
113
|
+
const operationId = (context) => `${WORKSPACE_HYDRATION_LEASE_OPERATION_PREFIX}${context.transactionId}`;
|
|
114
|
+
const startedAtMs = /* @__PURE__ */ new Map();
|
|
115
|
+
return {
|
|
116
|
+
async beforeTransaction(context) {
|
|
117
|
+
send({ type: "heartbeat_lease", operationId: operationId(context), kind: "hydration", active: true });
|
|
118
|
+
startedAtMs.set(context.transactionId, now());
|
|
119
|
+
log(`[r5d-worker] hydrating ${context.mountIds.length} workspace mount(s) under hydration lease ${context.transactionId}`);
|
|
120
|
+
await yieldToEventLoop();
|
|
121
|
+
},
|
|
122
|
+
afterTransaction(context) {
|
|
123
|
+
const startedAt = startedAtMs.get(context.transactionId);
|
|
124
|
+
startedAtMs.delete(context.transactionId);
|
|
125
|
+
send({ type: "heartbeat_lease", operationId: operationId(context), kind: "hydration", active: false });
|
|
126
|
+
log(
|
|
127
|
+
`[r5d-worker] released hydration lease ${context.transactionId} after ${startedAt === void 0 ? "?" : Math.round(now() - startedAt)}ms`
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
107
132
|
const workerLifecycleTestHarness = {
|
|
108
133
|
boundedWorkerLifecycleProgress,
|
|
109
|
-
workerServerHeartbeatAction
|
|
134
|
+
workerServerHeartbeatAction,
|
|
135
|
+
createWorkspaceHydrationLeaseHooks
|
|
110
136
|
};
|
|
111
137
|
class ProjectWorkspaceConfigurationDeferredError extends Error {
|
|
112
138
|
}
|
|
@@ -4377,6 +4403,7 @@ async function startWorker(options) {
|
|
|
4377
4403
|
error: error instanceof Error ? error.message : String(error)
|
|
4378
4404
|
};
|
|
4379
4405
|
};
|
|
4406
|
+
const workspaceHydrationTransactionHooks = createWorkspaceHydrationLeaseHooks((message) => sendWorkerMessageFromCurrentSource(ws, message));
|
|
4380
4407
|
const performWorkspaceSync = async (input) => {
|
|
4381
4408
|
if (!workspaceRemoteUrl || !workspaceCredentialHelper || !workspaceCredentialUsername || !workspaceGitIdentity) {
|
|
4382
4409
|
throw new Error("Worker workspace configuration has not been received");
|
|
@@ -4399,7 +4426,8 @@ async function startWorker(options) {
|
|
|
4399
4426
|
credentialHelper: workspaceCredentialHelper,
|
|
4400
4427
|
credentialUsername: workspaceCredentialUsername,
|
|
4401
4428
|
gitIdentity: workspaceGitIdentity,
|
|
4402
|
-
mounts: configuredMounts
|
|
4429
|
+
mounts: configuredMounts,
|
|
4430
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4403
4431
|
});
|
|
4404
4432
|
await applyObservedProjectHeadsAfterInboundWorkspace(observedProjectHeads2, reset.activeMountIds, true);
|
|
4405
4433
|
return {
|
|
@@ -4453,7 +4481,8 @@ async function startWorker(options) {
|
|
|
4453
4481
|
assertStillAdmitted: input.assertStillAdmitted,
|
|
4454
4482
|
afterWorkspacePublished: outerRemediation ? void 0 : async ({ activeMountIds, publishedHead }) => {
|
|
4455
4483
|
await pushChangedProjectHeads(activeMountIds, publishedHead, publicationHeads, expectedMirrorHeads, inboundMoveMountIds);
|
|
4456
|
-
}
|
|
4484
|
+
},
|
|
4485
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
4457
4486
|
});
|
|
4458
4487
|
input.assertStillAdmitted?.();
|
|
4459
4488
|
if (result.initialPublicationMountIds && result.initialPublicationMountIds.length > 0) {
|
|
@@ -5047,7 +5076,10 @@ async function startWorker(options) {
|
|
|
5047
5076
|
sourcePath: recoverySourceOverrides.get(mount.id) ?? mount.sourcePath,
|
|
5048
5077
|
busy: mount.busyForRecovery ?? mount.busy
|
|
5049
5078
|
}));
|
|
5050
|
-
await recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, {
|
|
5079
|
+
await recoverWorkspaceGitHydration(workspaceShadowRoot, recoveryMounts, {
|
|
5080
|
+
preserveStaleBases: true,
|
|
5081
|
+
hydrationHooks: workspaceHydrationTransactionHooks
|
|
5082
|
+
});
|
|
5051
5083
|
for (const project of message.projects) {
|
|
5052
5084
|
for (const { branchName } of project.preserveOnlyBranches) {
|
|
5053
5085
|
if (stillPendingCreatedBranches.has(pendingCreatedBranchKey(project.projectId, branchName))) {
|
|
@@ -5091,7 +5123,7 @@ async function startWorker(options) {
|
|
|
5091
5123
|
// layouts can be marked ready. No process can resolve an unready
|
|
5092
5124
|
// target, and preserving the fetched outer tree here prevents clean
|
|
5093
5125
|
// checkout initialization from erasing synchronized dirt.
|
|
5094
|
-
{ ignoreBusy: true }
|
|
5126
|
+
{ ignoreBusy: true, hydrationHooks: workspaceHydrationTransactionHooks }
|
|
5095
5127
|
);
|
|
5096
5128
|
await ensureConfiguredProjects((progress) => {
|
|
5097
5129
|
sendLifecycleProgress({ phase: "configuring", operationId: configurationOperationId, ...progress });
|
package/dist/mjs/package.json
CHANGED