dc-browser
Browser-compatible polyfill for Node.js's diagnostics_channel API. This package provides the core diagnostics channel functionality that works in browser environments, including integration with AsyncLocalStorage via als-browser.
Features
- Full
diagnostics_channel API compatibility
- Channel publish/subscribe mechanism
- TracingChannel for structured tracing
- Integration with AsyncLocalStorage via
bindStore
- Zero runtime dependencies
- TypeScript support with full type definitions
- ESM and CommonJS builds
- Comprehensive test coverage
Installation
npm install dc-browser
pnpm add dc-browser
yarn add dc-browser
For AsyncLocalStorage integration:
npm install als-browser
Usage
Basic Channel
import { channel } from 'dc-browser';
const requestChannel = channel('http.request');
requestChannel.subscribe((message, name) => {
console.log(`Received on ${name}:`, message);
});
requestChannel.publish({ url: '/api/data', method: 'GET' });
TracingChannel
TracingChannel provides structured tracing with start, end, asyncStart, asyncEnd, and error events:
import { tracingChannel } from 'dc-browser';
const httpChannel = tracingChannel('http.request');
httpChannel.subscribe({
start: (context) => {
console.log('Request started:', context);
},
end: (context) => {
console.log('Request ended:', context);
},
error: (context) => {
console.error('Request error:', context.error);
}
});
const result = httpChannel.traceSync(() => {
return fetchData();
}, { requestId: 'req-123' });
const data = await httpChannel.tracePromise(async () => {
return await fetch('/api/data');
}, { requestId: 'req-456' });
AsyncLocalStorage Integration
Channels can be bound to AsyncLocalStorage instances to transform events into stored context:
import { channel } from 'dc-browser';
import { AsyncLocalStorage } from 'als-browser';
const requestChannel = channel('http.request');
const requestContext = new AsyncLocalStorage();
requestChannel.bindStore(requestContext);
requestChannel.runStores({ requestId: 'req-789' }, () => {
console.log(requestContext.getStore());
doWork();
});
function doWork() {
const context = requestContext.getStore();
console.log('Current request:', context.requestId);
}
Transform Function
You can provide a transform function when binding a store to extract/transform the message:
import { channel } from 'dc-browser';
import { AsyncLocalStorage } from 'als-browser';
const requestChannel = channel('http.request');
const userIdStore = new AsyncLocalStorage<string>();
requestChannel.bindStore(
userIdStore,
(message) => message.userId
);
requestChannel.runStores({ userId: 'user-123', url: '/api/data' }, () => {
console.log(userIdStore.getStore());
});
Multiple Stores
You can bind multiple AsyncLocalStorage instances to a single channel:
const requestChannel = channel('http.request');
const requestIdStore = new AsyncLocalStorage<string>();
const userIdStore = new AsyncLocalStorage<string>();
requestChannel.bindStore(requestIdStore, (msg) => msg.requestId);
requestChannel.bindStore(userIdStore, (msg) => msg.userId);
requestChannel.runStores({ requestId: 'req-123', userId: 'user-456' }, () => {
console.log(requestIdStore.getStore());
console.log(userIdStore.getStore());
});
Unbinding Stores
const store = new AsyncLocalStorage();
const ch = channel('test');
ch.bindStore(store);
ch.unbindStore(store);
API
Module Functions
channel(name: string | symbol): Channel
Get or create a channel by name.
hasSubscribers(name: string | symbol): boolean
Check if a channel has any subscribers.
subscribe(name: string | symbol, callback: Function): void
Subscribe to a channel.
unsubscribe(name: string | symbol, callback: Function): boolean
Unsubscribe from a channel.
tracingChannel(name: string): TracingChannel
Create a TracingChannel for structured tracing.
Channel Class
subscribe(callback: (message: any, name: string) => void): void
Add a subscriber to this channel.
unsubscribe(callback: Function): boolean
Remove a subscriber from this channel.
publish(message: any): void
Publish a message to all subscribers.
bindStore(store: AsyncLocalStorage, transform?: (message: any) => any): void
Bind an AsyncLocalStorage instance to this channel. When runStores is called, the message (optionally transformed) will be set as the store value.
unbindStore(store: AsyncLocalStorage): boolean
Unbind an AsyncLocalStorage instance from this channel.
runStores(context: any, fn: () => any): any
Publish the context and run the function within all bound AsyncLocalStorage contexts.
TracingChannel Class
A TracingChannel manages 5 individual channels:
start: Published before operation begins
end: Published after operation completes
asyncStart: Published when async operation starts resolving
asyncEnd: Published when async operation finishes resolving
error: Published when operation throws/rejects
subscribe(handlers: ChannelHandlers): void
Subscribe to tracing events.
unsubscribe(handlers: ChannelHandlers): boolean
Unsubscribe from tracing events.
traceSync<T>(fn: Function, context?: any, thisArg?: any, ...args: any[]): T
Trace a synchronous operation.
tracePromise<T>(fn: Function, context?: any, thisArg?: any, ...args: any[]): Promise<T>
Trace a promise-based operation.
traceCallback<T>(fn: Function, position?: number, context?: any, thisArg?: any, ...args: any[]): any
Trace a callback-based operation.
Integration with als-browser
When using bindStore with als-browser, the context will be preserved through:
- Synchronous code: Full context propagation within the
runStores callback
- Patched async APIs: setTimeout, setInterval, requestAnimationFrame (via als-browser auto-patches)
- Manual propagation: Use
AsyncLocalStorage.bind() or snapshot() for other async operations
Note: Native Promise await boundaries will lose context unless you:
- Use the patched timer APIs (setTimeout, etc.)
- Manually bind callbacks with
AsyncLocalStorage.bind()
- Use the capture/restore functions from
als-browser
Example: Request Tracing with Context
import { tracingChannel } from 'dc-browser';
import { AsyncLocalStorage } from 'als-browser';
const httpChannel = tracingChannel('http.request');
const requestStore = new AsyncLocalStorage();
httpChannel.start.bindStore(requestStore);
httpChannel.subscribe({
start: (ctx) => console.log('Started:', ctx.requestId),
end: (ctx) => console.log('Ended:', ctx.requestId, 'result:', ctx.result),
error: (ctx) => console.error('Error:', ctx.requestId, ctx.error)
});
const response = httpChannel.traceSync(() => {
console.log('Current request:', requestStore.getStore()?.requestId);
return fetch('/api/data');
}, { requestId: 'req-123', url: '/api/data' });
Testing
pnpm test
pnpm build
License
MIT
Credits
This implementation is based on Node.js's diagnostics_channel API from Node.js core.