2023-11-01 13:16:58 +00:00
|
|
|
import * as plugins from './smartstream.plugins.js';
|
|
|
|
import { Duplex, type DuplexOptions } from 'stream';
|
2023-11-03 20:32:24 +00:00
|
|
|
|
|
|
|
export interface IStreamTools {
|
|
|
|
truncate: () => void;
|
2023-11-13 17:19:11 +00:00
|
|
|
push: (pipeObject: any) => Promise<void>;
|
2023-11-03 12:55:56 +00:00
|
|
|
}
|
2023-11-01 13:16:58 +00:00
|
|
|
|
2023-11-07 20:46:46 +00:00
|
|
|
export interface IStreamWriteFunction<T, rT> {
|
2023-11-03 20:32:24 +00:00
|
|
|
(chunkArg: T, toolsArg: IStreamTools): Promise<rT>;
|
|
|
|
}
|
2023-11-01 13:16:58 +00:00
|
|
|
|
2023-11-07 20:46:46 +00:00
|
|
|
export interface IStreamFinalFunction<rT> {
|
2023-11-03 20:32:24 +00:00
|
|
|
(toolsArg: IStreamTools): Promise<rT>;
|
|
|
|
}
|
2023-11-01 13:16:58 +00:00
|
|
|
|
2023-11-11 19:30:42 +00:00
|
|
|
export interface ISmartDuplexOptions<TInput, TOutput> extends DuplexOptions {
|
2023-11-13 16:43:15 +00:00
|
|
|
debug?: boolean;
|
|
|
|
name?: string;
|
2023-11-11 17:53:38 +00:00
|
|
|
handleBackpressure?: boolean;
|
2023-11-03 20:32:24 +00:00
|
|
|
readFunction?: () => Promise<void>;
|
2023-11-07 20:46:46 +00:00
|
|
|
writeFunction?: IStreamWriteFunction<TInput, TOutput>;
|
|
|
|
finalFunction?: IStreamFinalFunction<TOutput>;
|
2023-11-03 20:32:24 +00:00
|
|
|
// Add other custom options if necessary
|
|
|
|
}
|
2023-11-01 13:16:58 +00:00
|
|
|
|
2023-11-03 20:32:24 +00:00
|
|
|
export class SmartDuplex<TInput = any, TOutput = any> extends Duplex {
|
|
|
|
// STATIC
|
2023-11-11 19:30:42 +00:00
|
|
|
static fromBuffer(buffer: Buffer, options?: ISmartDuplexOptions<any, any>): SmartDuplex {
|
|
|
|
const smartDuplex = new SmartDuplex(options);
|
2023-11-01 13:16:58 +00:00
|
|
|
process.nextTick(() => {
|
2023-11-11 19:30:42 +00:00
|
|
|
smartDuplex.push(buffer);
|
|
|
|
smartDuplex.push(null); // Signal the end of the data
|
2023-11-01 13:16:58 +00:00
|
|
|
});
|
2023-11-11 19:30:42 +00:00
|
|
|
return smartDuplex;
|
2023-11-01 13:16:58 +00:00
|
|
|
}
|
|
|
|
|
2023-11-03 20:32:24 +00:00
|
|
|
// INSTANCE
|
2023-11-13 16:43:15 +00:00
|
|
|
private backpressuredArray = new plugins.lik.BackpressuredArray<TOutput>();
|
|
|
|
public options: ISmartDuplexOptions<TInput, TOutput>;
|
2023-11-03 20:32:24 +00:00
|
|
|
private observableSubscription?: plugins.smartrx.rxjs.Subscription;
|
2023-11-13 16:43:15 +00:00
|
|
|
private debugLog(messageArg: string) {
|
|
|
|
if (this.options.debug) {
|
|
|
|
console.log(messageArg);
|
|
|
|
}
|
|
|
|
}
|
2023-11-03 20:32:24 +00:00
|
|
|
|
2023-11-11 19:30:42 +00:00
|
|
|
constructor(optionsArg?: ISmartDuplexOptions<TInput, TOutput>) {
|
2023-11-03 20:32:24 +00:00
|
|
|
super(optionsArg);
|
2023-11-13 16:43:15 +00:00
|
|
|
this.options = optionsArg;
|
2023-11-03 20:32:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public async _read(size: number): Promise<void> {
|
2023-11-13 16:43:15 +00:00
|
|
|
this.debugLog(`${this.options.name}: read was called`);
|
2023-11-13 16:52:11 +00:00
|
|
|
await this.backpressuredArray.waitForItems();
|
|
|
|
this.debugLog(`${this.options.name}: successfully waited for items.`);
|
2023-11-13 16:43:15 +00:00
|
|
|
if (this.options.readFunction) {
|
|
|
|
await this.options.readFunction();
|
|
|
|
}
|
|
|
|
let canPushMore = true;
|
|
|
|
while(this.backpressuredArray.data.length > 0 && canPushMore) {
|
|
|
|
const nextChunk = this.backpressuredArray.shift();
|
|
|
|
if (nextChunk) {
|
|
|
|
canPushMore = this.push(nextChunk);
|
|
|
|
}
|
2023-11-03 20:32:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-11-12 21:34:55 +00:00
|
|
|
private asyncWritePromiseObjectmap = new plugins.lik.ObjectMap<Promise<any>>();
|
2023-11-03 20:32:24 +00:00
|
|
|
// Ensure the _write method types the chunk as TInput and encodes TOutput
|
|
|
|
public async _write(chunk: TInput, encoding: string, callback: (error?: Error | null) => void) {
|
2023-11-13 16:43:15 +00:00
|
|
|
if (!this.options.writeFunction) {
|
2023-11-03 20:32:24 +00:00
|
|
|
return callback(new Error('No stream function provided'));
|
|
|
|
}
|
|
|
|
|
2023-11-13 16:43:15 +00:00
|
|
|
let isTruncated = false;
|
2023-11-03 20:32:24 +00:00
|
|
|
const tools: IStreamTools = {
|
|
|
|
truncate: () => {
|
|
|
|
this.push(null);
|
2023-11-13 16:43:15 +00:00
|
|
|
isTruncated = true;
|
2023-11-03 20:32:24 +00:00
|
|
|
callback();
|
|
|
|
},
|
2023-11-13 17:19:11 +00:00
|
|
|
push: async (pushArg: TOutput) => {
|
|
|
|
const canPushMore = this.backpressuredArray.push(pushArg);
|
|
|
|
if (!canPushMore) {
|
|
|
|
this.debugLog(`${this.options.name}: cannot push more`);
|
|
|
|
await this.backpressuredArray.waitForSpace();
|
|
|
|
this.debugLog(`${this.options.name}: can push more again`);
|
|
|
|
}
|
2023-11-13 16:43:15 +00:00
|
|
|
},
|
2023-11-03 20:32:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
2023-11-12 21:34:55 +00:00
|
|
|
const writeDeferred = plugins.smartpromise.defer();
|
|
|
|
this.asyncWritePromiseObjectmap.add(writeDeferred.promise);
|
2023-11-13 16:43:15 +00:00
|
|
|
const modifiedChunk = await this.options.writeFunction(chunk, tools);
|
|
|
|
if (isTruncated) {
|
|
|
|
return;
|
|
|
|
}
|
2023-11-03 20:32:24 +00:00
|
|
|
if (modifiedChunk) {
|
2023-11-13 17:19:11 +00:00
|
|
|
await tools.push(modifiedChunk);
|
2023-11-03 20:32:24 +00:00
|
|
|
}
|
2023-11-13 16:43:15 +00:00
|
|
|
callback();
|
2023-11-12 21:34:55 +00:00
|
|
|
writeDeferred.resolve();
|
|
|
|
writeDeferred.promise.then(() => {
|
|
|
|
this.asyncWritePromiseObjectmap.remove(writeDeferred.promise);
|
|
|
|
});
|
2023-11-03 20:32:24 +00:00
|
|
|
} catch (err) {
|
|
|
|
callback(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public async _final(callback: (error?: Error | null) => void) {
|
2023-11-12 21:34:55 +00:00
|
|
|
await Promise.all(this.asyncWritePromiseObjectmap.getArray());
|
2023-11-13 16:43:15 +00:00
|
|
|
if (this.options.finalFunction) {
|
2023-11-03 20:32:24 +00:00
|
|
|
const tools: IStreamTools = {
|
|
|
|
truncate: () => callback(),
|
2023-11-13 17:19:11 +00:00
|
|
|
push: async (pipeObject) => {
|
|
|
|
this.push(pipeObject);
|
|
|
|
},
|
2023-11-03 20:32:24 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
try {
|
2023-11-13 16:43:15 +00:00
|
|
|
const finalChunk = await this.options.finalFunction(tools);
|
2023-11-03 20:32:24 +00:00
|
|
|
if (finalChunk) {
|
|
|
|
this.push(finalChunk);
|
|
|
|
}
|
|
|
|
} catch (err) {
|
2023-11-12 21:34:55 +00:00
|
|
|
this.push(null);
|
2023-11-03 20:32:24 +00:00
|
|
|
callback(err);
|
2023-11-12 21:34:55 +00:00
|
|
|
return;
|
2023-11-03 20:32:24 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-06 20:59:25 +00:00
|
|
|
this.push(null);
|
2023-11-06 21:10:20 +00:00
|
|
|
callback();
|
2023-11-03 20:32:24 +00:00
|
|
|
}
|
2023-11-01 13:16:58 +00:00
|
|
|
}
|