@push.rocks/smartfile
Version:
High-level file representation classes (SmartFile, StreamFile, VirtualDirectory) for efficient in-memory file management in Node.js using TypeScript. Works seamlessly with @push.rocks/smartfs for filesystem operations.
208 lines • 17 kB
JavaScript
import * as plugins from './plugins.js';
import { Readable, Writable } from 'stream';
/**
* The StreamFile class represents a file as a stream.
* It allows creating streams from a file path, a URL, or a buffer.
* Use SmartFileFactory to create instances of this class.
*/
export class StreamFile {
// STATIC
static async fromPath(filePath, smartFs) {
if (!smartFs) {
throw new Error('No SmartFs instance available. Create StreamFile through SmartFileFactory.');
}
const streamSource = async (streamFileArg) => {
return await streamFileArg.smartFs.file(filePath).readStream();
};
const streamFile = new StreamFile(streamSource, filePath, smartFs);
streamFile.multiUse = true;
streamFile.byteLengthComputeFunction = async () => {
const stats = await smartFs.file(filePath).stat();
return stats.size;
};
return streamFile;
}
static async fromUrl(url, smartFs) {
const streamSource = async (streamFileArg) => {
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.get();
const responseStream = response.stream();
if (!responseStream) {
throw new Error(`No response stream available for ${url}`);
}
return responseStream;
};
const streamFile = new StreamFile(streamSource, undefined, smartFs);
streamFile.multiUse = true;
streamFile.byteLengthComputeFunction = async () => {
const response = await plugins.smartrequest.SmartRequest.create()
.url(url)
.accept('binary')
.get();
const buffer = Buffer.from(await response.arrayBuffer());
return buffer.length;
};
return streamFile;
}
static fromBuffer(buffer, relativeFilePath, smartFs) {
const streamSource = async (streamFileArg) => {
const stream = new Readable();
stream.push(buffer);
stream.push(null); // End of stream
return stream;
};
const streamFile = new StreamFile(streamSource, relativeFilePath, smartFs);
streamFile.multiUse = true;
streamFile.byteLengthComputeFunction = async () => buffer.length;
return streamFile;
}
/**
* Creates a StreamFile from an existing Readable stream with an option for multiple uses.
* @param stream A Node.js Readable stream.
* @param relativeFilePath Optional file path for the stream.
* @param multiUse If true, the stream can be read multiple times, caching its content.
* @param smartFs Optional SmartFs instance for filesystem operations
* @returns A StreamFile instance.
*/
static fromStream(stream, relativeFilePath, multiUse = false, smartFs) {
const streamSource = (streamFileArg) => {
if (streamFileArg.multiUse) {
// If multi-use is enabled and we have cached content, create a new readable stream from the buffer
const bufferedStream = new Readable();
bufferedStream.push(streamFileArg.cachedStreamBuffer);
bufferedStream.push(null); // No more data to push
return Promise.resolve(bufferedStream);
}
else {
return Promise.resolve(stream);
}
};
const streamFile = new StreamFile(streamSource, relativeFilePath, smartFs);
streamFile.multiUse = multiUse;
// If multi-use is enabled, cache the stream when it's first read
if (multiUse) {
const chunks = [];
stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
stream.on('end', () => {
streamFile.cachedStreamBuffer = Buffer.concat(chunks);
});
// It's important to handle errors that may occur during streaming
stream.on('error', (err) => {
console.error('Error while caching stream:', err);
});
}
return streamFile;
}
// INSTANCE
relativeFilePath;
streamSource;
smartFs;
// enable stream based multi use
cachedStreamBuffer;
multiUse = false;
used = false;
byteLengthComputeFunction;
constructor(streamSource, relativeFilePath, smartFs) {
this.streamSource = streamSource;
this.relativeFilePath = relativeFilePath;
this.smartFs = smartFs;
}
// METHODS
checkMultiUse() {
if (!this.multiUse && this.used) {
throw new Error('This stream can only be used once.');
}
this.used = true;
}
/**
* Creates a new readable stream from the source.
*/
async createReadStream() {
const stream = await this.streamSource(this);
// Check if it's a Web ReadableStream and convert to Node.js Readable
if (stream && typeof stream.getReader === 'function') {
// This is a Web ReadableStream, convert it to Node.js Readable
return Readable.fromWeb(stream);
}
// It's already a Node.js Readable stream
return stream;
}
/**
* Writes the stream to the disk at the specified path.
* @param filePathArg The file path where the stream should be written.
*/
async writeToDisk(filePathArg) {
if (!this.smartFs) {
throw new Error('No SmartFs instance available. Create StreamFile through SmartFileFactory.');
}
this.checkMultiUse();
const readStream = await this.createReadStream();
let writeStream = await this.smartFs.file(filePathArg).writeStream();
// Check if it's a Web WritableStream and convert to Node.js Writable
if (writeStream && typeof writeStream.getWriter === 'function') {
// This is a Web WritableStream, convert it to Node.js Writable
writeStream = Writable.fromWeb(writeStream);
}
return new Promise((resolve, reject) => {
readStream.pipe(writeStream);
readStream.on('error', reject);
writeStream.on('error', reject);
writeStream.on('finish', resolve);
});
}
async writeToDir(dirPathArg) {
if (!this.smartFs) {
throw new Error('No SmartFs instance available. Create StreamFile through SmartFileFactory.');
}
this.checkMultiUse();
if (!this.relativeFilePath) {
throw new Error('Cannot write stream to directory without a relative file path.');
}
const filePath = plugins.path.join(dirPathArg, this.relativeFilePath);
const dirPath = plugins.path.parse(filePath).dir;
await this.smartFs.directory(dirPath).recursive().create();
return this.writeToDisk(filePath);
}
async getContentAsBuffer() {
this.checkMultiUse();
const done = plugins.smartpromise.defer();
const readStream = await this.createReadStream();
const chunks = [];
readStream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
readStream.on('error', done.reject);
readStream.on('end', () => {
const contentBuffer = Buffer.concat(chunks);
done.resolve(contentBuffer);
});
return done.promise;
}
async getContentAsString(formatArg = 'utf8') {
const contentBuffer = await this.getContentAsBuffer();
return contentBuffer.toString(formatArg);
}
/**
* Returns the size of the file content in bytes.
*/
async getSize() {
if (this.byteLengthComputeFunction) {
return this.byteLengthComputeFunction();
}
else {
return null;
}
}
/**
* Converts the StreamFile to a SmartFile by loading content into memory
*/
async toSmartFile() {
const { SmartFile } = await import('./classes.smartfile.js');
const buffer = await this.getContentAsBuffer();
return new SmartFile({
path: this.relativeFilePath || 'stream',
contentBuffer: buffer,
base: process.cwd()
}, this.smartFs);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5zdHJlYW1maWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy5zdHJlYW1maWxlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxRQUFRLEVBQUUsUUFBUSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBSTVDOzs7O0dBSUc7QUFDSCxNQUFNLE9BQU8sVUFBVTtJQUNyQixTQUFTO0lBRUYsTUFBTSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsUUFBZ0IsRUFBRSxPQUFhO1FBQzFELElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNiLE1BQU0sSUFBSSxLQUFLLENBQUMsNEVBQTRFLENBQUMsQ0FBQztRQUNoRyxDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQWtCLEtBQUssRUFBRSxhQUFhLEVBQUUsRUFBRTtZQUMxRCxPQUFPLE1BQU0sYUFBYSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUMsVUFBVSxFQUFFLENBQUM7UUFDakUsQ0FBQyxDQUFDO1FBQ0YsTUFBTSxVQUFVLEdBQUcsSUFBSSxVQUFVLENBQUMsWUFBWSxFQUFFLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztRQUNuRSxVQUFVLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQztRQUMzQixVQUFVLENBQUMseUJBQXlCLEdBQUcsS0FBSyxJQUFJLEVBQUU7WUFDaEQsTUFBTSxLQUFLLEdBQUcsTUFBTSxPQUFPLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ2xELE9BQU8sS0FBSyxDQUFDLElBQUksQ0FBQztRQUNwQixDQUFDLENBQUM7UUFDRixPQUFPLFVBQVUsQ0FBQztJQUNwQixDQUFDO0lBRU0sTUFBTSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBVyxFQUFFLE9BQWE7UUFDcEQsTUFBTSxZQUFZLEdB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