From 1d66a0f58e4ebcdf4f42c9d78f82a1ab49a2cf11 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Tue, 13 May 2025 18:58:45 -0700 Subject: snapshot! --- u/trace/itrace.ts | 72 ++++++++++++++++++++++++++++++++++++++++++++++ u/trace/logger.ts | 86 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ u/trace/mod.ts | 3 ++ u/trace/trace.ts | 34 ++++++++++++++++++++++ 4 files changed, 195 insertions(+) create mode 100644 u/trace/itrace.ts create mode 100644 u/trace/logger.ts create mode 100644 u/trace/mod.ts create mode 100644 u/trace/trace.ts (limited to 'u/trace') diff --git a/u/trace/itrace.ts b/u/trace/itrace.ts new file mode 100644 index 0000000..b483067 --- /dev/null +++ b/u/trace/itrace.ts @@ -0,0 +1,72 @@ +import { Mapper, SideEffect } from "../fn/mod.ts"; + +export interface ITrace { + addTrace: Mapper>; + trace: SideEffect; +} + +export type ITraceableTuple = [T, Trace]; +export type ITraceableMapper> = ( + w: W, +) => U; + +export interface ITraceable { + readonly item: T; + readonly trace: ITrace; + + move(u: U): ITraceable; + map: ( + mapper: ITraceableMapper, + ) => ITraceable; + bimap: ( + mapper: ITraceableMapper>, + ) => ITraceable; + peek: (peek: ITraceableMapper) => ITraceable; + flatMap: ( + mapper: ITraceableMapper>, + ) => ITraceable; + flatMapAsync( + mapper: ITraceableMapper>>, + ): ITraceable, Trace>; +} + +export class TraceableImpl implements ITraceable { + protected constructor( + readonly item: T, + readonly trace: ITrace, + ) {} + + public map(mapper: ITraceableMapper) { + const result = mapper(this); + return new TraceableImpl(result, this.trace); + } + + public flatMap( + mapper: ITraceableMapper>, + ): ITraceable { + return mapper(this); + } + + public flatMapAsync( + mapper: ITraceableMapper>>, + ): ITraceable, L> { + return new TraceableImpl( + mapper(this).then(({ item }) => item), + this.trace, + ); + } + + public peek(peek: ITraceableMapper) { + peek(this); + return this; + } + + public move(t: Tt): ITraceable { + return this.map(() => t); + } + + public bimap(mapper: ITraceableMapper>) { + const [item, trace] = mapper(this); + return new TraceableImpl(item, this.trace.addTrace(trace)); + } +} diff --git a/u/trace/logger.ts b/u/trace/logger.ts new file mode 100644 index 0000000..79da367 --- /dev/null +++ b/u/trace/logger.ts @@ -0,0 +1,86 @@ +import { + isDebug, + isObject, + type ITrace, + type SideEffect, + type Supplier, +} from "@emprespresso/utils"; + +export interface ILogger { + log: (...args: unknown[]) => void; + debug: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; +} +export type ILoggerLevel = "UNKNOWN" | "INFO" | "WARN" | "DEBUG" | "ERROR"; +const logLevelOrder: Array = ["DEBUG", "INFO", "WARN", "ERROR"]; +const defaultAllowedLevels = () => + [ + "UNKNOWN", + ...(isDebug() ? ["DEBUG"] : []), + "INFO", + "WARN", + "ERROR", + ] as Array; + +export const logWithLevel = ( + logger: ILogger, + level: ILoggerLevel, +): SideEffect => { + switch (level) { + case "UNKNOWN": + case "INFO": + return logger.log; + case "DEBUG": + return logger.debug; + case "WARN": + return logger.warn; + case "ERROR": + return logger.error; + } +}; + +export const LoggerImpl = console; + +export type LogTraceSupplier = string | Supplier | { + level: ILoggerLevel; +}; + +const foldTraces = (traces: Array) => { + const { line, level } = traces.reduce( + (acc: { line: string; level: number }, t) => { + if (isObject(t) && "level" in t) { + return { + ...acc, + level: Math.max(logLevelOrder.indexOf(t.level), acc.level), + }; + } + const prefix = [ + acc.line, + typeof t === "function" ? t() : t, + ].join(" "); + return { ...acc, prefix }; + }, + { line: "", level: -1 }, + ); + return { line, level: logLevelOrder[level] ?? "UNKNOWN" }; +}; + +const defaultTrace = () => `[${new Date().toISOString()}]`; +export const LogTrace = ( + logger: ILogger, + traces: Array = [defaultTrace], + allowedLevels: Supplier> = defaultAllowedLevels, + defaultLevel: ILoggerLevel = "INFO", +): ITrace => { + return { + addTrace: (trace: LogTraceSupplier) => + LogTrace(logger, traces.concat(trace)), + trace: (trace: LogTraceSupplier) => { + const { line, level: _level } = foldTraces(traces.concat(trace)); + if (!allowedLevels().includes(_level)) return; + const level = _level === "UNKNOWN" ? defaultLevel : _level; + logWithLevel(logger, level)(`[${level}]${line}`); + }, + }; +}; diff --git a/u/trace/mod.ts b/u/trace/mod.ts new file mode 100644 index 0000000..9c42858 --- /dev/null +++ b/u/trace/mod.ts @@ -0,0 +1,3 @@ +export * from "./itrace.ts"; +export * from "./logger.ts"; +export * from "./trace.ts"; diff --git a/u/trace/trace.ts b/u/trace/trace.ts new file mode 100644 index 0000000..5d5c59b --- /dev/null +++ b/u/trace/trace.ts @@ -0,0 +1,34 @@ +import type { Callable } from "@emprespresso/utils"; +import { + type ITraceableMapper, + type ITraceableTuple, + TraceableImpl, + TraceableLogger, +} from "./mod.ts"; + +export class Traceable extends TraceableImpl { + static from(t: T) { + return new Traceable(t, new TraceableLogger()); + } + + static withFunctionTrace( + f: F, + ): ITraceableMapper> { + return (t) => [t.item, f.name]; + } + + static withClassTrace( + c: C, + ): ITraceableMapper> { + return (t) => [t.item, c.constructor.name]; + } + + static promiseify( + mapper: ITraceableMapper, + ): ITraceableMapper, TraceableLogger, Promise> { + return (traceablePromise) => + traceablePromise.flatMapAsync(async (t) => + t.move(await t.item).map(mapper) + ).item; + } +} -- cgit v1.2.3-70-g09d2