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

@eklabdev/bling

v0.1.2

Published

A comprehensive TypeScript decorators library implementing Stage 3 decorators for TS 5.0+

Readme

@eklabdev/bling

A powerful TypeScript decorators library that provides a collection of useful decorators for class and method manipulation, scheduling, caching, error handling, and more.

Installation

npm install @eklabdev/bling

Features

Class Decorators

@Singleton

Ensures a class has only one instance throughout the application lifecycle.

@Singleton()
class DatabaseConnection {
  private connection: any;

  async connect() {
    this.connection = await createConnection();
  }

  async query(sql: string) {
    return this.connection.query(sql);
  }
}

// Usage
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
console.log(db1 === db2); // true - same instance

@Immutable

Makes all properties of a class read-only after initialization.

@Immutable()
class Configuration {
  constructor(
    public readonly apiKey: string,
    public readonly endpoint: string
  ) {}
}

// Usage
const config = new Configuration('key123', 'https://api.example.com');
// config.apiKey = 'newKey'; // Error: Cannot assign to read-only property

@Serializable

Adds serialization capabilities to a class.

@Serializable({
  toJSON: true,
  toObject: true,
  transform: value => ({
    ...value,
    createdAt: new Date(value.createdAt),
  }),
})
class User {
  constructor(
    public name: string,
    public email: string,
    public createdAt: Date
  ) {}
}

// Usage
const user = new User('John', '[email protected]', new Date());
const json = user.toJSON();
console.log(json); // { name: 'John', email: '[email protected]', createdAt: Date }

Method Decorators

Scheduling Decorators

@Schedule

Executes a method based on a cron expression.

class TaskScheduler implements Schedulable {
  @Schedule('*/5 * * * *') // Every 5 minutes
  async cleanup() {
    await this.removeOldFiles();
  }

  private async removeOldFiles() {
    // Implementation
  }

  // Required by Schedulable interface
  cleanupScheduling() {
    // Automatically added by decorator
  }
}

// Usage
const scheduler = new TaskScheduler();
// Task runs every 5 minutes
// Clean up when done
scheduler.cleanupScheduling();
@Interval

Executes a method at regular intervals.

class DataPoller implements Schedulable {
  private data: any[] = [];

  @Interval(5000) // Every 5 seconds
  async pollData() {
    const newData = await this.fetchData();
    this.data.push(...newData);
  }

  private async fetchData() {
    // Implementation
    return [];
  }
}

// Usage
const poller = new DataPoller();
// Data is polled every 5 seconds
// Clean up when done
poller.cleanupScheduling();
@Delay

Executes a method after a specified delay.

class DelayedTask implements Schedulable {
  @Delay(1000) // After 1 second
  async execute() {
    console.log('Task executed after delay');
  }
}

// Usage
const task = new DelayedTask();
// Task executes after 1 second
// Clean up when done
task.cleanupScheduling();

Error Handling Decorators

@Retry

Retries a method call on failure with configurable options.

class ApiClient {
  @Retry({
    maxRetries: 3,
    strategy: 'exponential',
    backoff: 1000,
    onRetry: (error, attempt) => {
      console.log(`Retry attempt ${attempt} due to ${error.message}`);
    },
  })
  async fetchData(id: string) {
    const response = await fetch(`/api/data/${id}`);
    if (!response.ok) throw new Error('API call failed');
    return response.json();
  }
}

// Usage
const client = new ApiClient();
try {
  const data = await client.fetchData('123');
  console.log(data);
} catch (error) {
  console.error('All retries failed:', error);
}
@Timeout

Limits the execution time of a method. If the method takes longer than the specified time, it will throw an error.

class ExternalService {
  @Timeout(5000) // 5 seconds timeout
  async callExternalApi() {
    const response = await fetch('https://slow-api.example.com');
    return response.json();
  }
}

// Usage
const service = new ExternalService();
try {
  const result = await service.callExternalApi();
  console.log(result);
} catch (error) {
  if (error.message.includes('timed out')) {
    console.error('API call took too long');
  }
}
@CircuitBreaker

Implements the circuit breaker pattern to prevent cascading failures. The circuit will open after a certain number of failures and close after a reset timeout.

class PaymentService {
  @CircuitBreaker({
    failureThreshold: 5,
    resetTimeout: 30000,
    onStateChange: state => {
      console.log(`Circuit state changed to: ${state}`);
    },
  })
  async processPayment(amount: number) {
    const response = await fetch('/api/payments', {
      method: 'POST',
      body: JSON.stringify({ amount }),
    });
    if (!response.ok) throw new Error('Payment failed');
    return response.json();
  }
}

