Lightweight Asynchronous Relay Core — The PAN (Page Area Network) messaging bus implementation
LARC Core provides the foundational messaging infrastructure for building loosely-coupled, event-driven web applications. It implements the PAN (Page Area Network) protocol, enabling seamless communication between components, iframes, workers, and tabs.
- 🚀 Zero build required — Drop-in
<pan-bus>element, communicate via CustomEvents - 🔌 Loose coupling — Components depend on topic contracts, not imports
- 🌐 Universal — Works with vanilla JS, Web Components, React, Lit, Vue, iframes
- 📬 Rich messaging — Pub/sub, request/reply, retained messages, cross-tab mirroring
- 🎯 Lightweight — ~12KB minified, no dependencies
- ⚡ Performance — 300k+ messages/second, zero memory leaks
- 🔒 Security — Built-in message validation and sanitization
npm install @larcjs/core<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!-- Load the autoloader -->
<script type="module" src="https://unpkg.com/@larcjs/core/src/pan.js"></script>
</head>
<body>
<!-- The pan-bus is automatically created -->
<script>
// Publish a message
document.dispatchEvent(new CustomEvent('pan:publish', {
detail: {
topic: 'greeting.message',
payload: { text: 'Hello, PAN!' }
}
}));
// Subscribe to messages
document.addEventListener('pan:message', (e) => {
if (e.detail.topic === 'greeting.message') {
console.log('Received:', e.detail.payload.text);
}
});
</script>
</body>
</html>import { PanBus } from '@larcjs/core';
// Create a bus instance
const bus = new PanBus();
// Subscribe to a topic
bus.subscribe('user.login', (message) => {
console.log('User logged in:', message.payload);
});
// Publish a message
bus.publish('user.login', { userId: 123, name: 'Alice' });
// Request/reply pattern
const response = await bus.request('user.get', { id: 123 });
console.log('User data:', response);The central message hub that routes all PAN messages.
<pan-bus id="myBus" mirror="false"></pan-bus>Attributes:
mirror— Enable cross-tab message mirroring (default: false)debug— Enable debug logging (default: false)
Simplifies publishing and subscribing for components.
<pan-client id="client"></pan-client>
<script>
const client = document.getElementById('client');
client.subscribe('data.changed', (msg) => {
console.log('Data updated:', msg.payload);
});
client.publish('data.request', { id: 42 });
</script>// Publisher
bus.publish('notifications.new', {
type: 'info',
message: 'Welcome!'
});
// Subscriber
bus.subscribe('notifications.new', (msg) => {
showNotification(msg.payload);
});// Responder
bus.subscribe('user.get', async (msg) => {
const user = await fetchUser(msg.payload.id);
return { ok: true, user };
});
// Requester
const result = await bus.request('user.get', { id: 123 });
if (result.ok) {
console.log('User:', result.user);
}// Publish with retain flag
bus.publish('app.state', { theme: 'dark' }, { retain: true });
// Late subscribers immediately receive the retained message
bus.subscribe('app.state', (msg) => {
applyTheme(msg.payload.theme);
});LARC uses hierarchical topic naming:
${resource}.list.get— Request list of items${resource}.list.state— Current list state (retained)${resource}.item.select— User selected an item${resource}.item.get— Request single item${resource}.item.save— Save an item${resource}.item.delete— Delete an item${resource}.changed— Item(s) changed notification${resource}.error— Error occurred
Example:
// Request list of products
await bus.request('products.list.get', {});
// Subscribe to product selection
bus.subscribe('products.item.select', (msg) => {
loadProductDetails(msg.payload.id);
});
// Save a product
await bus.request('products.item.save', {
item: { id: 1, name: 'Widget', price: 9.99 }
});Enable the mirror attribute to sync messages across browser tabs:
<pan-bus mirror="true"></pan-bus>Only non-sensitive topics should be mirrored. Use topic filters:
bus.setMirrorFilter((topic) => {
// Don't mirror authentication tokens
return !topic.startsWith('auth.');
});LARC Core is written in pure JavaScript with zero build requirements. TypeScript support is available via the optional @larcjs/core-types package:
npm install @larcjs/core
npm install -D @larcjs/core-typesFull type definitions for all APIs:
import { PanClient } from '@larcjs/core/pan-client.mjs';
import type { PanMessage, SubscribeOptions } from '@larcjs/core-types';
interface UserData {
id: number;
name: string;
}
const client = new PanClient();
// Fully typed publish
client.publish<UserData>({
topic: 'user.updated',
data: { id: 123, name: 'Alice' }
});
// Fully typed subscribe
client.subscribe<UserData>('user.updated', (msg: PanMessage<UserData>) => {
console.log(msg.data.name); // TypeScript knows this is a string!
});Why separate types? We keep runtime code lean (zero dependencies) and let TypeScript users opt-in to types. Best of both worlds!
┌─────────────────────────────────────────────────┐
│ Application │
├─────────────┬─────────────┬─────────────────────┤
│ Component │ Component │ Component │
│ A │ B │ C │
└──────┬──────┴──────┬──────┴──────┬──────────────┘
│ │ │
└─────────────┼─────────────┘
│
┌──────▼──────┐
│ <pan-bus> │ ← Central Message Hub
└──────┬──────┘
│
┌─────────────┼─────────────┐
│ │ │
┌──────▼──────┐ ┌───▼────┐ ┌──────▼──────┐
│ Worker │ │ iframe │ │ Other Tabs │
└─────────────┘ └────────┘ └─────────────┘
- @larcjs/core-types — TypeScript type definitions (opt-in)
- @larcjs/components — UI components built on LARC Core
- @larcjs/devtools — Chrome DevTools for debugging PAN messages
- @larcjs/examples — Demo applications and examples
- ✅ Chrome/Edge 90+
- ✅ Firefox 88+
- ✅ Safari 14+
- ✅ Opera 76+
- Throughput: 300,000+ messages/second
- Latency: <1ms per message (local)
- Memory: Zero leaks, constant memory usage
- Bundle size: ~12KB minified
Contributions are welcome! Please see our Contributing Guide.
MIT © Chris Robison