@adalink/spark-middleware
v1.0.0
Published
Method decorators for cross-cutting concerns (AOP)
Downloads
56
Maintainers
Readme
🔄 Spark Middleware
Method decorators for cross-cutting concerns. Zero dependencies, tree-shakeable.
📖 What is Spark Middleware?
Spark Middleware is a lightweight library that implements Aspect-Oriented Programming (AOP) using JavaScript decorators. It provides powerful interceptors for methods and setters, enabling clean separation of cross-cutting concerns like validation, logging, authentication, and data transformation.
🎯 Why Choose Spark Middleware?
- Zero Dependencies - No runtime dependencies, just pure JavaScript
- AOP Pattern - Separate cross-cutting concerns from business logic
- Composable - Stack multiple decorators together
- Framework Agnostic - Works with any framework or vanilla JavaScript
- Performance First - Minimal bundle size (~70B gzipped per decorator)
- Developer Experience - Clean, declarative API
- Production Ready - Battle-tested in real-world applications
🚀 Perfect For
- Validation - Transform and validate method arguments
- Logging - Log method calls without cluttering business logic
- Authentication - Inject authentication checks
- Data Transformation - Transform return values automatically
- Telemetry - Track method execution
- Security - Enforce security policies
✨ Key Features
🎯 Three Decorator Types
Execute code exactly where you need it:
import { before, around, after } from '@adalink/spark-middleware';
// @before: Transform arguments before method
@before(validate)
setValue(value) {
// value is already validated
}
// @around: Run code after method (async)
@around(log)
processData() {
// Method runs, then log fires
}
// @after: Transform return value
@after(format)
getData() {
// Result is formatted automatically
}🔄 Stacking Decorators
Combine multiple interceptors:
class Service {
@before(authenticate)
@around(log)
@after(sanitize)
processData(input) {
// Authenticate → Log → method → Sanitize
return { result: input };
}
}⚡ Zero Dependencies
Just pure JavaScript Proxy API:
# Bundle size per decorator
before: ~70B gzipped
around: ~70B gzipped
after: ~70B gzipped🔗 Integração com Spark Ecosystem
O Spark Middleware é parte do Spark Ecosystem, trabalhando em harmonia com outras bibliotecas Spark para criar aplicações web completas.
📦 Pacotes Relacionados
@adalink/spark-std - Biblioteca padrão com decorators para Web Components
Use Spark Middleware com componentes criados com Spark Std:
import { define, connected } from '@adalink/spark-std';
import { paint, html, css } from '@adalink/spark-std/dom';
import { event } from '@adalink/spark-std/event';
import { paint as paintDom, html as htmlDom, css as cssDom } from '@adalink/spark-std/dom';
import { before, around, after } from '@adalink/spark-middleware';
import http from '@adalink/spark-http';
import cookie from '@adalink/spark-cookie';
@define('spark-data-table')
@paintDom(template, styles)
class DataTable extends HTMLElement {
#data = [];
#loading = false;
@before(checkAuth)
@around(logFetch)
@after(transformData)
async fetchData() {
const token = cookie.getItem('access_token');
const { data } = await http
.GET(this.getAttribute('data-url'))
.headers({ Authorization: `Bearer ${token}` })
.json();
return data;
}
checkAuth(args) {
if (!cookie.getItem('access_token')) {
throw new Error('Unauthorized');
}
return args;
}
logFetch(args) {
console.log('Fetching data:', args);
}
transformData(data) {
return {
items: Array.isArray(data) ? data : [data],
total: Array.isArray(data) ? data.length : 1
};
}
@event.click('button.refresh')
refresh() {
this.fetchData().then(data => {
this.#data = data;
this.render();
});
}
render() {
if (this.#loading) {
this.shadowRoot.innerHTML = htmlDom`<div class="loading">Loading...</div>`;
} else {
this.shadowRoot.innerHTML = htmlDom`
<div class="data">
${this.#data.map(item => htmlDom`
<div class="row">${JSON.stringify(item)}</div>
`)}
</div>
<button class="refresh">Refresh</button>
`;
}
}
}
function template(component) {
return htmlDom`<div id="content"></div>`;
}
function styles(component) {
return cssDom`
.loading, .data {
padding: 1rem;
text-align: center;
}
.row {
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
button {
margin-top: 1rem;
padding: 0.5rem 1rem;
background: #667eea;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
`;
}Funcionalidades Combinadas:
- ✅ Spark Std fornece decorators base (
@define,@paint,@event) - ✅ Spark Middleware fornece interceptores de método
- ✅ Spark HTTP fornece requests HTTP fluentes
- ✅ Spark Cookie fornece gerenciamento de cookies (opcional)
- ✅ Use decorators para compor comportamento complexo
- ✅ Todos funcionam com Web Components nativos
🚀 Quick Start
Installation
Option 1: Install from npm (Recommended)
# Using npm
npm install @adalink/spark-middleware
# Using yarn
yarn add @adalink/spark-middleware
# Using pnpm
pnpm add @adalink/spark-middleware
# Using bun
bun add @adalink/spark-middlewareOption 2: Install from GitHub (Alternative)
# Install directly from GitHub repository
npm install github:Adalink-ai/spark_middleware#v1.0.0Option 3: Import from CDN (Browser Only)
// Import modules from unpkg or jsDelivr (ESM)
import { before, around, after } from "https://unpkg.com/@adalink/[email protected]/dist/middleware.js";⚠️ Important Notice: This package is private and published to npm. To install it:
- Have an npm account
- Request access to the
@adalinkscope from the maintainers - Configure npm authentication in your environment
Basic Usage
@before - Transform Arguments
Execute code BEFORE method call:
import { before } from '@adalink/spark-middleware';
class FormHandler {
@before(validate)
submit(data) {
// data is already validated and transformed
console.log(' submitting:', data);
api.send(data);
}
validate(data) {
if (!data.name) throw new Error('Name required');
if (!data.email?.includes('@')) throw new Error('Invalid email');
// Transform data before returning
return {
...data,
timestamp: Date.now(),
validated: true
};
}
}
const form = new FormHandler();
form.submit({ name: 'John', email: '[email protected]' });
// Runs validate() → submit(validated_data)@around - Run After (Async)
Execute code AFTER method call asynchronously:
import { around } from '@adalink/spark-middleware';
class APIService {
@around(logRequest)
async fetchUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
logRequest(args) {
// Called asynchronously after fetchUser completes
console.log('API request made with args:', args);
metrics.increment('api.calls');
}
}
const api = new APIService();
api.fetchUser(123);
// Runs fetchUser() → logRequest (async)@after - Transform Return Value
Execute code WITH the return value:
import { after } from '@adalink/spark-middleware';
class DataService {
@after(transformToDTO)
async getProduct(id) {
const response = await fetch(`https://api.example.com/products/${id}`);
const product = await response.json();
return product;
}
transformToDTO(product) {
// Transform the return value
return {
id: product.id,
name: product.name,
price: `$${product.price.toFixed(2)}`,
formatted: true
};
}
}
const service = new DataService();
const result = await service.getProduct(123);
console.log(result);
// { id: 123, name: "...", price: "$99.99", formatted: true }Stacking Multiple Decorators
Combine multiple interceptors:
import { before, around, after } from '@adalink/spark-middleware';
class SecureService {
@before(authenticate)
@around(logOperation)
@after(sanitizeResponse)
async updateData(data) {
// Execution order:
// 1. authenticate() transforms data
// 2. updateData() runs and returns result
// 3. logOperation() runs asynchronously
// 4. sanitizeResponse() transforms result
const response = await fetch('https://api.example.com/data', {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
authenticate(data) {
if (!data.token) throw new Error('Unauthorized');
return { ...data, authenticated: true };
}
logOperation(args) {
console.log('Operation:', args);
metrics.track('secure.update', args);
}
sanitizeResponse(data) {
// Remove sensitive data
const { password, token, ...sanitized } = data;
return sanitized;
}
}Works with Setters Too
Decorators work with setters:
import { before, after } from '@adalink/spark-middleware';
class SmartComponent {
#internalValue;
@before(validateInput)
@after(notifyChange)
set value(newValue) {
this.#internalValue = newValue;
}
get value() {
return this.#internalValue;
}
validateInput(value) {
if (typeof value !== 'string') throw new Error('Must be string');
return value.trim();
}
notifyChange(value) {
console.log('Value changed to:', value);
this.dispatchEvent(new CustomEvent('value-changed', { detail: value }));
}
}
const component = new SmartComponent();
component.value = ' hello '; // "hello" (trimmed and notified)
// Runs validateInput() → setter → notifyChange()📦 API Reference
@before(method)
Execute interceptor BEFORE method call.
Parameters:
method(function|string) - Interceptor method name or function
Behavior:
- Interceptor receives original arguments
- Interceptor must return transformed arguments
- Original method receives transformed args
- Original method's return value is returned to caller
Usage:
@before(validate)
setValue(value) { /* value is validated */ }
validate(value) {
return value.toUpperCase();
}@around(method)
Execute interceptor AFTER method call (async).
Parameters:
method(function|string) - Interceptor method name or function
Behavior:
- Original method runs immediately
- Interceptor runs asynchronously via setImmediate
- Interceptor doesn't affect return value
- Caller receives original method's return value
Usage:
@around(log)
async fetchData() { /* fetch and return */ }
log(args) {
console.log('Called with:', args);
}@after(method)
Execute interceptor WITH return value.
Parameters:
method(function|string) - Interceptor method name or function
Behavior:
- Original method runs and returns value
- Interceptor receives method's return value
- Interceptor must return transformed value
- Caller receives interceptor's return value
Usage:
@after(format)
getData() { return { raw: 'data' }; }
format(data) {
return { ...data, formatted: true };
}🎯 Real-World Use Cases
Validation decorator
import { before } from '@adalink/spark-middleware';
class Validator {
@before(validateEmail)
setEmail(email) {
this.email = email;
}
@before(validateAge)
setAge(age) {
this.age = age;
}
validateEmail(email) {
if (!email?.includes('@')) throw new Error('Invalid email');
return email.toLowerCase().trim();
}
validateAge(age) {
const num = parseInt(age, 10);
if (isNaN(num) || num < 0 || num > 150) throw new Error('Invalid age');
return num;
}
}Authentication decorator
import { before } from '@adalink/spark-middleware';
class AuthService {
#token = null;
@before(checkAuth)
async fetchProtectedData() {
const response = await fetch('https://api.example.com/protected', {
headers: { 'Authorization': `Bearer ${this.#token}` }
});
return response.json();
}
@before(requireAuth)
createSession(user, password) {
return api.login(user, password);
}
checkAuth() {
if (!this.#token) throw new Error('Not authenticated');
}
requireAuth(credentials) {
if (!credentials?.user || !credentials?.password) {
throw new Error('Credentials required');
}
return credentials;
}
}Logging decorator
import { around } from '@adalink/spark-middleware';
class Logger {
@around(logMethod)
async fetchData(id) {
const response = await fetch(`https://api.example.com/data/${id}`);
return response.json();
}
@around(logError)
async riskyOperation(data) {
// Might throw error
return process(data);
}
logMethod(args) {
console.log(`Method called with:`, args);
metrics.increment('method.calls');
}
logError(args) {
console.log(`Error occurred with args:`, args);
metrics.increment('method.errors');
}
}Data transformation decorator
import { after } from '@adalink/spark-middleware';
class Transformer {
@after(toCamelCase)
fetchData() {
return { first_name: 'John', last_name: 'Doe', user_age: 30 };
}
toCamelCase(obj) {
return Object.keys(obj).reduce((acc, key) => {
const camelKey = key.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
acc[camelKey] = obj[key];
return acc;
}, {});
}
// Returns: { firstName: 'John', lastName: 'Doe', userAge: 30 }
}📊 Why Spark Middleware Over Alternatives?
| Feature | Spark Middleware | Core Decorators | Decorator Agent | lodash-decorators | |---------|------------------|------------------|-----------------|-------------------| | Zero Dependencies | ✅ | ✅ | ✅ | ❌ | | Bundle Size | ~70B/dec | ~1.2KB | ~800B | ~2KB | | AOP Pattern | ✅ | ⚠️ | ⚠️ | ❌ | | @before | ✅ | ✅ | ✅ | ✅ | | @around | ✅ | ✅ | ✅ | ✅ | | @after | ✅ | ✅ | ❌ | ✅ | | Async Support | ✅ | ✅ | ⚠️ | ⚠️ | | Works with Setters | ✅ | ✅ | ✅ | ⚠️ | | Stacking | ✅ | ✅ | ✅ | ✅ | | TypeScript Ready | ✅ | ✅ | ✅ | ✅ |
🌐 Usage in Frameworks
With Vanilla JavaScript
import { before, after } from '@adalink/spark-middleware';
class UserService {
@before(validate)
async createUser(data) {
const response = await fetch('/api/users', {
method: 'POST',
body: JSON.stringify(data)
});
return response.json();
}
validate(user) {
if (!user.email || !user.password) {
throw new Error('Email and password required');
}
return { ...user, created_at: Date.now() };
}
}With React
import { before, around } from '@adalink/spark-middleware';
import { useState } from 'react';
class AuthManager {
constructor(setUser) {
this.setUser = setUser;
}
@before(checkCredentials)
async login(credentials) {
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials)
});
return response.json();
}
@around(logAuth)
async logout() {
const response = await fetch('/api/logout', { method: 'POST' });
this.setUser(null);
return response.json();
}
checkCredentials(creds) {
if (!creds.email || !creds.password) {
throw new Error('Email and password required');
}
return creds;
}
logAuth(args) {
console.log('Auth action:', args[0]);
}
}
function Login() {
const [user, setUser] = useState(null);
const auth = new AuthManager(setUser);
// Use auth.login and auth.logout
}With Vue
import { before, after } from '@adalink/spark-middleware';
class Store {
constructor() {
this.data = [];
}
@before(validateItem)
addItem(item) {
this.data.push(item);
return item;
}
@after(sortItems)
async fetchItems() {
const response = await fetch('/api/items');
const items = await response.json();
this.data = items;
return items;
}
validateItem(item) {
if (!item.id || !item.name) {
throw new Error('Item must have id and name');
}
return { ...item, timestamp: Date.now() };
}
sortItems(items) {
return items.sort((a, b) => a.name.localeCompare(b.name));
}
}
export default {
data() {
return {
store: new Store()
}
}
// Use store inside Vue component
}🛠️ Development
Prerequisites
- Node.js 18+
Setup
# Clone repository
git clone https://github.com/Adalink-ai/spark_middleware.git
cd spark_middleware
# Install dependencies
npm install
# Build package
npm run build
# Start development server
npm run dev
# Lint and format
npx biome check .
npx biome check --write .📚 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 Spark Middleware useful, please ⭐ star it on GitHub!
📢 Share
Share Spark Middleware with your network:
🌐 Spark Ecosystem
O Spark Ecosystem é um conjunto de bibliotecas para desenvolvimento web reativo:
- @adalink/spark-std - Biblioteca padrão com decorators para Web Components
- @adalink/spark-echo - Sistema de comunicação reativa entre componentes
- @adalink/spark-cookie - Gerenciamento de cookies
- @adalink/spark-http - Cliente HTTP fluente
Mais pacotes em breve:
- @adalink/spark-form - Componentes de formulário reativos (em breve)
- @adalink/spark-router - Roteamento SPA (em breve)
📄 License
Apache-2.0 © 2026 Adalink
🔗 Links
- Repository: github.com/Adalink-ai/spark_middleware
- NPM Package: npmjs.com/package/@adalink/spark-middleware
- Organization: github.com/Adalink-ai
Built with ❤️ by Adalink
Spark Middleware - Compose behavior with ease. 🔄
