@adalink/spark-echo
v1.0.0
Published
Echo reactive web components library
Maintainers
Readme
⚡ Spark Echo Library
Echo reactive web components library. Zero dependencies, reactive event-driven architecture.
📖 What is Echo?
Echo is a modern, lightweight library that provides a reactive event arc system for Web Components. Built on native Web Components APIs, it enables components to communicate through a declarative event routing system with powerful data transformation capabilities.
🎯 Why Choose Echo?
- Zero Dependencies - No runtime dependencies, just pure Web Platform APIs
- Declarative Event Arcs - Describe component communication via HTML attributes
- Reactive by Default - Components automatically respond to events
- Framework Agnostic - Works with any framework or vanilla JavaScript
- Performance First - Optimized for speed with minimal bundle size (~2KB)
- Production Ready - Battle-tested in real-world applications
🚀 Perfect For
- Web Components Projects - Add reactive communication to native Web Components
- Event-Driven Architecture - Build applications with pub/sub patterns
- Performance-Critical Apps - Minimal overhead, maximum speed
- Micro-Frontends - Isolated components with clean event interfaces
- Real-time Updates - Reactive data flows between components
✨ Key Features
🎯 Declarative Event Arcs
Describe component communication declaratively using HTML attributes:
// Traditional approach
class MyComponent extends HTMLElement {
connectedCallback() {
document.addEventListener('custom-event', this.handleEvent.bind(this));
}
disconnectedCallback() {
document.removeEventListener('custom-event', this.handleEvent.bind(this));
}
handleEvent(event) {
if (event.detail.id === 'some-component') {
this.value = event.detail.data;
}
}
}
// Echo approach
class MyComponent extends Echo(HTMLElement) {}
// In HTML:
<my-component on="*custom-event:attribute/value"></my-component>📦 Simple API
Only import what you need:
import Echo, { filter } from '@adalink/spark-echo';
// Create reactive component
class MyComponent extends Echo(HTMLElement) {}⚡ High Performance
- Event delegation (single listener per component)
- Automatic cleanup (no memory leaks)
- Lazy event registration
- No framework overhead
🚀 Quick Start
Installation
# Using npm
npm install @adalink/spark-echo
# Using yarn
yarn add @adalink/spark-echo
# Using pnpm
pnpm add @adalink/spark-echo
# Using bun
bun add @adalink/spark-echoYour First Reactive Component
Create a reactive counter with automatic event propagation:
import Echo, { filter } from '@adalink/spark-echo';
class Counter extends Echo(HTMLElement) {
#count = 0;
static observedAttributes = ['count'];
attributeChangedCallback(name, oldValue, newValue) {
if (name === 'count') {
this.#count = parseInt(newValue, 10);
this.updateDisplay();
}
}
connectedCallback() {
this.updateDisplay();
}
updateDisplay() {
this.innerHTML = `
<div class="counter">
<h2>Count: ${this.#count}</h2>
<button class="increment">+1</button>
<button class="reset">Reset</button>
</div>
`;
this.querySelector('.increment').addEventListener('click', () => {
this.#count++;
this.dispatchEvent(new CustomEvent('change', {
detail: this.#count,
bubbles: true,
composed: true
}));
});
this.querySelector('.reset').addEventListener('click', () => {
this.#count = 0;
this.dispatchEvent(new CustomEvent('change', {
detail: this.#count,
bubbles: true,
composed: true
}));
});
}
}
customElements.define('my-counter', Counter);Use it in your HTML:
<my-counter id="counter1" count="0"></my-counter>
<!-- Another component that listens to counter changes -->
<my-display on="#counter1/change:method/handleUpdate"></my-display>📦 Event Arc Syntax
The power of Echo comes from its declarative event arc syntax:
Arc Format
source/event:type/sink|filter1|filter2Components
source: Where the event comes from
*- Any component#id- Specific component by IDname- Component by name attributenode- Component by node name (tag)
event: Event type to listen for
click,change,input,custom-event, etc.
type: How to apply the result
method- Call a method on this componentattribute- Set an HTML attributesetter- Set a property
sink: Where to apply the result
- Method name, attribute name, or property name
filters: Data transformations (optional, multiple with
|)filtername=value- Apply filter with parameter
Examples
<!-- Listen to any component's click events and call handleClick -->
<my-component on="*click:method/handleClick"></my-component>
<!-- Listen to specific component's change events and set value attribute -->
<my-display on="#counter/change:attribute/value"></my-display>
<!-- Listen to component by name and set property -->
<my-log on="logger/input:setter/message"></my-log>
<!-- Multiple filters: add 10 then convert to string -->
<my-component on="*data:method/handleData|add=10|toString"></my-component>🎯 Available Filters
Echo includes powerful data transformation filters:
Basic Filters
import filter from '@adalink/spark-echo/filter';- truthy - Filter truthy values
- always - Always return the value
- equals - Compare with value
- different - Compare for difference
Math Filters
- add - Add value:
|add=10 - subtract - Subtract value:
|subtract=5 - inc - Increment by 1:
|inc - dec - Decrement by 1:
|dec
Comparison Filters
- gt - Greater than:
|gt=10 - gte - Greater than or equal:
|gte=10 - lt - Less than:
|lt=10 - lte - Less than or equal:
|lte=10
Property Filters
- prop - Get nested property:
|prop=user.name - len - Get length:
|len
Register Custom Filters
import filter from '@adalink/spark-echo/filter';
filter.set('uppercase', (data) => data.toUpperCase());
filter.set('reverse', (data) => data.split('').reverse().join(''));🎯 Real-World Use Cases
Form Validation
<form>
<input type="text" id="username" />
<input type="password" id="password" />
<my-button on="*change:method/checkValidity"></my-button>
<my-error on="*invalid:method/showError"></my-error>
</form>class FormValidator extends Echo(HTMLElement) {
checkValidity({ detail }) {
const isValid = this.validate(detail);
if (!isValid) {
this.dispatchEvent(new CustomEvent('invalid', {
detail: { field: detail.field, error: 'Invalid input' }
}));
}
}
}Data Synchronization
<my-list id="items" data='[1,2,3]'></my-list>
<my-chart on="#items/change:method/update|prop=length"></my-chart>
<my-counter on="#items/change:method/setCount|prop=length"></my-counter>Parent-Child Communication
<parent-component>
<child-component on="parent/action:method/childAction"></child-component>
</parent-component>🎯 Advanced Usage
Multiple Event Arcs
<my-component
on="*click:method/handleClick|add=1|toString"
on="*change:attribute/value|prop=data.newValue"
on="*error:method/handleError">
</my-component>Filter Chains
<!-- Transform data through multiple filters -->
<my-component on="*data:method/process|add=10|multiply=2|toFixed=2|toString"></my-component>Cross-Component Communication
<!-- Component A broadcasts events -->
<my-broadcaster id="broadcaster"></my-broadcaster>
<!-- Component B listens to A -->
<my-listener on="#broadcaster/message:method/handleMessage|prop=content"></my-listener>
<!-- Component C also listens to A -->
<my-logger on="#broadcaster/message:method/logMessage|len|toString"></my-logger>📊 Why Echo Over Alternatives?
| Feature | Echo | Event Bus | Redux | Signals | |---------|------|-----------|-------|---------| | Zero Dependencies | ✅ | ❌ | ❌ | ⚠️ | | Native Web Components | ✅ | ✅ | ⚠️ | ✅ | | Declarative Syntax | ✅ | ❌ | ❌ | ⚠️ | | Built-in Filters | ✅ | ❌ | ❌ | ❌ | | Automatic Cleanup | ✅ | ⚠️ | ✅ | ✅ | | Bundle Size | ~2KB | ~5KB | ~15KB | ~8KB | | Framework Agnostic | ✅ | ✅ | ⚠️ | ✅ |
🌐 Usage in Frameworks
With React
import Echo from '@adalink/spark-echo';
class ReactBridge extends Echo(HTMLElement) {
connectedCallback() {
this.addEventListener('react-click', (e) => {
// Call React component method
this._reactHandler?.(e.detail);
});
}
}
customElements.define('react-bridge', ReactBridge);
// In React
function App() {
const handleClick = (data) => console.log(data);
return <react-bridge ref={(el) => el._reactHandler = handleClick} />;
}With Vue
import Echo from '@adalink/spark-echo';
class VueBridge extends Echo(HTMLElement) {
connectedCallback() {
this.addEventListener('vue-event', (e) => {
// Emit to Vue component
this._vueComponent?.$emit('vue-event', e.detail);
});
}
}
customElements.define('vue-bridge', VueBridge);🛠️ Development
Prerequisites
- Node.js 18+
Setup
# Clone repository
git clone https://github.com/Adalink-ai/spark_echo.git
cd spark_echo
# Install dependencies
npm install
# Build package
npm run build
# Start development server
npm run dev
# Lint and format
npm run lint📚 Documentation
- Architecture: ARCHITECTURE.md - Design decisions and patterns
- Contributing: CONTRIBUTING.md - Development guidelines
- Security: SECURITY.md - Security policies
- Changelog: CHANGELOG.md - Project changes
- Authors: AUTHORS.md - Author information
🤝 Contributing
We welcome contributions! Please read our Contributing Guide before getting started.
Ways to contribute:
👥 Author & Community
Cleber de Moraes Goncalves - Creator & Lead Maintainer
- 📧 Email: [email protected]
- 🐙 GitHub: deMGoncalves
- 💼 LinkedIn: deMGoncalves
- 📸 Instagram: deMGoncalves
🌟 Star the Project
If you find Echo useful, please ⭐ star it on GitHub!
📢 Share
Share Echo with your network:
📄 License
Apache-2.0 © 2026 Adalink
🔗 Links
- Repository: github.com/Adalink-ai/spark_echo
- NPM Package: npmjs.com/package/@adalink/spark-echo
- Organization: github.com/Adalink-ai
Built with ❤️ by Adalink
Spark Echo Library - Build reactive web components with event arcs. ⚡