// Usage
const paymentService = new PaymentService();
try {
  const result = await paymentService.processPayment(100);
  console.log('Payment processed:', result);
} catch (error) {
  console.error('Payment failed:', error);
}
@Fallback

Provides a fallback implementation when the method fails.

class DataService {
  @Fallback(() => ({ data: 'fallback data' }))
  async getData() {
    const response = await fetch('/api/data');
    if (!response.ok) throw new Error('Failed to fetch data');
    return response.json();
  }
}

// Usage
const service = new DataService();
const result = await service.getData();
console.log(result); // Either actual data or fallback data

Performance Decorators

@Memoize

Caches method results permanently.

class Calculator {
  @Memoize()
  expensiveOperation(x: number, y: number) {
    console.log('Computing...');
    return x * y;
  }
}

// Usage
const calc = new Calculator();
console.log(calc.expensiveOperation(5, 3)); // Computing... 15
console.log(calc.expensiveOperation(5, 3)); // 15 (cached)
@DebounceSync/@DebounceAsync

Debounces method calls to prevent rapid-fire execution.

class SearchService {
  @DebounceSync(300)
  search(query: string) {
    console.log('Searching for:', query);
    // Implementation
  }

  @DebounceAsync(300)
  async searchAsync(query: string) {
    console.log('Async searching for:', query);
    // Implementation
  }
}

// Usage
const search = new SearchService();
search.search('test'); // Only the last call within 300ms will execute
await search.searchAsync('test'); // Only the last call within 300ms will execute
@ThrottleSync/@ThrottleAsync

Throttles method calls to limit execution frequency.

class EventHandler {
  @ThrottleSync(1000)
  handleEvent() {
    console.log('Event handled');
  }

  @ThrottleAsync(1000)
  async handleEventAsync() {
    console.log('Async event handled');
  }
}

// Usage
const handler = new EventHandler();
handler.handleEvent(); // Executes
handler.handleEvent(); // Throttled
await handler.handleEventAsync(); // Executes
await handler.handleEventAsync(); // Throttled

Lifecycle Decorators

@EffectBefore/@EffectAfter/@EffectError

Executes functions before, after, or on error of a method.

class UserService {
  @EffectBefore(context => {
    console.log(`Before ${context.functionName} with args:`, context.args);
  })
  @EffectAfter(context => {
    console.log(`After ${context.functionName} with result:`, context.result);
  })
  @EffectError(context => {
    console.error(`Error in ${context.functionName}:`, context.error);
  })
  async createUser(user: { name: string; email: string }) {
    // Implementation
    return { id: 1, ...user };
  }
}

// Usage
const userService = new UserService();
try {
  const user = await userService.createUser({ name: 'John', email: '[email protected]' });
  console.log('User created:', user);
} catch (error) {
  console.error('Failed to create user:', error);
}

Validation Decorators

@GuardSync/@GuardAsync

Checks for required permissions before executing a method.

class AdminService {
  @GuardSync(user => user.isAdmin)
  deleteUser(userId: string) {
    console.log(`Deleting user ${userId}`);
  }

  @GuardAsync(async user => await checkPermissions(user))
  async updateSettings(settings: Settings) {
    console.log('Updating settings:', settings);
  }
}

// Usage
const admin = new AdminService();
try {
  admin.deleteUser('123'); // Only executes if user.isAdmin is true
  await admin.updateSettings({ theme: 'dark' }); // Only executes if checkPermissions returns true
} catch (error) {
  console.error('Permission denied:', error);
}
@Deprecate

Marks a method as deprecated.

class LegacyService {
  @Deprecate('Use newMethod instead')
  oldMethod() {
    console.log('This is the old implementation');
  }

  newMethod() {
    console.log('This is the new implementation');
  }
}

// Usage
const service = new LegacyService();
service.oldMethod(); // Warning: oldMethod is deprecated. Use newMethod instead

Field Decorators

Access Decorators

@Getter/@Setter/@Builder

Auto-generates getters, setters, and builder methods for fields. These decorators come with TypeScript helper types to ensure type safety.

class Person {
  @Getter()
  private name: string = '';

  @Setter()
  private age: number = 0;

  @Builder()
  private address: string = '';

  constructor(name: string, age: number, address: string) {
    this.name = name;
    this.age = age;
    this.address = address;
  }
}

// Type augmentation
type PersonWithHelpers = Person &
  WithGetter<Person, 'name'> &
  WithSetter<Person, 'age'> &
  WithBuilder<Person, 'address'>;

// Usage
const person = new Person('John', 30, '123 Main St') as PersonWithHelpers;
console.log(person.getName()); // 'John'
person.setAge(31);
console.log(person.getAge()); // 31
person.withAddress('456 Oak St');
console.log(person.getAddress()); // '456 Oak St'

