From 0d98ef9044cc480af225881b4fa370dcfa11c750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20Sj=C3=B6green?= Date: Wed, 21 Feb 2024 13:34:11 +0100 Subject: [PATCH] feat: alternative ConsoleReadableStreamOptions and make options partial --- deno.json | 2 +- mod.ts | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 6 deletions(-) diff --git a/deno.json b/deno.json index 645e69e..216d077 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@denosaurs/log", - "version": "0.0.4", + "version": "0.0.5", "exports": { ".": "./mod.ts", "./transforms/filter": "./transforms/filter.ts", diff --git a/mod.ts b/mod.ts index 2267be4..4e8919a 100644 --- a/mod.ts +++ b/mod.ts @@ -2,6 +2,10 @@ const console = globalThis.console; +type RecursivePartial = { + [P in keyof T]?: RecursivePartial; +}; + export type LogLevel = | "trace" | "debug" @@ -27,16 +31,99 @@ export interface Log { export interface ConsoleReadableStreamOptions { internals: { + /** + * @returns A millisecond resolution unix timestamp of when the log was made + */ now: () => number; + /** + * Clones the given data so that references are not shared between logs. + */ clone: (data: T) => T; }; } +/** + * Default options for the {@link ConsoleReadableStreamOptions}. It uses normal + * resolution timestamps by calling `Date.now()` and a deep cloning by + * using the structured clone algorithm. + */ export const defaultConsoleReadableStreamOptions: ConsoleReadableStreamOptions = + { + internals: { + now: Date.now, + clone: (data: T) => { + if ( + typeof data === "string" || + typeof data === "number" || + typeof data === "boolean" || + data === null || + data === undefined + ) { + return data; + } + + return structuredClone(data); + }, + }, + }; + +/** + * This is the fastest option, but it may not be suitable for all use cases. + * Read the caution below before using these default options. + * + * Fast defaults for the {@link ConsoleReadableStreamOptions}. It uses normal + * resolution timestamps by calling `Date.now()` and a shallow cloning by + * transforming the data to `JSON` and back. + * + * Caution: This option only supports JSON-serializable data and data may be + * lost in the process making future transforms which rely on non-json types + * impossible. If you need to support non-json types, you should use the + * {@link defaultConsoleReadableStreamOptions} instead. + */ +export const fastConsoleReadableStreamOptions: ConsoleReadableStreamOptions = { + internals: { + now: Date.now, + clone: (data: T) => { + if ( + typeof data === "string" || + typeof data === "number" || + typeof data === "boolean" || + data === null || + data === undefined + ) { + return data; + } + + return JSON.parse(JSON.stringify(data)); + }, + }, +}; + +/** + * This is the most accurate option, but may suffer from a slight performance + * penalty by using high resolution timestamps and a deep cloning by using the + * structured clone algorithm. + * + * Use this option if you need to support non-json types and need high + * resolution timestamps. + */ +export const hrtimeConsoleReadableStreamOptions: ConsoleReadableStreamOptions = { internals: { now: () => performance.timeOrigin + performance.now(), - clone: (data: T) => structuredClone(data), + clone: (data: T) => { + if ( + typeof data === "string" || + typeof data === "number" || + typeof data === "boolean" || + data === null || + data === undefined + ) { + return data; + } + + return structuredClone(data); + }, }, }; @@ -49,9 +136,18 @@ export const defaultConsoleReadableStreamOptions: ConsoleReadableStreamOptions = * object, call the {@link ConsoleReadableStream.cancel} method. */ export class ConsoleReadableStream extends ReadableStream { + #options: ConsoleReadableStreamOptions; + constructor( - options: ConsoleReadableStreamOptions = defaultConsoleReadableStreamOptions, + options: RecursivePartial = + defaultConsoleReadableStreamOptions, ) { + options ??= defaultConsoleReadableStreamOptions; + options.internals ??= defaultConsoleReadableStreamOptions.internals; + options.internals.now ??= defaultConsoleReadableStreamOptions.internals.now; + options.internals.clone ??= + defaultConsoleReadableStreamOptions.internals.clone; + let controller: ReadableStreamDefaultController; const groups: any[] = []; @@ -61,12 +157,14 @@ export class ConsoleReadableStream extends ReadableStream { }, }); + this.#options = options as ConsoleReadableStreamOptions; + const wrapper = (level: LogLevel) => (...data: any[]) => { controller.enqueue({ - timestamp: options.internals.now(), + timestamp: this.#options.internals.now(), level, - groups: groups.length === 0 ? [] : options.internals.clone(groups), - data: options.internals.clone(data), + groups: this.#options.internals.clone(groups), + data: this.#options.internals.clone(data), }); };