everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
145 lines (143 loc) • 5.32 kB
JavaScript
let node_path = require("node:path");
let effect = require("effect");
let node_fs_promises = require("node:fs/promises");
//#region src/storage-upload.ts
async function walkDist(dir, baseDir) {
const entries = await (0, node_fs_promises.readdir)(dir, { withFileTypes: true });
const files = [];
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
const fullPath = (0, node_path.join)(dir, entry.name);
if (entry.isDirectory()) {
files.push(...await walkDist(fullPath, baseDir));
continue;
}
if (!(await (0, node_fs_promises.stat)(fullPath)).isFile()) continue;
files.push({
path: (0, node_path.relative)(baseDir, fullPath).split(node_path.sep).join("/"),
bytes: new Uint8Array(await (0, node_fs_promises.readFile)(fullPath))
});
}
return files;
}
async function collectDistFiles(distDir) {
return walkDist(distDir, distDir);
}
const STATUS_ATTEMPTS = 3;
const TRANSPORT_ATTEMPTS = 5;
const RETRYABLE_STATUSES = /* @__PURE__ */ new Set([
408,
429,
500,
502,
503,
504
]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const backoffMs = (attempt) => 1e3 * attempt + Math.floor(Math.random() * 500);
var BundleUploadError = class BundleUploadError extends effect.Data.TaggedError("BundleUploadError") {
constructor(status, detail, note) {
super({
status,
detail,
note
});
}
get message() {
return `${this.detail}${this.note ?? ""}`;
}
withNote(note) {
return new BundleUploadError(this.status, this.detail, `${this.note ?? ""}${note}`);
}
};
function describeUploadFailure(status, detail) {
const trimmed = detail.slice(0, 300);
if (status === 408 || /request timeout|bundle upload timed out/i.test(trimmed)) return `${trimmed} — the serving host timed out the upload; raise BOS_STORAGE_UPLOAD_TIMEOUT_MS there or upload smaller batches (BOS_MAX_BUNDLE_UPLOAD_BYTES)`;
try {
const parsed = JSON.parse(detail);
if (parsed?.code === "INTERNAL_SERVER_ERROR" && parsed?.message === "Internal Server Error") return `${trimmed} — the host hid the underlying error; check the host logs for the cause`;
} catch {}
return trimmed;
}
async function uploadBundle(input) {
const fetchImpl = input.fetchImpl ?? fetch;
const headers = { "content-type": "application/json" };
if (input.apiKey) headers["x-api-key"] = input.apiKey;
const body = JSON.stringify({
account: input.account,
gateway: input.gateway,
workspace: input.workspace,
files: input.files.map((file) => ({
path: file.path,
contentBase64: Buffer.from(file.bytes).toString("base64")
}))
});
let lastError;
let transportFailures = 0;
let statusFailures = 0;
for (let attempt = 1; attempt <= TRANSPORT_ATTEMPTS; attempt++) {
try {
const response = await fetchImpl(`${input.origin.replace(/\/$/, "")}/api/storage/bundles`, {
method: "POST",
headers: transportFailures > 0 ? {
...headers,
connection: "close"
} : headers,
body
});
if (response.ok) return await response.json();
const detail = await response.text().catch(() => "");
lastError = new BundleUploadError(response.status, `[publish] bundle upload for ${input.workspace} failed: ${response.status} ${describeUploadFailure(response.status, detail)}`);
if (!RETRYABLE_STATUSES.has(response.status)) break;
statusFailures += 1;
if (statusFailures >= STATUS_ATTEMPTS) break;
} catch (error) {
transportFailures += 1;
lastError = new BundleUploadError(0, `[publish] bundle upload for ${input.workspace} failed: ${error instanceof Error ? error.message : String(error)}`);
}
if (attempt < TRANSPORT_ATTEMPTS) await sleep(backoffMs(attempt));
}
if (lastError instanceof BundleUploadError && lastError.status === 0) lastError = lastError.withNote(" (Bun fetch note: re-run with fetch verbose diagnostics for detail)");
throw lastError ?? /* @__PURE__ */ new Error(`[publish] bundle upload for ${input.workspace} failed`);
}
async function uploadWorkspaceDist(input) {
const maxRequestBytes = input.maxRequestBytes ?? (Number(process.env.BOS_MAX_BUNDLE_UPLOAD_BYTES) || 67108864);
const merged = {
stored: 0,
totalBytes: 0,
integrity: {}
};
let batch = [];
let batchBytes = 0;
const overheadPerFile = (file) => Math.ceil(file.bytes.byteLength / 3) * 4 + file.path.length + 64;
const flush = async () => {
if (batch.length === 0) return;
const result = await uploadBundle({
origin: input.origin,
apiKey: input.apiKey,
account: input.account,
gateway: input.gateway,
workspace: input.workspace,
files: batch
});
merged.stored += result.stored;
merged.totalBytes += result.totalBytes;
Object.assign(merged.integrity, result.integrity);
merged.storage = result.storage;
batch = [];
batchBytes = 0;
};
for (const file of input.files) {
const cost = overheadPerFile(file);
if (cost > maxRequestBytes) throw new Error(`[publish] ${input.workspace}/${file.path} (${file.bytes.byteLength} bytes) exceeds the per-request upload ceiling`);
if (batchBytes + cost > maxRequestBytes) await flush();
batch.push(file);
batchBytes += cost;
}
await flush();
return merged;
}
//#endregion
exports.collectDistFiles = collectDistFiles;
exports.uploadBundle = uploadBundle;
exports.uploadWorkspaceDist = uploadWorkspaceDist;
//# sourceMappingURL=storage-upload.cjs.map