redblink
v1.0.0
Published
Lightweight Node.js library combining Publisher/Subscriber patterns with Async Local Storage for context-aware applications
Maintainers
Readme
RedBlink
RedBlink is a lightweight Node.js library that combines Publisher/Subscriber patterns with Async Local Storage for building scalable, context-aware applications. It enables flat backend architectures where you can easily import files and test endpoints without complex dependency injection.
Features
- Publisher/Subscriber Pattern: Create named channels for async communication
- Async Local Storage: Share context data across async operations
- Zero Dependencies: Built on native Node.js APIs
- Proxy-based API: Intuitive syntax with dynamic property access
- Event-driven Architecture: Based on Node.js EventEmitter
- TypeScript Ready: Full type support included
Installation
npm install redblinkQuick Start
import RedBlink from 'redblink';
const redblink = new RedBlink();
// Create workers/handlers
redblink.worker('add', ({ a, b }) => {
return a + b * redblink.context.add.multiplier;
});
redblink.api('getUserById', async ({ id }) => {
const user = await database.findUser(id);
return { user, requestId: redblink.context.request.id };
});
// Use with context
async function handleRequest() {
redblink.context.add = { multiplier: 3 };
redblink.context.request = { id: 'req-123' };
const sum = await redblink.worker.add({ a: 1, b: 2 }); // Result: 7
const userData = await redblink.api.getUserById({ id: 42 });
console.log('Sum:', sum);
console.log('User data:', userData);
}
handleRequest();Core Concepts
Publisher/Subscriber
RedBlink creates named channels dynamically. Any property access (except context) becomes a pub/sub channel:
// Define a subscriber (handler)
redblink.notifications('email', async (data) => {
await sendEmail(data.to, data.subject, data.body);
return { sent: true, timestamp: Date.now() };
});
// Publish to the channel
const result = await redblink.notifications.email({
to: '[email protected]',
subject: 'Welcome!',
body: 'Thanks for signing up!'
});
console.log(result); // { sent: true, timestamp: 1234567890 }Async Local Storage
The context property provides isolated storage that persists across async operations:
redblink.database('getUser', async ({ id }) => {
const traceId = redblink.context.trace.id;
console.log(`[${traceId}] Fetching user ${id}`);
const user = await db.users.findById(id);
return user;
});
async function handleUserRequest(userId) {
// Set context for this async flow
redblink.context.trace = { id: `trace-${Date.now()}` };
const user = await redblink.database.getUser({ id: userId });
// The trace ID is automatically available in the handler
}Advanced Usage
Multiple Handlers
You can register multiple handlers for the same channel:
redblink.hooks('beforeSave', (data) => {
console.log('Validation hook');
return data;
});
redblink.hooks('beforeSave', (data) => {
console.log('Audit hook');
return data;
});
// Both handlers will be called
await redblink.hooks.beforeSave({ name: 'John' });Error Handling
RedBlink is built on EventEmitter, so you can listen for errors:
redblink.on('error', (error) => {
console.error('RedBlink error:', error);
});
redblink.worker('failingTask', () => {
throw new Error('Something went wrong');
});
try {
await redblink.worker.failingTask();
} catch (error) {
console.log('Caught error:', error.message);
}Testing
RedBlink makes testing easy by providing a flat, importable structure:
// workers/math.js
import RedBlink from 'redblink';
const redblink = new RedBlink();
redblink.math('add', ({ a, b }) => a + b);
redblink.math('multiply', ({ a, b }) => a * b);
// test/math.test.js
import './workers/math.js';
import RedBlink from 'redblink';
const redblink = new RedBlink();
test('addition works', async () => {
const result = await redblink.math.add({ a: 2, b: 3 });
expect(result).toBe(5);
});
test('multiplication with context', async () => {
redblink.context.multiplier = { factor: 2 };
const result = await redblink.math.multiply({ a: 3, b: 4 });
expect(result).toBe(12);
});API Reference
new RedBlink()
Create a new RedBlink instance.
redblink[channel](topic, handler)
Register a handler for a specific topic on a channel.
channel: String - The channel name (any property except 'context')topic: String - The topic name within the channelhandler: Function - Async or sync function to handle the message
redblink[channel].topic(payload)
Publish a message to a specific topic on a channel.
channel: String - The channel nametopic: String - The topic namepayload: Any - Data to send to the handler
Returns a Promise that resolves with the handler's return value.
redblink.context
Proxy object for accessing async local storage. Set and get context data that persists across async operations.
// Set context
redblink.context.user = { id: 123, role: 'admin' };
redblink.context.request = { traceId: 'abc-123' };
// Get context (available in any async operation)
const userId = redblink.context.user.id;
const traceId = redblink.context.request.traceId;Use Cases
- Microservices Communication: Easy pub/sub between service components
- Request Context: Share request data (user info, trace IDs) across handlers
- Event-driven Architecture: Decouple components with async messaging
- Testing: Simple, flat structure for unit testing backend logic
- Hooks & Middleware: Implement before/after hooks for business logic
Comparison with Other Solutions
| Feature | RedBlink | EventEmitter | Message Queues | |---------|---------|--------------|----------------| | Async Local Storage | ✅ | ❌ | ❌ | | Zero Dependencies | ✅ | ✅ | ❌ | | Promise-based | ✅ | ❌ | Varies | | Dynamic Channels | ✅ | ❌ | ❌ | | Built-in Context | ✅ | ❌ | ❌ |
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
- Yarflam - Initial work
Inspired by the need for simple, context-aware pub/sub patterns in Node.js applications.
