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

@expressive-tea/commons

v2026.1.1

Published

Core utilities and shared types for Expressive Tea framework

Readme

@expressive-tea/commons

🎯 The foundation that powers decorator magic in Expressive Tea

npm version npm downloads license

📅 Versioning: This package uses Calendar Versioning (CalVer) in the format YYYY.MM.MICRO (e.g., 2026.1.0). Learn more →


[!IMPORTANT]

🔄 Version Compatibility

This package (v2026.1.0) requires @expressive-tea/core >= 2.0.0

Using Expressive Tea Core v1.x? You'll need @expressive-tea/[email protected] (last SemVer version for Core v1.x).

📦 Expressive Tea Core Reference: This package is designed to work with Expressive Tea Core - the main framework.


[!NOTE]

📦 Metadata Package

As of v2026.1.0, all metadata functionality has been extracted to @expressive-tea/metadata

This package now re-exports all metadata APIs from @expressive-tea/metadata for backward compatibility:

  • Metadata class (metadata management)
  • @SetMetadata, @Meta, @InheritMetadata, @CacheInMetadata, @Deprecated decorators
  • getClass helper utility

For new projects, consider using @expressive-tea/metadata directly for better tree-shaking and smaller bundle sizes.

For existing projects, no changes needed - everything works the same way!


Why Commons?

Ever wondered how decorators like @Route or @Inject actually work under the hood? That's where commons comes in! This package is the secret sauce that makes TypeScript decorators feel like pure magic ✨

Think of it as your metadata Swiss Army knife - a lean, mean, type-safe wrapper around reflect-metadata that powers the entire Expressive Tea ecosystem.

⚡ Features That'll Make You Smile

  • Metadata Management - Store and retrieve data on classes, methods, and properties with ease
  • 🎨 Decorator Utilities - Ready-to-use decorators (@SetMetadata, @Meta, @Deprecated, and more!)
  • 🎯 TypeScript-First - Written in TS5 with strict mode - your IDE will love you
  • 🔒 100% Type-Safe - No more any nightmares in your decorator code
  • 📦 Feather-Light - Zero dependencies except reflect-metadata
  • 🧪 Battle-Tested - 94%+ test coverage - we don't ship bugs
  • 📚 Self-Documenting - Full JSDoc on every method - no docs, no merge!

🚀 Installation

# npm
npm install @expressive-tea/commons reflect-metadata

# yarn
yarn add @expressive-tea/commons reflect-metadata

# pnpm
pnpm add @expressive-tea/commons reflect-metadata

