summaryrefslogtreecommitdiff
path: root/utils/trace.ts
blob: 373f37ec290fda0ff7d1faa305575be918a2243b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
export interface Logger {
  log: (...args: unknown[]) => void;
  debug: (...args: unknown[]) => void;
  warn: (...args: unknown[]) => void;
  error: (...args: unknown[]) => void;
}

type Supplier<T> = () => T;
type TraceSupplier = Supplier<string>;
export interface ITraceableLogger<L extends ITraceableLogger<L>>
  extends Logger {
  addTracer: (traceSupplier: TraceSupplier) => L;
}

export type ITraceableTuple<T> = [T, TraceSupplier];
export type ITraceableMapper<T, L extends ITraceableLogger<L>, U> = (
  t: ITraceable<T, L>,
) => U;
export interface ITraceable<T, L extends ITraceableLogger<L>> {
  item: T;
  logger: L;

  map: <U>(mapper: ITraceableMapper<T, L, U>) => ITraceable<U, L>;
  bimap: <U>(
    mapper: ITraceableMapper<T, L, ITraceableTuple<U>>,
  ) => ITraceable<U, L>;
  peek: (peek: ITraceableMapper<T, L, void>) => ITraceable<T, L>;
  flatMap: <U>(
    mapper: ITraceableMapper<T, L, ITraceable<U, L>>,
  ) => ITraceable<U, L>;
  flatMapAsync<U>(
    mapper: ITraceableMapper<T, L, Promise<ITraceable<U, L>>>,
  ): ITraceable<Promise<U>, L>;
}

export class TraceableLogger implements ITraceableLogger<TraceableLogger> {
  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());
  }
}

class TraceableImpl<
  T,
  L extends ITraceableLogger<L>,
> implements ITraceable<T, L> {
  protected constructor(readonly item: T, readonly logger: L) {}

  public map<U>(mapper: ITraceableMapper<T, L, U>) {
    const result = mapper(this);
    return new TraceableImpl(result, this.logger);
  }

  public flatMap<U>(
    mapper: ITraceableMapper<T, L, ITraceable<U, L>>,
  ): ITraceable<U, L> {
    return mapper(this);
  }

  public flatMapAsync<U>(
    mapper: ITraceableMapper<T, L, Promise<ITraceable<U, L>>>,
  ): ITraceable<Promise<U>, L> {
    return new TraceableImpl(
      mapper(this).then(({ item }) => item),
      this.logger,
    );
  }

  public peek(peek: ITraceableMapper<T, L, void>) {
    peek(this);
    return this;
  }

  public bimap<U>(mapper: ITraceableMapper<T, L, ITraceableTuple<U>>) {
    const [item, trace] = mapper(this);
    return new TraceableImpl(item, this.logger.addTracer(trace));
  }

  static promiseify<T, L extends ITraceableLogger<L>, U>(
    mapper: ITraceableMapper<T, L, U>,
  ): ITraceableMapper<Promise<T>, L, Promise<U>> {
    return (traceablePromise) =>
      traceablePromise.flatMapAsync(async (t) => {
        const item = await t.item;
        return t.map(() => item).map(mapper);
      }).item;
  }
}

export class Traceable<T> extends TraceableImpl<T, TraceableLogger> {
  static withClassTrace<C extends object, T>(
    c: C,
  ): ITraceableMapper<T, TraceableLogger, ITraceableTuple<T>> {
    return (t) => [t.item, () => c.constructor.name];
  }

  static from<T>(t: T) {
    return new Traceable(t, new TraceableLogger());
  }
}