smartstream/ts/smartstream.classes.smartduplex.ts

137 lines
4.1 KiB
TypeScript
Raw Normal View History

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-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
}