// / import { isPromise } from "./is-promise.js"; import { bin2string } from "./bin2text.js"; import { Option } from "./option.js"; import { Result } from "./result.js"; import { TxtEnDecoder, TxtEnDecoderSingleton } from "./txt-en-decoder.js"; import { CoerceURI } from "./uri.js"; import { isJSON } from "./is-json.js"; import { ReadonlyURL } from "./mutable-url.js"; import { FnSerialized, Level, LogSerializable, LogValue, Serialized } from "./types.js"; // export function sanitizeSerialize(lineEnd?: string): (key: unknown, val: unknown) => unknown { // const cache = new Set(); // return function (this: unknown, key: unknown, value: unknown) { // if (typeof value === "object" && value !== null) { // // Duplicate reference found, discard key // if (cache.has(value)) return "..."; // cache.add(value); // } // return lineEnd ? value + lineEnd : value; // }; // } export function asyncLogValue(_val: () => Promise): Promise { // return Promise.resolve(logValue(val)); throw new Error("Not implemented"); } export type LogValueArg = LogValue | Serialized | Serialized[] | FnSerialized | undefined | null; export interface LogValueState { readonly state?: Set; readonly ignoreAttr: Option; } export function logValue(val: LogValueArg, ctx: LogValueState): LogValue { return logValueInternal(val, { ...ctx, state: ctx.state || new Set([Math.random()]), }); } type LogValueStateInternal = LogValueState & { readonly state: Set }; function logValueInternal(val: LogValueArg, ctx: LogValueStateInternal): LogValue { ctx = { ...ctx, state: ctx.state || new Set([Math.random()]), } satisfies LogValueStateInternal; switch (typeof val) { case "function": return new LogValue(val); case "string": { const resIsJson = isJSON(val); if (resIsJson.isJSON) { const ret = resIsJson.parsed; if (typeof ret === "object" && ret !== null) { return logValueInternal(ret, ctx); } } const resIsURI = ReadonlyURL.from(val); if (resIsURI.isOk()) { return new LogValue(() => resIsURI.Ok().toString()); } if (val.match(/[\n\r]/)) { const lines = val.split(/[\n\r]+/).map((v) => v.trim()); return new LogValue(() => lines); } return new LogValue(() => val.toString()); } case "number": return new LogValue(() => val); case "boolean": return new LogValue(() => val); case "object": { if (val === null) { return new LogValue(() => "null"); } if (ArrayBuffer.isView(val)) { try { // should be injected const decoder = TxtEnDecoderSingleton(); const asStr = decoder.decode(val); const resIsJson = isJSON(asStr); if (resIsJson.isJSON) { const obj = JSON.parse(asStr) as LogValueArg; return logValueInternal(obj, ctx); } } catch (e) { // noop, we will return the stringified value } return logValueInternal(bin2string(val, 512), ctx); } if (Array.isArray(val)) { return new LogValue(() => (val as Serialized[]).map((v) => logValue(v, { ...ctx, state: undefined }).value() as Serialized), ); } // if (val instanceof Response) { // // my = my.clone() as unknown as LogValue | Serialized[] | null // // const rval = my as unknown as Partial; // // delete rval.clone // // delete rval.blob // } if (val instanceof Headers) { const headers: Record = {}; val.forEach((v, k) => { headers[k] = v; }); return new LogValue(() => headers as unknown as Serialized); } if (val instanceof ReadableStream) { return new LogValue(() => ">Stream<"); } if (isPromise(val)) { return new LogValue(() => ">Promise<"); } // Duplicate reference found, discard key if (ctx.state?.has(val)) { return new LogValue(() => "..."); } ctx.state?.add(val); try { if (typeof val.toJSON === "function") { return new LogValue(() => val.toJSON()); } } catch (e) { /* chrome is a strange beast Failed to read a named property 'toJSON' from 'Window': Blocked a frame with origin "http://localhost:3001" from accessing a cross-origin frame. */ } const res: Record = {}; const typedVal = val as unknown as Record; for (const key in typedVal) { if (ctx.ignoreAttr.IsSome() && ctx.ignoreAttr.unwrap().test(key)) { continue; } const element = typedVal[key]; if (element instanceof LogValue) { res[key] = element; } else { if (typeof element !== "function") { res[key] = logValueInternal(element, ctx); } } } // ugly as hell cast but how declare a self-referencing type? return new LogValue(() => res as unknown as Serialized); } default: if (!val) { return new LogValue(() => "--Falsy--"); } throw new Error(`Invalid type:${typeof val}`); } } export interface Sized { size: number; } export interface Lengthed { length: number; } export type SizeOrLength = Sized | Lengthed; export interface LogFormatter { format(attr: LogSerializable): Uint8Array; } export interface LevelHandler { enableLevel(level: Level, ...modules: string[]): void; disableLevel(level: Level, ...modules: string[]): void; setExposeStack(enable?: boolean): void; setIgnoreAttr(re?: RegExp): void; ignoreAttr: Option; isStackExposed: boolean; timerStart(key: string): Date; // now timerEnd(key: string): { now: Date; duration: number }; // returns duration in nanoseconds setDebug(...modules: (string | string[])[]): void; isEnabled(ilevel: unknown, module: unknown): boolean; } export type HttpType = Response | Result | Request | Result; export interface LoggerInterface { readonly levelHandler: LevelHandler; TxtEnDe(): TxtEnDecoder; Module(key: string): R; // if modules is empty, set for all Levels EnableLevel(level: Level, ...modules: string[]): R; DisableLevel(level: Level, ...modules: string[]): R; Attributes(): Record; SetDebug(...modules: (string | string[])[]): R; // default is /^_/ SetIgnoreAttribute(re?: RegExp): R; SetExposeStack(enable?: boolean): R; SetFormatter(fmt: LogFormatter): R; Ref(key: string, action: { toString: () => string } | FnSerialized): R; Result(key: string, res: Result): R; // default key url Url(url: CoerceURI, key?: string): R; // len Len(value: unknown, key?: string): R; Hash(value: unknown, key?: string): R; Str>(key: T, value?: T extends string ? string : undefined): R; Uint64>(key: T, value?: T extends string ? number : undefined): R; Int>(key: T, value?: T extends string ? number : undefined): R; Bool>(key: T, value?: T extends string ? unknown : undefined): R; Any>(key: T, value?: T extends string ? unknown : undefined): R; // first string is the key // first response is Response // first request is Request Http(...mix: (HttpType | string)[]): R; Pair(x: Record): R; Error(): R; Warn(): R; Debug(): R; Log(): R; WithLevel(level: Level): R; Err(err: T | Result | Error): R; // could be Error, or something which coerces to string Info(): R; Timestamp(): R; TimerStart(key: string): R; TimerEnd(key: string): R; Dur(key: string, nsec: number): R; } export function IsLogger(obj: unknown): obj is Logger { return ( typeof obj === "object" && [ "Module", "EnableLevel", "DisableLevel", "SetDebug", "Str", "Error", "Warn", "Debug", "Log", "WithLevel", "Err", "Info", "Timestamp", "Any", "Dur", "Uint64", ] .map((fn) => typeof (obj as Record)[fn] === "function") .reduce((a, b) => a && b, true) ); } export interface WithLogger extends LoggerInterface { Logger(): Logger; } export interface AsError { AsError(): Error; ResultError(): Result; } export interface Logger extends LoggerInterface { With(): WithLogger; Msg(...args: string[]): AsError; Flush(): Promise; }