Message Topics Reference
This appendix provides a comprehensive reference for LARC message topic conventions. Topics are the fundamental addressing mechanism in LARC applications—they determine how messages are routed, who receives them, and how components interact. Understanding topic patterns is essential for building scalable, maintainable LARC applications.
Topic Format and Structure
Standard Format
LARC topics follow a hierarchical dotted notation:
resource.action.qualifier
Components:
- Resource: The domain entity or system (users, posts, nav, ui, auth)
- Action: The operation or event type (list, item, get, save, updated)
- Qualifier: Additional context (state, request, reply, event)
Topic Examples
| Topic | Resource | Action | Qualifier | Purpose |
|-------|----------|--------|-----------|---------|
| users.list.state | users | list | state | Current user list (retained) |
| users.item.get | users | item | get | Fetch single user (request) |
| posts.item.updated | posts | item | updated | Post was modified (event) |
| nav.goto | nav | goto | — | Navigate to route (command) |
| ui.modal.opened | ui | modal.opened | — | Modal opened (event) |
| auth.session.state | auth | session | state | Current session (retained) |
Naming Rules
Case and Separators:- Use lowercase letters only
- Separate segments with dots (
.) - Use hyphens (
-) within a segment if needed:auth.two-factor.verify
- Alphanumeric characters:
a-z,0-9 - Dots for segment separation:
. - Hyphens for compound words:
- - No underscores, spaces, or special characters
- Keep topics concise but descriptive
- Typical range: 15-40 characters
- Avoid abbreviations that sacrifice clarity
Topic Patterns and Matching
Exact Match
The simplest pattern matches a specific topic exactly:
client.subscribe('users.item.updated', (msg) => {
console.log('User updated:', msg.data);
});
Receives only messages published to users.item.updated.
Single-Segment Wildcard
The asterisk (*) matches exactly one segment:
| Pattern | Matches | Does Not Match |
|---------|---------|----------------|
| users.* | users.list, users.item | users.list.state, users.item.updated |
| *.updated | users.updated, posts.updated | users.item.updated |
| users.*.state | users.list.state, users.item.state | users.state, users.list.item.state |
// Subscribe to all user-related events
client.subscribe('users.*', (msg) => {
console.log('User event:', msg.topic);
});
Global Wildcard
The special pattern * matches all topics:
// Monitor all messages (use sparingly)
client.subscribe('*', (msg) => {
console.log('[ALL]', msg.topic, msg.data);
});
Warning: Global wildcard subscriptions match every message in the system. Use them only for debugging, logging, or analytics. They can significantly impact performance in high-throughput applications.
Pattern Matching Examples
// Match all list operations
client.subscribe('*.list.*', (msg) => {
// Matches: users.list.state, posts.list.get, comments.list.get
});
// Match all state topics
client.subscribe('*.*.state', (msg) => {
// Matches: users.list.state, app.theme.state, nav.route.state
});
// Match all operations on items
client.subscribe('*.item.*', (msg) => {
// Matches: users.item.get, posts.item.save, users.item.updated
});
Reserved Topic Namespaces
Certain topic namespaces are reserved for LARC internal use. Applications must not publish to these topics directly.
pan:* Namespace (System Internal)
The pan:* namespace is reserved for PAN bus internals:
| Topic | Purpose | Usage |
|-------|---------|-------|
| pan:sys.ready | Bus ready signal | Listen only |
| pan:sys.stats | Bus statistics | Request only |
| pan:sys.error | System errors | Listen only |
| pan:sys.clear-retained | Clear retained messages | Request only |
| pan:publish | Internal publish event | Internal only |
| pan:subscribe | Internal subscribe event | Internal only |
| pan:unsubscribe | Internal unsubscribe event | Internal only |
| pan:deliver | Internal message delivery | Internal only |
| pan:hello | Client registration | Internal only |
| pan:$reply:* | Auto-generated reply topics | Internal only |
- Publish to
pan:*topics directly - Subscribe to internal topics like
pan:publishorpan:subscribe - Manually create
pan:$reply:*topics (these are auto-generated byrequest())
pan:sys.ready and pan:sys.error.
sys:* Namespace (System Reserved)
The sys:* namespace is reserved for future system-level functionality:
sys:error # Future: System-wide errors
sys:perf # Future: Performance monitoring
sys:debug # Future: Debug information
sys:config # Future: Configuration changes
Do not use sys:* topics in application code.
Application Namespaces
Your application should establish its own top-level namespaces:
| Namespace | Purpose | Examples |
|-----------|---------|----------|
| app.* | Application-level concerns | app.config.state, app.theme.state |
| auth.* | Authentication/authorization | auth.login, auth.session.state |
| session.* | Session management | session.started, session.expired |
| nav.* | Navigation | nav.goto, nav.route.state |
| ui.* | UI components | ui.modal.opened, ui.toast.show |
| analytics.* | Analytics tracking | analytics.event, analytics.page-view |
CRUD Topic Patterns
CRUD operations (Create, Read, Update, Delete) follow consistent topic patterns across all resources.
List Operations
#### List State (Retained)
Topic:${resource}.list.state
Purpose: Retained snapshot of current list data. New subscribers receive the most recent list immediately.
Message Format:
{
topic: 'users.list.state',
data: {
items: [/* array of items */],
total: 150, // Total count (for pagination)
page: 1, // Current page
filter: {}, // Active filters
sort: 'name-asc' // Active sort
},
retain: true
}
Usage:
// Publish list state
client.publish({
topic: 'users.list.state',
data: {
items: users,
total: users.length,
page: 1
},
retain: true
});
// Subscribe to list state
client.subscribe('users.list.state', (msg) => {
renderList(msg.data.items);
}, { retained: true });
#### List Get (Request)
Topic:${resource}.list.get
Purpose: Request to fetch list data with optional parameters.
Request Format:
{
topic: 'users.list.get',
data: {
page: 1,
limit: 20,
filter: { active: true },
sort: 'name-asc'
}
}
Response Format:
{
ok: true,
items: [/* array */],
total: 150,
page: 1
}
Usage:
// Request list
const response = await client.request('users.list.get', {
page: 1,
limit: 20,
filter: { active: true }
});
if (response.data.ok) {
renderList(response.data.items);
}
Item Operations
#### Item Get (Request)
Topic:${resource}.item.get
Purpose: Fetch a single item by ID.
Request Format:
{
topic: 'users.item.get',
data: { id: 123 }
}
Response Format:
// Success
{ ok: true, item: { id: 123, name: 'Alice', email: '...' } }
// Not found
{ ok: false, error: 'Not found', code: 'NOT_FOUND' }
#### Item Save (Request)
Topic:${resource}.item.save
Purpose: Create or update an item. If id is present, updates existing item. If id is omitted, creates new item.
Request Format:
{
topic: 'users.item.save',
data: {
item: {
id: 123, // Omit for create
name: 'Alice',
email: 'alice@example.com'
}
}
}
Response Format:
// Success
{ ok: true, item: { id: 123, name: 'Alice', email: '...' } }
// Validation error
{ ok: false, error: 'Invalid email', code: 'VALIDATION_ERROR' }
#### Item Delete (Request)
Topic:${resource}.item.delete
Purpose: Delete an item by ID.
Request Format:
{
topic: 'users.item.delete',
data: { id: 123 }
}
Response Format:
// Success
{ ok: true, id: 123 }
// Not found
{ ok: false, error: 'Not found', code: 'NOT_FOUND' }
#### Item Select (Event)
Topic:${resource}.item.select
Purpose: User selected/focused an item (no reply expected).
Message Format:
{
topic: 'users.item.select',
data: { id: 123 }
}
Usage:
// Publish selection
client.publish({
topic: 'users.item.select',
data: { id: userId }
});
// Handle selection
client.subscribe('users.item.select', (msg) => {
highlightItem(msg.data.id);
loadDetails(msg.data.id);
});
Item Events
Item events notify about completed operations:
| Topic | Trigger | Data |
|-------|---------|------|
| \${resource}.item.created | Item created | { item: {...} } |
| \${resource}.item.updated | Item updated | { item: {...} } |
| \${resource}.item.deleted | Item deleted | { id: 123 } |
// After save operation completes
client.publish({
topic: 'users.item.updated',
data: { item: savedUser }
});
Per-Item State
For tracking state of individual items:
Topic:${resource}.item.state.${id}
Purpose: Retained state for a specific item (e.g., online status, typing indicator).
Example:
// Publish item state
client.publish({
topic: `users.item.state.${userId}`,
data: {
id: userId,
online: true,
typing: false,
lastSeen: Date.now()
},
retain: true
});
// Subscribe to specific item
client.subscribe(`users.item.state.${userId}`, (msg) => {
updatePresence(msg.data);
}, { retained: true });
// Subscribe to all item states
client.subscribe('users.item.state.*', (msg) => {
updatePresence(msg.data);
});
State Management Topics
State topics use the .state qualifier and are always retained.
Global State
Pattern:${domain}.state
Examples:
'app.config.state' # Application configuration
'app.theme.state' # Current theme
'app.language.state' # Current language
'ui.sidebar.state' # Sidebar open/closed
'ui.loading.state' # Loading indicator
Usage:
// Publish state
client.publish({
topic: 'app.theme.state',
data: { mode: 'dark', accent: '#007bff' },
retain: true
});
// Subscribe (receives current state immediately)
client.subscribe('app.theme.state', (msg) => {
applyTheme(msg.data);
}, { retained: true });
Scoped State
Pattern:${domain}.${scope}.state
Examples:
'users.list.state' # User list
'auth.session.state' # Current session
'nav.route.state' # Current route
'search.query.state' # Search query
'filters.active.state' # Active filters
Events vs Commands
Distinguish between events (past tense) and commands (imperative).
Events
Events describe something that already happened. They use past tense.
Characteristics:- Past tense verbs:
created,updated,deleted,opened,closed - Fire-and-forget (no reply expected)
- Multiple subscribers allowed
- Informational
| Topic | Description |
|-------|-------------|
| users.item.created | A user was created |
| users.item.updated | A user was updated |
| ui.modal.opened | A modal was opened |
| ui.modal.closed | A modal was closed |
| session.started | Session started |
| session.expired | Session expired |
| auth.login.success | Login succeeded |
| auth.login.failed | Login failed |
| nav.navigated | Navigation completed |
// Publish event
client.publish({
topic: 'users.item.created',
data: { item: newUser }
});
// Multiple handlers can react
client.subscribe('users.item.created', logAnalytics);
client.subscribe('users.item.created', sendWelcomeEmail);
client.subscribe('users.item.created', updateDashboard);
Commands
Commands request something to happen. They use imperative/verb form.
Characteristics:- Imperative verbs:
save,delete,open,close,goto - May expect reply (request/reply pattern)
- Usually single handler
- May fail
| Topic | Description |
|-------|-------------|
| users.item.save | Save a user |
| users.item.delete | Delete a user |
| ui.modal.open | Open a modal |
| ui.modal.close | Close a modal |
| nav.goto | Navigate to route |
| nav.back | Go back in history |
| auth.login | Perform login |
| auth.logout | Perform logout |
// Fire-and-forget command
client.publish({
topic: 'nav.goto',
data: { route: '/users/123' }
});
// Request command (expect reply)
const response = await client.request('users.item.save', {
item: { name: 'Alice' }
});
Domain-Specific Patterns
Authentication
// Commands
'auth.login' # Login request
'auth.logout' # Logout request
'auth.refresh' # Refresh token
'auth.verify' # Verify credentials
// Events
'auth.login.success' # Login succeeded
'auth.login.failed' # Login failed
'auth.logout' # User logged out
'auth.token.expired' # Token expired
// State
'auth.session.state' # Current session (retained)
'auth.user.state' # Current user info (retained)
Navigation
// Commands
'nav.goto' # Navigate to route
'nav.back' # Go back
'nav.forward' # Go forward
'nav.replace' # Replace current route
// Events
'nav.navigated' # Navigation completed
'nav.error' # Navigation error
// State
'nav.route.state' # Current route (retained)
'nav.history.state' # History stack (retained)
UI Components
// Modal
'ui.modal.open' # Command: open modal
'ui.modal.close' # Command: close modal
'ui.modal.opened' # Event: modal opened
'ui.modal.closed' # Event: modal closed
'ui.modal.state' # State: current modal (retained)
// Sidebar
'ui.sidebar.toggle' # Command: toggle sidebar
'ui.sidebar.open' # Command: open sidebar
'ui.sidebar.close' # Command: close sidebar
'ui.sidebar.state' # State: open/closed (retained)
// Toast
'ui.toast.show' # Command: show toast
'ui.toast.hide' # Command: hide toast
// Loading
'ui.loading.start' # Command: start loading
'ui.loading.stop' # Command: stop loading
'ui.loading.state' # State: loading status (retained)
Forms
// Validation
'form.validate' # Command: validate form
'form.validated' # Event: validation complete
'form.validation.state' # State: validation errors (retained)
// Submission
'form.submit' # Command: submit form
'form.submitted' # Event: form submitted
'form.submit.success' # Event: submission succeeded
'form.submit.failed' # Event: submission failed
// Field changes
'form.field.changed' # Event: field value changed
'form.field.focused' # Event: field focused
'form.field.blurred' # Event: field blurred
Data Synchronization
// Sync commands
'sync.start' # Start sync
'sync.stop' # Stop sync
'sync.refresh' # Force refresh
// Sync events
'sync.started' # Sync started
'sync.completed' # Sync completed
'sync.failed' # Sync failed
'sync.conflict' # Sync conflict detected
// Sync state
'sync.status.state' # Current sync status (retained)
'sync.last-update.state' # Last update time (retained)
Best Practices
Topic Naming
DO:- Use lowercase letters
- Use dots to separate segments
- Use descriptive names
- Be consistent across resources
- Use
.statefor retained topics - Use past tense for events
- Use imperative for commands
- Use underscores or camelCase
- Use abbreviations that sacrifice clarity
- Mix naming conventions
- Use verbs for events (
users.update[X] ->users.updated[check]) - Overuse wildcards (
*matches everything)
Topic Catalog
For larger applications, maintain a centralized topic catalog:
// topics.js
export const TOPICS = {
USERS: {
LIST: {
STATE: 'users.list.state',
GET: 'users.list.get'
},
ITEM: {
GET: 'users.item.get',
SAVE: 'users.item.save',
DELETE: 'users.item.delete',
SELECT: 'users.item.select',
UPDATED: 'users.item.updated',
DELETED: 'users.item.deleted',
STATE: (id) => `users.item.state.${id}`
}
},
NAV: {
GOTO: 'nav.goto',
BACK: 'nav.back',
ROUTE_STATE: 'nav.route.state'
},
AUTH: {
LOGIN: 'auth.login',
LOGOUT: 'auth.logout',
SESSION_STATE: 'auth.session.state'
}
};
// Usage
client.publish({
topic: TOPICS.USERS.ITEM.UPDATED,
data: { item: user }
});
Performance Considerations
Wildcard Usage:- Avoid global wildcard (
*) in production code - Prefer specific patterns (
users.over) - Each wildcard increases matching overhead
- Keep topics shallow (3-4 segments ideal)
- Deeper hierarchies increase matching cost
- Balance specificity with performance
- Use retention sparingly (only for actual state)
- Don't retain high-volume event streams
- Clear retained messages when no longer needed
Summary
Key Principles:resource.action.qualifier.state for retained topicspan: or sys:| Pattern | Example | Use Case |
|---------|---------|----------|
| \${resource}.list.state | users.list.state | List data (retained) |
| \${resource}.list.get | users.list.get | Request list |
| \${resource}.item.get | users.item.get | Request single item |
| \${resource}.item.save | users.item.save | Save item |
| \${resource}.item.delete | users.item.delete | Delete item |
| \${resource}.item.updated | users.item.updated | Item updated event |
| \${domain}.state | app.theme.state | Global state (retained) |
| \${domain}.\${action} | nav.goto | Command |
For complete API documentation, see the main API Reference.