UNPKG

@pipedream/platform

Version:

Pipedream platform globals (typing and runtime type checking)

151 lines (150 loc) 4.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getFileStreamAndMetadata = exports.getFileStream = void 0; const stream_1 = require("stream"); const fs_1 = require("fs"); const os_1 = require("os"); const path_1 = require("path"); const promises_1 = require("stream/promises"); const uuid_1 = require("uuid"); const mime = require("mime-types"); /** * @param pathOrUrl - a file path or a URL * @returns a Readable stream of the file content */ async function getFileStream(pathOrUrl) { if (isUrl(pathOrUrl)) { const response = await fetch(pathOrUrl); if (!response.ok || !response.body) { throw new Error(`Failed to fetch ${pathOrUrl}: ${response.status} ${response.statusText}`); } return stream_1.Readable.fromWeb(response.body); } else { await safeStat(pathOrUrl); return fs_1.createReadStream(pathOrUrl); } } exports.getFileStream = getFileStream; /** * @param pathOrUrl - a file path or a URL * @returns a Readable stream of the file content and its metadata */ async function getFileStreamAndMetadata(pathOrUrl) { if (isUrl(pathOrUrl)) { return await getRemoteFileStreamAndMetadata(pathOrUrl); } else { return await getLocalFileStreamAndMetadata(pathOrUrl); } } exports.getFileStreamAndMetadata = getFileStreamAndMetadata; function isUrl(pathOrUrl) { try { new URL(pathOrUrl); return true; } catch (_a) { return false; } } async function safeStat(path) { try { return await fs_1.promises.stat(path); } catch (_a) { throw new Error(`File not found: ${path}`); } } async function getLocalFileStreamAndMetadata(filePath) { const stats = await safeStat(filePath); const contentType = mime.lookup(filePath) || undefined; const metadata = { size: stats.size, lastModified: stats.mtime, name: path_1.basename(filePath), contentType, }; const stream = fs_1.createReadStream(filePath); return { stream, metadata, }; } async function getRemoteFileStreamAndMetadata(url) { const response = await fetch(url); if (!response.ok || !response.body) { throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); } const headers = response.headers; const contentLength = headers.get("content-length"); const lastModified = headers.get("last-modified") ? new Date(headers.get("last-modified")) : undefined; const etag = headers.get("etag") || undefined; const urlObj = new URL(url); const name = path_1.basename(urlObj.pathname); const contentType = headers.get("content-type") || mime.lookup(urlObj.pathname) || undefined; const baseMetadata = { contentType, lastModified, name, etag, }; // If we have content-length, we can stream directly if (contentLength) { const metadata = { ...baseMetadata, size: parseInt(contentLength, 10), }; const stream = stream_1.Readable.fromWeb(response.body); return { stream, metadata, }; } // No content-length header - need to download to temporary file to get size return await downloadToTemporaryFile(response, baseMetadata); } async function downloadToTemporaryFile(response, baseMetadata) { // Generate unique temporary file path const tempFileName = `file-stream-${uuid_1.v4()}`; const tempFilePath = path_1.join(os_1.tmpdir(), tempFileName); // Download to temporary file const fileStream = fs_1.createWriteStream(tempFilePath); const webStream = stream_1.Readable.fromWeb(response.body); try { await promises_1.pipeline(webStream, fileStream); const stats = await fs_1.promises.stat(tempFilePath); const metadata = { ...baseMetadata, size: stats.size, }; const stream = fs_1.createReadStream(tempFilePath); const cleanup = async () => { try { await fs_1.promises.unlink(tempFilePath); } catch (_a) { // Ignore cleanup errors } }; stream.once("close", cleanup); stream.once("end", cleanup); stream.once("error", cleanup); return { stream, metadata, }; } catch (err) { // Cleanup on error try { await fs_1.promises.unlink(tempFilePath); } catch (_a) { // Ignore cleanup errors } throw err; } }