2024-11-19 09:51:05 +01:00
|
|
|
import * as plugins from './smartstream.plugins.js';
|
2024-10-13 13:49:13 +02:00
|
|
|
|
|
|
|
|
/**
|
2026-03-02 06:55:11 +00:00
|
|
|
* Creates a Web ReadableStream from a file using pull-based backpressure.
|
2024-10-13 13:49:13 +02:00
|
|
|
*
|
|
|
|
|
* @param filePath - The path to the file to be read
|
|
|
|
|
* @returns A Web ReadableStream that reads the file in chunks
|
|
|
|
|
*/
|
|
|
|
|
export function createWebReadableStreamFromFile(filePath: string): ReadableStream<Uint8Array> {
|
2024-11-19 09:51:05 +01:00
|
|
|
const fileStream = plugins.fs.createReadStream(filePath);
|
2024-10-13 13:49:13 +02:00
|
|
|
|
|
|
|
|
return new ReadableStream({
|
|
|
|
|
start(controller) {
|
2026-03-02 06:55:11 +00:00
|
|
|
fileStream.on('error', (err) => {
|
|
|
|
|
controller.error(err);
|
2024-10-13 13:49:13 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
fileStream.on('end', () => {
|
|
|
|
|
controller.close();
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-02 06:55:11 +00:00
|
|
|
// Pause immediately — pull() will drive reads
|
|
|
|
|
fileStream.pause();
|
|
|
|
|
},
|
|
|
|
|
pull(controller) {
|
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
|
|
|
const chunk = fileStream.read();
|
|
|
|
|
if (chunk !== null) {
|
|
|
|
|
controller.enqueue(chunk as Uint8Array);
|
|
|
|
|
resolve();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// No data available yet — wait for 'readable' or 'end'
|
|
|
|
|
const onReadable = () => {
|
|
|
|
|
cleanup();
|
|
|
|
|
const data = fileStream.read();
|
|
|
|
|
if (data !== null) {
|
|
|
|
|
controller.enqueue(data as Uint8Array);
|
|
|
|
|
}
|
|
|
|
|
resolve();
|
|
|
|
|
};
|
|
|
|
|
const onEnd = () => {
|
|
|
|
|
cleanup();
|
|
|
|
|
resolve();
|
|
|
|
|
};
|
|
|
|
|
const onError = (err: Error) => {
|
|
|
|
|
cleanup();
|
|
|
|
|
reject(err);
|
|
|
|
|
};
|
|
|
|
|
const cleanup = () => {
|
|
|
|
|
fileStream.removeListener('readable', onReadable);
|
|
|
|
|
fileStream.removeListener('end', onEnd);
|
|
|
|
|
fileStream.removeListener('error', onError);
|
|
|
|
|
};
|
|
|
|
|
fileStream.once('readable', onReadable);
|
|
|
|
|
fileStream.once('end', onEnd);
|
|
|
|
|
fileStream.once('error', onError);
|
2024-10-13 13:49:13 +02:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
cancel() {
|
|
|
|
|
fileStream.destroy();
|
|
|
|
|
}
|
|
|
|
|
});
|
2024-11-19 09:51:05 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a Web ReadableStream to a Node.js Readable stream.
|
|
|
|
|
*
|
|
|
|
|
* @param webStream - The Web ReadableStream to convert
|
|
|
|
|
* @returns A Node.js Readable stream that reads data from the Web ReadableStream
|
|
|
|
|
*/
|
|
|
|
|
export function convertWebReadableToNodeReadable(webStream: ReadableStream<Uint8Array>): plugins.stream.Readable {
|
|
|
|
|
const reader = webStream.getReader();
|
|
|
|
|
|
|
|
|
|
return new plugins.stream.Readable({
|
2026-03-02 06:55:11 +00:00
|
|
|
read() {
|
|
|
|
|
reader.read().then(
|
|
|
|
|
({ value, done }) => {
|
|
|
|
|
if (done) {
|
|
|
|
|
this.push(null);
|
|
|
|
|
} else {
|
|
|
|
|
this.push(Buffer.from(value));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
(err) => {
|
|
|
|
|
this.destroy(err);
|
2024-11-19 09:51:05 +01:00
|
|
|
}
|
2026-03-02 06:55:11 +00:00
|
|
|
);
|
2024-11-19 09:51:05 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2026-03-02 06:55:11 +00:00
|
|
|
* Converts a Node.js Readable stream to a Web ReadableStream using pull-based backpressure.
|
2024-11-19 09:51:05 +01:00
|
|
|
*
|
|
|
|
|
* @param nodeStream - The Node.js Readable stream to convert
|
|
|
|
|
* @returns A Web ReadableStream that reads data from the Node.js Readable stream
|
|
|
|
|
*/
|
|
|
|
|
export function convertNodeReadableToWebReadable(nodeStream: plugins.stream.Readable): ReadableStream<Uint8Array> {
|
|
|
|
|
return new ReadableStream({
|
|
|
|
|
start(controller) {
|
2026-03-02 06:55:11 +00:00
|
|
|
nodeStream.on('error', (err) => {
|
|
|
|
|
controller.error(err);
|
2024-11-19 09:51:05 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
nodeStream.on('end', () => {
|
|
|
|
|
controller.close();
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-02 06:55:11 +00:00
|
|
|
// Pause immediately — pull() will drive reads
|
|
|
|
|
nodeStream.pause();
|
|
|
|
|
},
|
|
|
|
|
pull(controller) {
|
|
|
|
|
return new Promise<void>((resolve, reject) => {
|
|
|
|
|
const chunk = nodeStream.read();
|
|
|
|
|
if (chunk !== null) {
|
|
|
|
|
controller.enqueue(new Uint8Array(chunk));
|
|
|
|
|
resolve();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// No data available yet — wait for 'readable' or 'end'
|
|
|
|
|
const onReadable = () => {
|
|
|
|
|
cleanup();
|
|
|
|
|
const data = nodeStream.read();
|
|
|
|
|
if (data !== null) {
|
|
|
|
|
controller.enqueue(new Uint8Array(data));
|
|
|
|
|
}
|
|
|
|
|
resolve();
|
|
|
|
|
};
|
|
|
|
|
const onEnd = () => {
|
|
|
|
|
cleanup();
|
|
|
|
|
resolve();
|
|
|
|
|
};
|
|
|
|
|
const onError = (err: Error) => {
|
|
|
|
|
cleanup();
|
|
|
|
|
reject(err);
|
|
|
|
|
};
|
|
|
|
|
const cleanup = () => {
|
|
|
|
|
nodeStream.removeListener('readable', onReadable);
|
|
|
|
|
nodeStream.removeListener('end', onEnd);
|
|
|
|
|
nodeStream.removeListener('error', onError);
|
|
|
|
|
};
|
|
|
|
|
nodeStream.once('readable', onReadable);
|
|
|
|
|
nodeStream.once('end', onEnd);
|
|
|
|
|
nodeStream.once('error', onError);
|
2024-11-19 09:51:05 +01:00
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
cancel() {
|
|
|
|
|
nodeStream.destroy();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a Web WritableStream to a Node.js Writable stream.
|
|
|
|
|
*
|
|
|
|
|
* @param webWritable - The Web WritableStream to convert
|
|
|
|
|
* @returns A Node.js Writable stream that writes data to the Web WritableStream
|
|
|
|
|
*/
|
|
|
|
|
export function convertWebWritableToNodeWritable(webWritable: WritableStream<Uint8Array>): plugins.stream.Writable {
|
|
|
|
|
const writer = webWritable.getWriter();
|
|
|
|
|
|
|
|
|
|
return new plugins.stream.Writable({
|
2026-03-02 06:55:11 +00:00
|
|
|
write(chunk, encoding, callback) {
|
|
|
|
|
writer.write(new Uint8Array(chunk)).then(
|
|
|
|
|
() => callback(),
|
|
|
|
|
(err) => callback(err)
|
|
|
|
|
);
|
2024-11-19 09:51:05 +01:00
|
|
|
},
|
|
|
|
|
final(callback) {
|
|
|
|
|
writer.close().then(() => callback()).catch(callback);
|
|
|
|
|
},
|
|
|
|
|
destroy(err, callback) {
|
2026-03-02 06:55:11 +00:00
|
|
|
if (err) {
|
|
|
|
|
writer.abort(err).then(() => callback(err)).catch(() => callback(err));
|
|
|
|
|
} else {
|
|
|
|
|
// Clean destroy — just release the lock
|
|
|
|
|
writer.releaseLock();
|
|
|
|
|
callback(null);
|
|
|
|
|
}
|
2024-11-19 09:51:05 +01:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Converts a Node.js Writable stream to a Web WritableStream.
|
|
|
|
|
*
|
|
|
|
|
* @param nodeWritable - The Node.js Writable stream to convert
|
|
|
|
|
* @returns A Web WritableStream that writes data to the Node.js Writable stream
|
|
|
|
|
*/
|
|
|
|
|
export function convertNodeWritableToWebWritable(nodeWritable: plugins.stream.Writable): WritableStream<Uint8Array> {
|
|
|
|
|
return new WritableStream({
|
|
|
|
|
write(chunk) {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
nodeWritable.write(Buffer.from(chunk), (err) => {
|
|
|
|
|
if (err) {
|
|
|
|
|
reject(err);
|
|
|
|
|
} else {
|
|
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
close() {
|
|
|
|
|
return new Promise((resolve, reject) => {
|
2026-03-02 06:55:11 +00:00
|
|
|
nodeWritable.end((err: Error | null) => {
|
2024-11-19 09:51:05 +01:00
|
|
|
if (err) {
|
|
|
|
|
reject(err);
|
|
|
|
|
} else {
|
|
|
|
|
resolve();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
abort(reason) {
|
2026-03-02 06:55:11 +00:00
|
|
|
nodeWritable.destroy(reason instanceof Error ? reason : new Error(String(reason)));
|
2024-11-19 09:51:05 +01:00
|
|
|
}
|
|
|
|
|
});
|
2026-03-02 06:55:11 +00:00
|
|
|
}
|