Validation Decorators

@DefaultValue

Sets a default value for a field if undefined.

class Configuration {
  @DefaultValue('development')
  environment: string;

  @DefaultValue(3000)
  port: number;
}

// Usage
const config = new Configuration();
console.log(config.environment); // 'development'
console.log(config.port); // 3000

Reactivity Decorators

@Computed

Creates a computed property based on other properties.

class User {
  @Setter()
  firstName: string = '';

  @Setter()
  lastName: string = '';

  @Computed(self => `${self.firstName} ${self.lastName}`)
  fullName: string = '';
}

// Usage
const user = new User() as User & WithSetter<User, 'firstName'> & WithSetter<User, 'lastName'>;
user.setFirstName('John');
user.setLastName('Doe');
console.log(user.fullName); // 'John Doe'
@Observable

Makes a field observable with change tracking.

class User {
  @Observable()
  @Setter()
  name: string = '';

  @Observable()
  @Setter()
  email: string = '';
}

// Type augmentation
type UserWithObservable = User &
  WithObservable<User, 'name'> &
  WithObservable<User, 'email'> &
  WithSetter<User, 'name'> &
  WithSetter<User, 'email'>;

// Usage
const user = new User() as UserWithObservable;

// Subscribe to changes
const unsubscribe = user.subscribeName((newValue, oldValue) => {
  console.log(`Name changed from ${oldValue} to ${newValue}`);
});

user.setName('John');
user.setName('Jane');
unsubscribe();
user.setName('Bob'); // No console log

TypeScript Helper Types

The library provides several TypeScript helper types to ensure type safety when using decorators:

Schedulable Interface

Used with scheduling decorators (@Schedule, @Interval, @Delay):

import { Schedule, Interval, Delay, Schedulable } from '@eklabdev/bling';

class TaskScheduler implements Schedulable {
  @Schedule('*/5 * * * *')
  async cleanup() {
    // Cleanup logic
  }

  // Required by Schedulable interface
  cleanupScheduling() {
    // This method is automatically added by the decorators
  }
}

WithCache Type

Used with caching decorators (@Cached, @Memoize):

import { Cached, Memoize, WithCache } from '@eklabdev/bling';

class DataService {
  @Cached({ expiryTime: 5000 })
  async fetchData(id: string) {
    // Data fetching logic
  }
}

// Type augmentation for cache invalidation
type DataServiceWithCache = DataService & WithCache<DataService, 'fetchData'>;

const service = new DataService() as DataServiceWithCache;
service.invalidateFetchData(); // Clears the cache

WithObservable Type

Used with the @Observable decorator:

import { Observable, WithObservable } from '@eklabdev/bling';

class User {
  @Observable()
  name: string = '';
}

// Type augmentation for observable fields
type UserWithObservable = User & WithObservable<User, 'name'>;

const user = new User() as UserWithObservable;

// Now TypeScript knows about the observer methods
const unsubscribe = user.observeName((newValue, oldValue) => {
  console.log(`Name changed from ${oldValue} to ${newValue}`);
});

// Or use the subscribe pattern
user.subscribeName({
  next: value => console.log(`New name: ${value}`),
  error: error => console.error(error),
  complete: () => console.log('Completed'),
});

// Get current value
const currentName = user.getName();

WithSerialize Type

Used with the @Serialize decorator:

import { Serialize, WithSerialize } from '@eklabdev/bling';

class User {
  @Serialize()
  name: string = '';
}

// Type augmentation for serialization
type UserWithSerialize = User & WithSerialize<User, 'name'>;

const user = new User() as UserWithSerialize;
const serialized = user.serialize();

Access Helper Types

Used with field access decorators (@Getter, @Setter, @Builder):

import { Getter, Setter, Builder, WithGetter, WithSetter, WithBuilder } from '@eklabdev/bling';

class Person {
  @Getter()
  name: string = '';

  @Setter()
  age: number = 0;

  @Builder()
  address: string = '';
}

// Type augmentation for the decorated fields
type PersonWithHelpers = Person &
  WithGetter<Person, 'name'> &
  WithSetter<Person, 'age'> &
  WithBuilder<Person, 'address'>;

const person = new Person() as PersonWithHelpers;

// Now TypeScript knows about the generated methods
person.getName(); // Returns string
person.setAge(31); // Returns Person
person.withAddress('123 Main St'); // Returns Person

Cleanup

For scheduling decorators (@Schedule, @Interval, @Delay), make sure to call the cleanup method when you're done:

class TaskScheduler {
  @Schedule('*/5 * * * *')
  async cleanup() {
    // Cleanup logic
  }

  destroy() {
    this.cleanupScheduling();
  }
}

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT