UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

145 lines (143 loc) • 5.32 kB
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