@indigoai-us/hq-cloud 6.15.12 → 6.15.13
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/cli/share.d.ts +49 -0
- package/dist/cli/share.d.ts.map +1 -1
- package/dist/cli/share.js +42 -2
- package/dist/cli/share.js.map +1 -1
- package/dist/cli/share.test.js +130 -1
- package/dist/cli/share.test.js.map +1 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +6 -2
- package/dist/cli/sync.js.map +1 -1
- package/dist/sync-core.d.ts +58 -1
- package/dist/sync-core.d.ts.map +1 -1
- package/dist/sync-core.js +133 -5
- package/dist/sync-core.js.map +1 -1
- package/dist/sync-core.test.d.ts +2 -0
- package/dist/sync-core.test.d.ts.map +1 -0
- package/dist/sync-core.test.js +113 -0
- package/dist/sync-core.test.js.map +1 -0
- package/package.json +1 -1
package/dist/cli/share.test.js
CHANGED
|
@@ -52,7 +52,7 @@ vi.mock("readline", () => ({
|
|
|
52
52
|
})),
|
|
53
53
|
}));
|
|
54
54
|
import * as readline from "readline";
|
|
55
|
-
import { share, isForbiddenCompanyVaultKey, UnreachablePushPathsError, _testing as shareTesting } from "./share.js";
|
|
55
|
+
import { share, isForbiddenCompanyVaultKey, UnreachablePushPathsError, currentUploadBytes, _testing as shareTesting } from "./share.js";
|
|
56
56
|
import { deleteRemoteFile, downloadFile, headRemoteFile, uploadFile, uploadSymlink, WindowsSymlinkPrivilegeError } from "../s3.js";
|
|
57
57
|
import { VaultAuthError } from "../vault-client.js";
|
|
58
58
|
import { hashFile } from "../journal.js";
|
|
@@ -5873,6 +5873,50 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5873
5873
|
// still fire (now at file-settle time, not plan-walk time) so the menubar
|
|
5874
5874
|
// stream parser sees the same shape. Aborts mid-batch let in-flight PUTs
|
|
5875
5875
|
// finish but stop queueing new ones.
|
|
5876
|
+
describe("currentUploadBytes (byte-budget admission size)", () => {
|
|
5877
|
+
it("returns the CURRENT on-disk size, not a stale planned size", () => {
|
|
5878
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "cub-grow-"));
|
|
5879
|
+
try {
|
|
5880
|
+
const abs = path.join(dir, "grew.log");
|
|
5881
|
+
fs.writeFileSync(abs, Buffer.alloc(5000)); // on disk NOW: 5000 bytes
|
|
5882
|
+
const item = {
|
|
5883
|
+
action: "upload",
|
|
5884
|
+
kind: "file",
|
|
5885
|
+
absolutePath: abs,
|
|
5886
|
+
relativePath: "grew.log",
|
|
5887
|
+
localHash: "h",
|
|
5888
|
+
size: 10, // stale planned size
|
|
5889
|
+
};
|
|
5890
|
+
expect(currentUploadBytes(item)).toBe(5000);
|
|
5891
|
+
}
|
|
5892
|
+
finally {
|
|
5893
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
5894
|
+
}
|
|
5895
|
+
});
|
|
5896
|
+
it("returns 0 for a symlink (empty wire body)", () => {
|
|
5897
|
+
const item = {
|
|
5898
|
+
action: "upload",
|
|
5899
|
+
kind: "symlink",
|
|
5900
|
+
absolutePath: "/nonexistent/link",
|
|
5901
|
+
relativePath: "link",
|
|
5902
|
+
target: "target",
|
|
5903
|
+
localHash: "h",
|
|
5904
|
+
size: 0,
|
|
5905
|
+
};
|
|
5906
|
+
expect(currentUploadBytes(item)).toBe(0);
|
|
5907
|
+
});
|
|
5908
|
+
it("falls back to the planned size when the file cannot be stat'd", () => {
|
|
5909
|
+
const item = {
|
|
5910
|
+
action: "upload",
|
|
5911
|
+
kind: "file",
|
|
5912
|
+
absolutePath: "/nonexistent/x.bin",
|
|
5913
|
+
relativePath: "x.bin",
|
|
5914
|
+
localHash: "h",
|
|
5915
|
+
size: 42,
|
|
5916
|
+
};
|
|
5917
|
+
expect(currentUploadBytes(item)).toBe(42);
|
|
5918
|
+
});
|
|
5919
|
+
});
|
|
5876
5920
|
describe("parallel upload pool (5.36.0)", () => {
|
|
5877
5921
|
it("runs uploads with measurable parallelism (8+ in flight before first settles)", async () => {
|
|
5878
5922
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
@@ -5999,6 +6043,91 @@ describe("currency-gated: journal version 2 fixtures", () => {
|
|
|
5999
6043
|
delete process.env.HQ_SYNC_TRANSFER_CONCURRENCY;
|
|
6000
6044
|
}
|
|
6001
6045
|
});
|
|
6046
|
+
it("byte budget caps in-flight uploads below the worker count", async () => {
|
|
6047
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
6048
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
6049
|
+
// 10 files of exactly 2048 bytes each.
|
|
6050
|
+
for (let i = 0; i < 10; i++) {
|
|
6051
|
+
fs.writeFileSync(path.join(companyRoot, `f-${i}.bin`), Buffer.alloc(2048));
|
|
6052
|
+
}
|
|
6053
|
+
// Worker count is high (16) so it is NOT the limiter; the byte budget is.
|
|
6054
|
+
// 0.0048 MiB = 5033 bytes: two 2048-byte bodies fit (4096), three do not
|
|
6055
|
+
// (6144), so at most 2 uploads may be in flight regardless of the 16 slots.
|
|
6056
|
+
process.env.HQ_SYNC_TRANSFER_CONCURRENCY = "16";
|
|
6057
|
+
process.env.HQ_SYNC_UPLOAD_BYTE_BUDGET_MB = "0.0048";
|
|
6058
|
+
const startTimes = [];
|
|
6059
|
+
const endTimes = [];
|
|
6060
|
+
vi.mocked(uploadFile).mockImplementation(async () => {
|
|
6061
|
+
startTimes.push(Date.now());
|
|
6062
|
+
await new Promise((r) => setTimeout(r, 30));
|
|
6063
|
+
endTimes.push(Date.now());
|
|
6064
|
+
return { etag: '"upload-etag"' };
|
|
6065
|
+
});
|
|
6066
|
+
try {
|
|
6067
|
+
const result = await share({
|
|
6068
|
+
paths: [companyRoot],
|
|
6069
|
+
company: "acme",
|
|
6070
|
+
vaultConfig: mockConfig,
|
|
6071
|
+
hqRoot: tmpDir,
|
|
6072
|
+
onConflict: "keep",
|
|
6073
|
+
});
|
|
6074
|
+
expect(result.filesUploaded).toBe(10); // all made progress, no deadlock
|
|
6075
|
+
const sortedEvents = [];
|
|
6076
|
+
for (const t of startTimes)
|
|
6077
|
+
sortedEvents.push({ t, kind: "start" });
|
|
6078
|
+
for (const t of endTimes)
|
|
6079
|
+
sortedEvents.push({ t, kind: "end" });
|
|
6080
|
+
sortedEvents.sort((a, b) => a.t - b.t || (a.kind === "end" ? -1 : 1));
|
|
6081
|
+
let cur = 0;
|
|
6082
|
+
let maxConcurrent = 0;
|
|
6083
|
+
for (const ev of sortedEvents) {
|
|
6084
|
+
if (ev.kind === "start") {
|
|
6085
|
+
cur++;
|
|
6086
|
+
if (cur > maxConcurrent)
|
|
6087
|
+
maxConcurrent = cur;
|
|
6088
|
+
}
|
|
6089
|
+
else {
|
|
6090
|
+
cur--;
|
|
6091
|
+
}
|
|
6092
|
+
}
|
|
6093
|
+
// Byte-limited to 2, far below the 16-slot worker cap.
|
|
6094
|
+
expect(maxConcurrent).toBeLessThanOrEqual(2);
|
|
6095
|
+
}
|
|
6096
|
+
finally {
|
|
6097
|
+
delete process.env.HQ_SYNC_TRANSFER_CONCURRENCY;
|
|
6098
|
+
delete process.env.HQ_SYNC_UPLOAD_BYTE_BUDGET_MB;
|
|
6099
|
+
}
|
|
6100
|
+
});
|
|
6101
|
+
it("a file larger than the whole byte budget still uploads (no deadlock)", async () => {
|
|
6102
|
+
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
6103
|
+
fs.mkdirSync(companyRoot, { recursive: true });
|
|
6104
|
+
// Three 4096-byte files against a ~1 KiB budget: each exceeds the budget
|
|
6105
|
+
// alone, so the "admit one when nothing is in flight" escape hatch must
|
|
6106
|
+
// let every file through one at a time instead of wedging the pool.
|
|
6107
|
+
for (let i = 0; i < 3; i++) {
|
|
6108
|
+
fs.writeFileSync(path.join(companyRoot, `big-${i}.bin`), Buffer.alloc(4096));
|
|
6109
|
+
}
|
|
6110
|
+
process.env.HQ_SYNC_TRANSFER_CONCURRENCY = "16";
|
|
6111
|
+
process.env.HQ_SYNC_UPLOAD_BYTE_BUDGET_MB = "0.001"; // 1048 bytes < 4096
|
|
6112
|
+
vi.mocked(uploadFile).mockImplementation(async () => {
|
|
6113
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
6114
|
+
return { etag: '"upload-etag"' };
|
|
6115
|
+
});
|
|
6116
|
+
try {
|
|
6117
|
+
const result = await share({
|
|
6118
|
+
paths: [companyRoot],
|
|
6119
|
+
company: "acme",
|
|
6120
|
+
vaultConfig: mockConfig,
|
|
6121
|
+
hqRoot: tmpDir,
|
|
6122
|
+
onConflict: "keep",
|
|
6123
|
+
});
|
|
6124
|
+
expect(result.filesUploaded).toBe(3);
|
|
6125
|
+
}
|
|
6126
|
+
finally {
|
|
6127
|
+
delete process.env.HQ_SYNC_TRANSFER_CONCURRENCY;
|
|
6128
|
+
delete process.env.HQ_SYNC_UPLOAD_BYTE_BUDGET_MB;
|
|
6129
|
+
}
|
|
6130
|
+
});
|
|
6002
6131
|
it("one failed upload does not abort sibling uploads in the pool", async () => {
|
|
6003
6132
|
const companyRoot = path.join(tmpDir, "companies", "acme");
|
|
6004
6133
|
fs.mkdirSync(companyRoot, { recursive: true });
|