diff --git a/README.md b/README.md index 3d7bd1e..5b4e3d4 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,7 @@ import "@denosaurs/log/transforms/text_encoder_stream"; import { ConsoleReadableStream } from "@denosaurs/log"; -import { getStdoutWritableStream } from "@denosaurs/log/writables/stdout"; +import { StdoutWritableStream } from "@denosaurs/log/writables/std"; import { JsonStringifyStream } from "@std/json"; @@ -118,7 +118,7 @@ stream // Encode the output to an UTF-8 byte stream .pipeThrough(new TextEncoderStream()) // Pipe the output to stdout - .pipeTo(getStdoutWritableStream()); + .pipeTo(new StdoutWritableStream()); // Log some messages console.log("Hello, world!"); @@ -143,8 +143,10 @@ import "@denosaurs/log/transforms/text_encoder_stream"; import { ConsoleReadableStream } from "@denosaurs/log"; -import { getStderrWritableStream } from "@denosaurs/log/writables/stderr"; -import { getStdoutWritableStream } from "@denosaurs/log/writables/stdout"; +import { + StderrWritableStream, + StdoutWritableStream, +} from "@denosaurs/log/writables/std"; import { OmitLogLevelStream } from "@denosaurs/log/transforms/omit"; import { PickLogLevelStream } from "@denosaurs/log/transforms/pick"; @@ -164,7 +166,7 @@ a // Encode the output to an UTF-8 byte stream .pipeThrough(new TextEncoderStream()) // Pipe the output to stdout - .pipeTo(getStdoutWritableStream()); + .pipeTo(new StdoutWritableStream()); b // Pick only the error logs @@ -174,7 +176,7 @@ b // Encode the output to an UTF-8 byte stream .pipeThrough(new TextEncoderStream()) // Pipe the output to stderr - .pipeTo(getStderrWritableStream()); + .pipeTo(new StderrWritableStream()); // Log some messages console.error("This is going to stderr"); diff --git a/benchmarks/deno.json b/benchmarks/deno.json index 71b770b..1937fd2 100644 --- a/benchmarks/deno.json +++ b/benchmarks/deno.json @@ -7,8 +7,7 @@ "@denosaurs/log/transforms/pick": "../transforms/pick.ts", "@denosaurs/log/transforms/text_encoder_stream": "../transforms/text_encoder_stream.ts", "@denosaurs/log/writables/console": "../writables/console.ts", - "@denosaurs/log/writables/stderr": "../writables/stderr.ts", - "@denosaurs/log/writables/stdout": "../writables/stdout.ts", + "@denosaurs/log/writables/std": "../writables/std.ts", "@std/json": "jsr:@std/json", "bole": "npm:bole", "bunyan": "npm:bunyan", diff --git a/deno.json b/deno.json index feaa501..b803d0d 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@denosaurs/log", - "version": "0.0.11", + "version": "0.0.12", "exports": { ".": "./mod.ts", "./transforms/filter": "./transforms/filter.ts", @@ -10,8 +10,7 @@ "./transforms/redact": "./transforms/redact.ts", "./transforms/text_encoder_stream": "./transforms/text_encoder_stream.ts", "./writables/console": "./writables/console.ts", - "./writables/stderr": "./writables/stderr.ts", - "./writables/stdout": "./writables/stdout.ts" + "./writables/std": "./writables/std.ts" }, "lock": false, "tasks": { diff --git a/examples/deno.json b/examples/deno.json index 8aed657..8a42c11 100644 --- a/examples/deno.json +++ b/examples/deno.json @@ -9,8 +9,7 @@ "@denosaurs/log/transforms/redact": "../transforms/redact.ts", "@denosaurs/log/transforms/text_encoder_stream": "../transforms/text_encoder_stream.ts", "@denosaurs/log/writables/console": "../writables/console.ts", - "@denosaurs/log/writables/stderr": "../writables/stderr.ts", - "@denosaurs/log/writables/stdout": "../writables/stdout.ts", + "@denosaurs/log/writables/std": "../writables/std.ts", "@std/json": "jsr:@std/json" }, "tasks": { diff --git a/examples/json/json.js b/examples/json/json.js index a129f6d..875edda 100644 --- a/examples/json/json.js +++ b/examples/json/json.js @@ -3,7 +3,7 @@ import "@denosaurs/log/transforms/text_encoder_stream"; import { ConsoleReadableStream } from "@denosaurs/log"; -import { getStdoutWritableStream } from "@denosaurs/log/writables/stdout"; +import { StdoutWritableStream } from "@denosaurs/log/writables/std"; import { JsonStringifyStream } from "@std/json"; @@ -15,7 +15,7 @@ stream // Encode the output to an UTF-8 byte stream .pipeThrough(new TextEncoderStream()) // Pipe the output to stdout - .pipeTo(getStdoutWritableStream()); + .pipeTo(new StdoutWritableStream()); // Log some messages console.log("Hello, world!"); diff --git a/examples/tee/tee.js b/examples/tee/tee.js index 8b8b4d6..ab5623d 100644 --- a/examples/tee/tee.js +++ b/examples/tee/tee.js @@ -3,8 +3,10 @@ import "@denosaurs/log/transforms/text_encoder_stream"; import { ConsoleReadableStream } from "@denosaurs/log"; -import { getStdoutWritableStream } from "@denosaurs/log/writables/stdout"; -import { getStderrWritableStream } from "@denosaurs/log/writables/stderr"; +import { + StderrWritableStream, + StdoutWritableStream, +} from "@denosaurs/log/writables/std"; import { OmitLogLevelStream } from "@denosaurs/log/transforms/omit"; import { PickLogLevelStream } from "@denosaurs/log/transforms/pick"; @@ -24,7 +26,7 @@ a // Encode the output to an UTF-8 byte stream .pipeThrough(new TextEncoderStream()) // Pipe the output to stdout - .pipeTo(getStdoutWritableStream()); + .pipeTo(new StdoutWritableStream()); b // Pick only the error logs @@ -34,7 +36,7 @@ b // Encode the output to an UTF-8 byte stream .pipeThrough(new TextEncoderStream()) // Pipe the output to stderr - .pipeTo(getStderrWritableStream()); + .pipeTo(new StderrWritableStream()); // Log some messages console.error("This is going to stderr"); diff --git a/utils/std.ts b/utils/std.ts deleted file mode 100644 index 9661960..0000000 --- a/utils/std.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { environment, isNode } from "../utils/runtime.ts"; - -let nodeWritableToWeb: ( - stream: unknown, -) => WritableStream | undefined; -if (isNode) { - nodeWritableToWeb = (await import("node:stream")).Writable.toWeb as ( - stream: unknown, - ) => WritableStream; -} - -export function getStdWritableStream( - stream: "stdout" | "stderr", -): WritableStream { - switch (environment) { - case "deno": - return globalThis.Deno[stream].writable; - case "bun": { - // Once https://github.com/oven-sh/bun/issues/3927 is completed we can use the node code for bun. - return new WritableStream({ - write: async (chunk) => { - // @ts-expect-error: The type checking environment is deno, the bun types are not available - await globalThis.Bun.write(globalThis.Bun[stream], chunk); - }, - }); - } - case "node": { - // @ts-expect-error: The type checking environment is deno, the node types are not available - return nodeWritableToWeb!(globalThis.process[stream]); - } - case "browser": - case "unknown": { - const decoder = new TextDecoder(); - let buffer = ""; - const write = stream === "stdout" ? console.log : console.error; - return new WritableStream({ - write: (chunk) => { - buffer += decoder.decode(chunk); - const lines = buffer.split("\n"); - if (lines.length > 1) { - buffer = lines.pop() ?? ""; - for (const line of lines) { - write(line); - } - } - }, - close: () => { - if (buffer.length > 0) { - const lines = buffer.split("\n"); - for (const line of lines) { - write(line); - } - } - }, - }); - } - } -} diff --git a/writables/std.ts b/writables/std.ts new file mode 100644 index 0000000..4f4b77d --- /dev/null +++ b/writables/std.ts @@ -0,0 +1,115 @@ +/** + * # StdoutWritableStream and StderrWritableStream + * + * Cross-runtime writable streams for the standard output and standard error. + * + * @example + * ```ts + * import { ConsoleReadableStream } from "@denosaurs/log"; + * import { StdoutWritableStream } from "@denosaurs/log/writables/std"; + * import { JsonStringifyStream } from "@std/json"; + * + * // Capture logs from the console + * const stream = new ConsoleReadableStream(); + * stream + * // Stringify the logs to JSON + * .pipeThrough(new JsonStringifyStream()) + * // Encode the output to an UTF-8 byte stream + * .pipeThrough(new TextEncoderStream()) + * // Pipe the output to stdout + * .pipeTo(new StdoutWritableStream()); + * ``` + * + * @module + */ + +import { originalConsole } from "../utils/original_console.ts"; +import { environment, isNode } from "../utils/runtime.ts"; + +let nodeWritableToWeb: ( + stream: unknown, +) => WritableStream | undefined; +if (isNode) { + nodeWritableToWeb = (await import("node:stream")).Writable.toWeb as ( + stream: unknown, + ) => WritableStream; +} + +/** + * A writable stream for standard output or error. + */ +export class StdWritableStream extends WritableStream { + constructor(stream: "stdout" | "stderr") { + let sink: UnderlyingSink; + switch (environment) { + case "deno": + sink = { + write: async (chunk) => { + await globalThis.Deno[stream].write(chunk); + }, + } + break; + case "bun": + // Once https://github.com/oven-sh/bun/issues/3927 is completed we can use the node code for bun. + sink = { + write: async (chunk) => { + // @ts-expect-error: The type checking environment is deno, the bun types are not available + await globalThis.Bun.write(globalThis.Bun[stream], chunk); + }, + }; + break; + case "node": + // @ts-expect-error: The type checking environment is deno, the node types are not available + sink = nodeWritableToWeb!(globalThis.process[stream]); + break; + case "browser": + case "unknown": { + const decoder = new TextDecoder(); + let buffer = ""; + const write = stream === "stdout" ? originalConsole.log : originalConsole.error; + + sink = { + write: (chunk) => { + buffer += decoder.decode(chunk); + const lines = buffer.split("\n"); + if (lines.length > 1) { + buffer = lines.pop() ?? ""; + for (const line of lines) { + write(line); + } + } + }, + close: () => { + if (buffer.length > 0) { + const lines = buffer.split("\n"); + for (const line of lines) { + write(line); + } + } + }, + }; + break; + } + } + + super(sink); + } +} + +/** + * A writable stream for standard output. + */ +export class StdoutWritableStream extends StdWritableStream { + constructor() { + super("stdout"); + } +} + +/** + * A writable stream for standard error. + */ +export class StderrWritableStream extends StdWritableStream { + constructor() { + super("stderr"); + } +} diff --git a/writables/stderr.ts b/writables/stderr.ts deleted file mode 100644 index 38ee6ef..0000000 --- a/writables/stderr.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getStdWritableStream } from "../utils/std.ts"; - -/** - * Get a runtime agnostic writable stream for stderr. - */ -export function getStderrWritableStream(): WritableStream { - return getStdWritableStream("stderr"); -} diff --git a/writables/stdout.ts b/writables/stdout.ts deleted file mode 100644 index 2e6b180..0000000 --- a/writables/stdout.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { getStdWritableStream } from "../utils/std.ts"; - -/** - * Get a runtime agnostic writable stream for stdout. - */ -export function getStdoutWritableStream(): WritableStream { - return getStdWritableStream("stdout"); -}