@steve02081504/virtual-console 0.0.3 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +46 -36
  2. package/main.mjs +9 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # Virtual Console
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@steve02081504/virtual-console.svg)](https://www.npmjs.com/package/@steve02081504/virtual-console)
4
- [![GitHub license](https://img.shields.io/github/license/steve02081504/virtual-console)](https://github.com/steve02081504/virtual-console/blob/main/LICENSE)
5
4
  [![GitHub issues](https://img.shields.io/github/issues/steve02081504/virtual-console)](https://github.com/steve02081504/virtual-console/issues)
6
5
 
7
6
  A powerful and flexible virtual console for Node.js that allows you to capture, manipulate, and redirect terminal output. Built with modern asynchronous contexts (`AsyncLocalStorage`) for robust, concurrency-safe operations.
@@ -15,27 +14,32 @@ A powerful and flexible virtual console for Node.js that allows you to capture,
15
14
 
16
15
  ---
17
16
 
18
- ## Core Concept: The Global Console Proxy
17
+ ## How It Works: The Global Console Proxy
19
18
 
20
- **This library is designed for zero-refactoring integration.** Upon import, it replaces `globalThis.console` with a smart proxy. Here’s how it works:
19
+ **This library is designed for zero-refactoring integration.** Upon import, it replaces `globalThis.console` with a smart proxy that is aware of the asynchronous execution context.
21
20
 
22
- 1. **Default Behavior (No-op):** By default, the proxy simply forwards all console calls (`console.log`, etc.) to the original, real console. It doesn't record or change anything. This makes the library safe to include anywhere without side effects.
21
+ 1. **The Proxy:** The new `console` object is a proxy. When you call a method like `console.log()`, the proxy intercepts the call.
23
22
 
24
- 2. **Activation via `hookAsyncContext`:** To capture or manipulate output, you create a `new VirtualConsole()` instance and activate it for a specific block of code using `vc.hookAsyncContext(yourFunction)`.
23
+ 2. **`AsyncLocalStorage` for Isolation:** The proxy uses `AsyncLocalStorage` to look up the currently active `VirtualConsole` instance for the current async operation.
25
24
 
26
- 3. **Context-Aware Routing:** Inside the `hookAsyncContext` block, the proxy detects the active `VirtualConsole` instance and routes all `console` calls to it. This allows your instance to capture output, handle stateful methods like `freshLine`, or apply custom logic, all while being completely isolated from other asynchronous operations.
25
+ 3. **Context-Aware Routing:**
26
+ - If you have activated a `VirtualConsole` instance using `vc.hookAsyncContext()`, the proxy finds your instance and routes all `console` calls to it. This allows your instance to capture output, handle stateful methods like `freshLine`, or apply custom logic.
27
+ - If no instance is active for the current context, the proxy forwards the call to a default, passthrough console that simply prints to the original terminal, just like the real `console` would.
27
28
 
28
- This architecture means you **don't need to pass console instances around**. Just keep using the global `console` as you always have, and wrap the code you want to monitor.
29
+ This architecture means you **don't need to refactor your code to pass console instances around**. Just keep using the global `console` as you always have, and wrap the code you want to monitor in a hook.
30
+
31
+ **Enhanced Compatibility:** The proxy is designed to be a good citizen. If other libraries or your own code assign custom properties to the global `console` object (e.g., `console.myLogger = ...`), these properties are preserved and remain accessible.
29
32
 
30
33
  ## Features
31
34
 
32
35
  - **Zero-Configuration Capturing:** Capture output from any module without changing its source code.
33
36
  - **Concurrency-Safe Isolation:** Uses `AsyncLocalStorage` to guarantee that output from concurrent operations is captured independently and correctly.
34
- - **Output Recording:** Captures all `stdout` and `stderr` output to a string property for inspection.
37
+ - **Output Recording:** Captures all `stdout` and `stderr` output to a string property for easy inspection.
35
38
  - **Real Console Passthrough:** Optionally, print to the actual console while also capturing.
36
- - **TTY Emulation:** Behaves like a real TTY, inheriting properties like `columns`, `rows`, and color support.
37
- - **Updatable Lines (`freshLine`)**: A stateful method for creating overwritable lines, perfect for progress indicators.
38
- - **Extensible:** Provide a custom base console, define a dedicated `Error` handler, and more.
39
+ - **Full TTY Emulation:** Behaves like a real TTY, inheriting properties like `columns`, `rows`, and color support from the base console. It also respects terminal resize events.
40
+ - **Updatable Lines (`freshLine`)**: A stateful method for creating overwritable lines, perfect for progress indicators. Requires a TTY with ANSI support.
41
+ - **Custom Error Handling:** Provide a dedicated handler for `Error` objects passed to `console.error`.
42
+ - **Extensible:** Can be integrated with other async-context-aware libraries via `setGlobalConsoleReflect`.
39
43
 
40
44
  ## Installation
41
45
 
@@ -107,29 +111,31 @@ await Promise.all([
107
111
  console.log('All tasks finished!');
108
112
  ```
109
113
 
110
- ### Use Case: Activating a Hook for an Entire Async Flow
114
+ ### Use Case: Custom Error Handling
111
115
 
112
- You can call `hookAsyncContext()` without a function to activate an instance for the *rest of the current asynchronous context*. This is useful in frameworks like Express or Koa middleware.
116
+ You can provide a dedicated `error_handler` to process `Error` objects passed to `console.error`. This is useful for integrating with logging services or custom error-reporting frameworks.
113
117
 
114
118
  ```javascript
115
- const vc = new VirtualConsole({ realConsoleOutput: true });
119
+ import { VirtualConsole } from '@steve02081504/virtual-console';
116
120
 
117
- async function middleware(next) {
118
- console.log('Middleware: Activating virtual console for this request.');
119
- // Activate `vc` for the rest of this async flow
120
- vc.hookAsyncContext();
121
-
122
- // Now, `next()` and any subsequent code in this request's
123
- // async chain will have its console output handled by `vc`.
124
- await next();
125
- }
121
+ const reportedErrors = [];
126
122
 
127
- async function handler() {
128
- console.log('Handler: This output is being captured.');
129
- }
123
+ const vc = new VirtualConsole({
124
+ // This handler is called ONLY when a single Error object is passed to console.error
125
+ error_handler: (err) => {
126
+ console.log(`Reporting error: "${err.message}"`);
127
+ reportedErrors.push(err);
128
+ },
129
+ realConsoleOutput: true,
130
+ });
131
+
132
+ await vc.hookAsyncContext(() => {
133
+ console.error(new Error('Something went wrong!')); // Handled by error_handler
134
+ console.error('A regular error message.'); // Not an Error object, logged normally
135
+ });
130
136
 
131
- // Simulate a request
132
- await middleware(handler);
137
+ // Verify the custom handler was called
138
+ console.log('Reported errors:', reportedErrors.map(e => e.message));
133
139
  ```
134
140
 
135
141
  ## API Reference
@@ -141,15 +147,15 @@ Creates a new `VirtualConsole` instance.
141
147
  - `options` `<object>`
142
148
  - `realConsoleOutput` `<boolean>`: If `true`, output is also sent to the base console. **Default:** `false`.
143
149
  - `recordOutput` `<boolean>`: If `true`, output is captured in the `outputs` property. **Default:** `true`.
144
- - `base_console` `<Console>`: The console instance for passthrough. **Default:** The original `global.console`.
145
- - `error_handler` `<function(Error): void>`: A dedicated handler for `Error` objects passed to `console.error`.
146
- - `supportsAnsi` `<boolean>`: Manually set ANSI support. **Default:** Inherited from `base_console`.
150
+ - `base_console` `<Console>`: The console instance for passthrough. **Default:** The original `global.console` before it was patched.
151
+ - `error_handler` `<function(Error): void>`: A dedicated handler for `Error` objects passed to `console.error`. If `console.error` is called with a single argument that is an `instanceof Error`, this handler is invoked instead of the standard `console.error` logic. **Default:** `null`.
152
+ - `supportsAnsi` `<boolean>`: Manually set ANSI support, which affects `freshLine`. **Default:** Inherited from `base_console`'s stream, or the result of the `supports-ansi` package.
147
153
 
148
154
  ### `virtualConsole.hookAsyncContext(fn?)`
149
155
 
150
156
  Hooks the virtual console into an asynchronous context.
151
157
 
152
- - **`hookAsyncContext(fn)`**: Runs `fn` in a new context. All `console` calls within `fn` (and any functions it `await`s) are routed to this instance. Returns the result of `fn`.
158
+ - **`hookAsyncContext(fn)`**: Runs `fn` in a new context. All `console` calls within `fn` (and any functions it `await`s) are routed to this instance. Returns a `Promise` that resolves with the return value of `fn`.
153
159
  - **`hookAsyncContext()`**: Activates the instance for the *current* asynchronous context. Useful for "activating" a console and leaving it active for the remainder of an async flow (e.g., within middleware).
154
160
 
155
161
  ### `virtualConsole.outputs`
@@ -162,7 +168,7 @@ A string containing all captured `stdout` and `stderr` output.
162
168
 
163
169
  *Note: This stateful method is available on the global `console` object but only works as intended inside a `hookAsyncContext`.*
164
170
 
165
- Prints a line. If the previously printed line (within the same hook) had the same `id`, it overwrites the previous line.
171
+ Prints a line. If the previously printed line (within the same hook) had the same `id`, it overwrites the previous line using ANSI escape codes. This requires a TTY environment.
166
172
 
167
173
  - `id` `<string>`: A unique identifier for the overwritable line.
168
174
  - `...args` `<any>`: The content to print, same as `console.log()`.
@@ -171,10 +177,14 @@ Prints a line. If the previously printed line (within the same hook) had the sam
171
177
 
172
178
  Clears the captured `outputs` string. If `realConsoleOutput` is enabled, it also attempts to call `clear()` on the base console.
173
179
 
174
- ### For Library Authors: `setGlobalConsoleReflect(...)`
180
+ ### `virtualConsole.error(...args)`
181
+
182
+ The standard `console.error` method. However, if an `error_handler` is configured in the constructor, and `error()` is called with a single argument that is an `instanceof Error`, the handler will be invoked with the error object.
183
+
184
+ ## For Library Authors: `setGlobalConsoleReflect(...)`
175
185
 
176
- `VirtualConsole` exposes its `AsyncLocalStorage`-based reflection mechanism. If you are building another library that manages async context, you can use `setGlobalConsoleReflect` to integrate `VirtualConsole`'s logic into your own context manager, preventing conflicts. See the source code for details.
186
+ `VirtualConsole` exposes its `AsyncLocalStorage`-based reflection mechanism. If you are building another library that also manages async context (e.g., a custom APM or transaction tracer), you can use `setGlobalConsoleReflect` to integrate `VirtualConsole`'s logic into your own context manager. This prevents the two libraries from fighting over control of the async context. See the source code for details on its function signature.
177
187
 
178
188
  ## Contributing
179
189
 
180
- Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/steve02081504/VirtualConsole).
190
+ Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/steve02081504/virtual-console).
package/main.mjs CHANGED
@@ -156,6 +156,7 @@ export class VirtualConsole extends Console {
156
156
 
157
157
  const originalConsole = globalThis.console
158
158
  export const defaultConsole = new VirtualConsole({ base_console: originalConsole, recordOutput: false, realConsoleOutput: true })
159
+ export const globalConsoleAdditionalProperties = {}
159
160
  /** @type {() => VirtualConsole} */
160
161
  let consoleReflect = () => consoleAsyncStorage.getStore() ?? defaultConsole
161
162
  /** @type {(value: VirtualConsole) => void} */
@@ -181,4 +182,11 @@ export function getGlobalConsoleReflect() {
181
182
  ReflectRun: consoleReflectRun
182
183
  }
183
184
  }
184
- export const console = globalThis.console = new FullProxy(() => consoleReflect())
185
+ export const console = globalThis.console = new FullProxy(() => Object.assign({}, globalConsoleAdditionalProperties, consoleReflect()), {
186
+ set: (target, property, value) => {
187
+ target = consoleReflect()
188
+ if (property in target) return Reflect.set(target, property, value)
189
+ globalConsoleAdditionalProperties[property] = value
190
+ return true
191
+ }
192
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/virtual-console",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "A virtual console for capturing and manipulating terminal output.",
5
5
  "main": "main.mjs",
6
6
  "type": "module",