export interface Logger { log: (...args: unknown[]) => void; debug: (...args: unknown[]) => void; warn: (...args: unknown[]) => void; error: (...args: unknown[]) => void; } type Supplier = () => T; type TraceSupplier = Supplier; export interface ITraceableLogger> extends Logger { addTracer: (traceSupplier: TraceSupplier) => L; } export type ITraceableTuple = [T, TraceSupplier]; export type ITraceableMapper, U> = ( t: ITraceable, ) => U; export interface ITraceable> { item: T; logger: L; map: (mapper: ITraceableMapper) => ITraceable; bimap: ( mapper: ITraceableMapper>, ) => ITraceable; peek: (peek: ITraceableMapper) => ITraceable; flatMap: (mapper: ITraceableMapper>) => ITraceable; flatMapAsync(mapper: ITraceableMapper>>): ITraceable, L>; } export class TraceableLogger implements ITraceableLogger { private readonly logger: Logger = console; constructor( private readonly traces = [() => `[${new Date().toISOString()}]`], ) { } public debug(...args: unknown[]) { this.logger.debug("[DEBUG]", ...this.getPrefix(), args); } public log(...args: unknown[]) { this.logger.log("[INFO]", ...this.getPrefix(), args); } public warn(...args: unknown[]) { this.logger.warn("[WARN]", ...this.getPrefix(), args); } public error(...args: unknown[]) { this.logger.error("[ERROR]", ...this.getPrefix(), args); } public addTracer(traceSupplier: TraceSupplier) { return new TraceableLogger(this.traces.concat(traceSupplier)); } private getPrefix() { return this.traces.map((tracer) => tracer()); } } export class TraceableImpl< T, L extends ITraceableLogger, > implements ITraceable { private constructor(readonly item: T, readonly logger: L) {} public map(mapper: ITraceableMapper) { const result = mapper(this); return new TraceableImpl(result, this.logger); } public flatMap(mapper: ITraceableMapper>): ITraceable { return mapper(this); } public flatMapAsync(mapper: ITraceableMapper>>): ITraceable, L> { return new TraceableImpl(mapper(this).then((i) => ) } public peek(peek: ITraceableMapper) { peek(this); return this; } public bimap(mapper: ITraceableMapper>) { const [item, trace] = mapper(this); return new TraceableImpl(item, this.logger.addTracer(trace)); } static promiseify, U>( mapper: ITraceableMapper, ): ITraceableMapper, L, Promise> { return (traceablePromise) => traceablePromise.map( async ({ item: promise }) => { const t = await promise; return traceablePromise.map(() => t).map(mapper).item; }); // return (traceable) => // traceable.item.then((item) => mapper(new TraceableImpl(item, traceable.logger))); } static from(t: T) { return new TraceableImpl(t, new TraceableLogger()); } } export interface Traceable extends ITraceable