💻 Requirements

  • Node.js ≥ 18.0.0 (LTS recommended)
  • TypeScript ≥ 5.0.0 (if you're using TypeScript)
  • reflect-metadata 0.2.x

⚙️ TypeScript Setup

Add this to your tsconfig.json (don't skip this!):

{
  "compilerOptions": {
    "experimentalDecorators": true,     // 🎨 Enable decorator magic
    "emitDecoratorMetadata": true,       // 📊 Emit design-time type info
    "target": "ES2017",
    "module": "commonjs"
  }
}

🎨 Usage Examples

Creating Your First Decorator

Let's build a @Route decorator from scratch:

import 'reflect-metadata';
import { Metadata } from '@expressive-tea/commons';

// 1. Create the decorator
function Route(path: string) {
  return function (target: any, propertyKey: string) {
    Metadata.set('route:path', path, target, propertyKey);
  };
}

// 2. Use it on a class
class UserController {
  @Route('/users')
  getUsers() {
    return [];
  }
  
  @Route('/users/:id')
  getUser() {
    return {};
  }
}

// 3. Read the metadata
const routes = Metadata.get('route:path', UserController.prototype, 'getUsers');
console.log(routes); // '/users'

BOOM! 💥 You just created a decorator system!

Class-Level Metadata

Decorators aren't just for methods - classes can have metadata too:

import { Metadata } from '@expressive-tea/commons';

// Mark a class as a controller
function Controller(basePath: string) {
  return function (target: Function) {
    Metadata.set('controller:basePath', basePath, target);
    Metadata.set('controller:isController', true, target);
  };
}

@Controller('/api/v1')
class ApiController {}

// Retrieve it later
const basePath = Metadata.get('controller:basePath', ApiController);
console.log(basePath); // '/api/v1'

// Check if it's a controller
if (Metadata.has('controller:isController', ApiController)) {
  console.log('Yes, this is a controller! 🎮');
}

// Get all metadata keys
const keys = Metadata.getKeys(ApiController);
console.log(keys); // ['controller:basePath', 'controller:isController']

Property Decorators

Need to inject dependencies? Property metadata has your back:

import { Metadata } from '@expressive-tea/commons';

// Dependency injection decorator
function Inject(token: string) {
  return function (target: any, propertyKey: string) {
    Metadata.set('inject:token', token, target, propertyKey);
  };
}

class UserService {
  @Inject('DATABASE')
  private db: Database;
  
  @Inject('LOGGER')
  private logger: Logger;
}

// Build your DI container
function getInjectableProperties(target: any) {
  return Object.getOwnPropertyNames(target)
    .filter(prop => Metadata.has('inject:token', target, prop))
    .map(prop => ({
      property: prop,
      token: Metadata.get('inject:token', target, prop)
    }));
}

const injectables = getInjectableProperties(UserService.prototype);
console.log(injectables);
// [
//   { property: 'db', token: 'DATABASE' },
//   { property: 'logger', token: 'LOGGER' }
// ]

Parameter Decorators

Level up with parameter metadata:

import { Metadata } from '@expressive-tea/commons';

// Body parameter decorator
function Body() {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    const existingParams = Metadata.get('params:body', target, propertyKey) || [];
    existingParams.push(parameterIndex);
    Metadata.set('params:body', existingParams, target, propertyKey);
  };
}

// Query parameter decorator
function Query(name: string) {
  return function (target: any, propertyKey: string, parameterIndex: number) {
    const existingParams = Metadata.get('params:query', target, propertyKey) || {};
    existingParams[parameterIndex] = name;
    Metadata.set('params:query', existingParams, target, propertyKey);
  };
}

class AuthController {
  login(
    @Body() credentials: LoginDto,
    @Query('rememberMe') remember: boolean
  ) {
    // Your login logic
  }
}

// Extract parameter metadata
const bodyParams = Metadata.get('params:body', AuthController.prototype, 'login');
const queryParams = Metadata.get('params:query', AuthController.prototype, 'login');

console.log('Body parameters at indices:', bodyParams);   // [0]
console.log('Query parameters:', queryParams);            // { 1: 'rememberMe' }

Design-Time Type Information

TypeScript can tell you the types at design time:

import { Metadata } from '@expressive-tea/commons';

class BlogService {
  createPost(title: string, content: string): Promise<Post> {
    return Promise.resolve({} as Post);
  }
}

// Get parameter types
const paramTypes = Metadata.getParamTypes(BlogService.prototype, 'createPost');
console.log(paramTypes); // [String, String]

// Get return type
const returnType = Metadata.getReturnType(BlogService.prototype, 'createPost');
console.log(returnType); // Promise

// Get design type
const designType = Metadata.getType(BlogService.prototype, 'createPost');
console.log(designType); // Function

Helper Utilities

import { getClass } from '@expressive-tea/commons';

const instance = new UserController();
const Constructor = getClass(instance);

console.log(Constructor.name); // 'UserController'
console.log(Constructor === UserController); // true

🎁 Decorator Utilities

NEW in v2.0.0! Commons now includes ready-to-use decorator utilities to make working with metadata even easier.

@SetMetadata

Set metadata with a single decorator:

import { SetMetadata, Metadata } from '@expressive-tea/commons';

// Static value
@SetMetadata('role', 'admin')
class AdminController {}

// Dynamic value with factory function
@SetMetadata('timestamp', () => Date.now())
class MyClass {}

// On methods
class UserController {
  @SetMetadata('route:path', '/users')
  @SetMetadata('route:method', 'GET')
  getUsers() {}
}

// Retrieve the metadata
const role = Metadata.get('role', AdminController); // 'admin'
const path = Metadata.get('route:path', UserController.prototype, 'getUsers'); // '/users'

@Meta

Set multiple metadata key-value pairs at once:

import { Meta, Metadata } from '@expressive-tea/commons';

// Multiple values on a class
@Meta({
  controller: true,
  basePath: '/api',
  version: 'v1',
  deprecated: false
})
class ApiController {}

// Multiple values on a method
class ProductController {
  @Meta({
    'route:path': '/products',
    'route:method': 'GET',
    'cache:ttl': 3600,
    'auth:required': true
  })
  getProducts() {}
}

// Retrieve any key
const basePath = Metadata.get('basePath', ApiController); // '/api'
const cacheTtl = Metadata.get('cache:ttl', ProductController.prototype, 'getProducts'); // 3600

@InheritMetadata

Copy metadata from one class to another:

import { Meta, InheritMetadata, Metadata } from '@expressive-tea/commons';

// Base class with configuration
@Meta({ timeout: 5000, retries: 3, maxSize: 100 })
class BaseController {}

// Inherit specific keys
@InheritMetadata(BaseController, ['timeout', 'retries'])
class UserController {}

// Check inherited values
const timeout = Metadata.get('timeout', UserController); // 5000
const retries = Metadata.get('retries', UserController); // 3
const maxSize = Metadata.get('maxSize', UserController); // undefined (not inherited)

@CacheInMetadata

Cache method results in metadata:

import { CacheInMetadata, Metadata } from '@expressive-tea/commons';

class ConfigProvider {
  @CacheInMetadata('computed:config')
  getConfig() {
    console.log('Computing expensive config...');
    return {
      apiKey: process.env.API_KEY,
      timeout: 5000
    };
  }
}

const provider = new ConfigProvider();
provider.getConfig(); // Logs: "Computing expensive config..."
provider.getConfig(); // Returns cached value, no log!

// Access the cached value directly
const cached = Metadata.get('computed:config', ConfigProvider.prototype, 'getConfig');

@Deprecated

Mark classes or methods as deprecated:

import { Deprecated, Metadata } from '@expressive-tea/commons';

// Deprecate a class
@Deprecated('Use NewUserController instead')
class UserController {}

// Deprecate a method with warning
class ProductService {
  @Deprecated('Use findById() instead', true)
  getProduct(id: string) {
    return this.findById(id);
  }
  
  findById(id: string) {
    return { id, name: 'Product' };
  }
}

// When called, logs:  [DEPRECATED] ProductService.getProduct is deprecated. Use findById() instead
const service = new ProductService();
service.getProduct('123');

// Check if deprecated
const isDeprecated = Metadata.get('deprecated', UserController); // true
const message = Metadata.get('deprecated:message', UserController); // 'Use NewUserController instead'

Combining Decorators

Stack them up for maximum power! 🚀

import { Meta, SetMetadata, Deprecated, CacheInMetadata } from '@expressive-tea/commons';

@Meta({ type: 'controller', version: 'v2' })
@SetMetadata('basePath', '/api/v2')
class ApiController {
  @SetMetadata('route:path', '/users')
  @Meta({ method: 'GET', auth: true, rateLimit: 100 })
  @CacheInMetadata('cache:users')
  getUsers() {
    return [];
  }
  
  @Deprecated('Use getUsers() with pagination instead')
  getAllUsers() {
    return this.getUsers();
  }
}

📖 API Reference

Metadata Class

The star of the show! All methods are static:

| Method | What It Does | |--------|-------------| | set(key, value, target, propertyKey?) | 💾 Store metadata | | get(key, target, propertyKey?) | 🔍 Retrieve metadata | | has(key, target, propertyKey?) | ✅ Check if metadata exists | | delete(key, target, propertyKey?) | 🗑️ Remove metadata | | getKeys(target, propertyKey?) | 🗝️ List all metadata keys | | getOwnKeys(target, propertyKey?) | 🏠 Get own keys (not inherited) | | getType(target, propertyKey) | 🎯 Get design-time type | | getParamTypes(target, propertyKey) | 📝 Get parameter types | | getReturnType(target, propertyKey) | ↩️ Get return type |

Pro tip: Check out the JSDoc comments for detailed usage and examples!

Helper Functions

| Function | What It Does | |----------|-------------| | getClass(target) | 🏗️ Get constructor from instance or prototype |

🛠️ Development

Want to contribute? Awesome! Here's how to get started:

# Install dependencies
yarn install

# Build the package
yarn build

# Run tests
yarn test

# Watch mode for tests
yarn test:watch

# Lint your code
yarn lint

# Format your code
yarn format

📁 Project Structure

@expressive-tea/commons/
├── src/
│   ├── classes/
│   │   └── Metadata.ts          # 🎯 The metadata powerhouse
│   ├── helpers/
│   │   └── object-helper.ts     # 🛠️ Handy utilities
│   ├── types/
│   │   └── index.ts             # 📘 TypeScript type definitions
│   ├── __test__/
│   │   └── unit/                # 🧪 Comprehensive tests
│   └── index.ts                 # 📦 Public API exports
├── dist/                        # 🏗️ Compiled output (generated)
├── package.json
├── tsconfig.json
└── README.md

🤝 Contributing

We'd love your help making commons even better! Here's how:

  1. 🍴 Fork the repo
  2. 🌿 Create a feature branch: git checkout -b feature/amazing-feature
  3. ✍️ Make your changes and add tests (we love tests!)
  4. ✅ Run yarn test - all green? Great!
  5. 🎨 Run yarn lint - make it pretty
  6. 💾 Commit: git commit -m "feat: add amazing feature"
  7. 🚀 Push and create a PR

Quality checklist:

  • ✅ Tests pass
  • ✅ Linting passes
  • ✅ Coverage stays high (>90%)
  • ✅ JSDoc added for public APIs
  • ✅ TypeScript compiles with no errors

Check out our Contributing Guide for more details!

🔗 Related Packages

📝 Changelog

See CHANGELOG.md for what's new in each release.

🔄 Migrating from v0.0.x?

Check out our Migration Guide - it's easier than you think!

💬 Support

Need help? We've got you covered:

📄 License

Apache-2.0 License - see LICENSE for details.

Free to use, free to modify, free to distribute. Build something awesome! 🚀

👨‍💻 Author

Zero One IT - https://zerooneit.com

🌟 Contributors

See all amazing contributors who've helped build this!