npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@adalink/spark-middleware

v1.0.0

Published

Method decorators for cross-cutting concerns (AOP)

Downloads

56

Readme

🔄 Spark Middleware

npm version License Build Status Code Style Ecosystem

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-middleware

Option 2: Install from GitHub (Alternative)

# Install directly from GitHub repository
npm install github:Adalink-ai/spark_middleware#v1.0.0

Option 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:

  1. Have an npm account
  2. Request access to the @adalink scope from the maintainers
  3. 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:

  1. Interceptor receives original arguments
  2. Interceptor must return transformed arguments
  3. Original method receives transformed args
  4. 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:

  1. Original method runs immediately
  2. Interceptor runs asynchronously via setImmediate
  3. Interceptor doesn't affect return value
  4. 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:

  1. Original method runs and returns value
  2. Interceptor receives method's return value
  3. Interceptor must return transformed value
  4. 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


🤝 Contributing

We welcome contributions! Please read our Contributing Guide before getting started.

Ways to contribute:


👥 Author & Community

Cleber de Moraes Goncalves - Creator & Lead Maintainer

🌟 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:

Mais pacotes em breve:


📄 License

Apache-2.0 © 2026 Adalink


🔗 Links


Built with ❤️ by Adalink

Spark Middleware - Compose behavior with ease. 🔄