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

@orchestr-sh/traits

v0.1.0

Published

PHP-style traits for TypeScript with decorators - bring Laravel's trait system to TypeScript

Downloads

93

Readme

@orchestr-sh/traits

PHP-style traits for TypeScript with decorators - bring Laravel's elegant trait system to TypeScript!

npm version License: MIT

Why Traits?

Traits provide a clean way to reuse code across multiple classes without the limitations of single inheritance. They're perfect for:

  • Horizontal code reuse - Share functionality across unrelated classes
  • 🎯 Multiple behaviors - Apply several traits to a single class
  • 🔧 Composition over inheritance - Flexible and maintainable
  • 🚀 Laravel familiarity - Same patterns you know from PHP

Installation

npm install @orchestr-sh/traits reflect-metadata

Important: Add reflect-metadata import at the top of your entry file:

import 'reflect-metadata';

Also ensure your tsconfig.json has:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Quick Start

import 'reflect-metadata';
import { Trait, UsesTraits } from '@orchestr-sh/traits';

// Define a trait
@Trait()
class Timestampable {
  created_at?: Date;
  updated_at?: Date;

  touch(): void {
    this.updated_at = new Date();
  }
}

// Use the trait
@UsesTraits(Timestampable)
class User {
  name: string = '';

  // Declare trait methods for TypeScript
  touch!: () => void;
  created_at?: Date;
  updated_at?: Date;
}

// It just works!
const user = new User();
user.touch();
console.log(user.updated_at); // Current date

Features

✅ Multiple Traits

Apply as many traits as you need:

@UsesTraits(Timestampable, SoftDeletes, Notifiable, Auditable)
class Article {
  // Methods from all traits are available
}

✅ Conflict Resolution

Handle method conflicts elegantly:

@UsesTraits(TraitA, TraitB)
@TraitInsteadOf(TraitA, 'greet', TraitB)  // Use TraitA's greet
@TraitAlias(TraitB, 'greet', 'greetB')    // Alias TraitB's greet
class MyClass {
  greet!: () => string;   // From TraitA
  greetB!: () => string;  // From TraitB (aliased)
}

✅ Trait Composition

Traits can use other traits:

@Trait()
@UsesTraits(Timestampable)
class SoftDeletes {
  touch!: () => void;  // From Timestampable

  delete(): void {
    this.deleted_at = new Date();
    this.touch();  // Use method from composed trait
  }
}

✅ Type Safety

Full TypeScript support with autocomplete:

const user = new User();
user.touch();  // ✓ Type-safe
user.notify(); // ✓ Type-safe

✅ Runtime Inspection

Helper functions to inspect traits at runtime:

import { getTraits, hasTrait, getTraitInfo } from '@orchestr-sh/traits';

const traits = getTraits(user);
const hasTimestamp = hasTrait(user, Timestampable);
const info = getTraitInfo(User);

API Reference

Decorators

@Trait()

Marks a class as a trait that can be used by other classes.

@Trait()
class MyTrait {
  myMethod(): void {
    // Implementation
  }
}

@UsesTraits(...traits)

Applies one or more traits to a class.

@UsesTraits(Trait1, Trait2, Trait3)
class MyClass {
  // Trait methods are now available
}

@TraitAlias(trait, originalName, alias)

Creates an alias for a trait method to avoid conflicts.

@UsesTraits(TraitA, TraitB)
@TraitAlias(TraitB, 'save', 'saveB')
class MyClass {
  save!: () => void;   // From TraitA
  saveB!: () => void;  // From TraitB (aliased)
}

@TraitInsteadOf(useTrait, method, ...insteadOf)

Resolves conflicts by specifying which trait's method to use.

@UsesTraits(TraitA, TraitB)
@TraitInsteadOf(TraitA, 'save', TraitB)
class MyClass {
  save!: () => void;  // Uses TraitA's save, not TraitB's
}

Helper Functions

getTraits(target)

Get all traits used by a class or instance.

const traits = getTraits(user);
// Returns: [Timestampable, SoftDeletes, ...]

hasTrait(target, trait)

Check if a class or instance uses a specific trait.

if (hasTrait(user, Timestampable)) {
  user.touch();
}

getTraitMethods(trait)

Get all method names provided by a trait.

const methods = getTraitMethods(Timestampable);
// Returns: ['touch', 'touchCreation']

getTraitProperties(trait)

Get all property names defined by a trait.

const properties = getTraitProperties(Timestampable);
// Returns: ['created_at', 'updated_at']

detectConflicts(traits)

Find method conflicts between traits.

const conflicts = detectConflicts([TraitA, TraitB, TraitC]);
// Returns: Map { 'save' => [TraitA, TraitB] }

getTraitInfo(target)

Get comprehensive information about traits used by a class.

const info = getTraitInfo(User);
// Returns: {
//   traits: [Timestampable, SoftDeletes],
//   methods: ['touch', 'delete', 'restore'],
//   properties: ['created_at', 'updated_at', 'deleted_at'],
//   conflicts: Map {}
// }

Examples

Laravel-Style Soft Deletes

@Trait()
class SoftDeletes {
  deleted_at?: Date;

  delete(): void {
    this.deleted_at = new Date();
  }

  restore(): void {
    this.deleted_at = undefined;
  }

  isDeleted(): boolean {
    return this.deleted_at !== undefined;
  }
}

@UsesTraits(SoftDeletes)
class Post {
  title: string = '';

  delete!: () => void;
  restore!: () => void;
  isDeleted!: () => boolean;
  deleted_at?: Date;
}

const post = new Post();
post.delete();         // Soft delete
console.log(post.isDeleted()); // true
post.restore();        // Restore

Notifiable Trait

@Trait()
class Notifiable {
  notify(message: string): void {
    console.log(`📧 ${message}`);
  }

  notifyViaEmail(email: string, message: string): void {
    console.log(`📧 Sending to ${email}: ${message}`);
  }
}

@UsesTraits(Notifiable)
class User {
  email: string = '';
  notify!: (message: string) => void;
  notifyViaEmail!: (email: string, message: string) => void;
}

const user = new User();
user.notify('Welcome!');
user.notifyViaEmail(user.email, 'Your account is ready');

Multiple Traits with Composition

@Trait()
class Timestampable {
  created_at?: Date;
  updated_at?: Date;

  touch(): void {
    this.updated_at = new Date();
  }
}

@Trait()
@UsesTraits(Timestampable)
class Auditable {
  audit_log: string[] = [];
  touch!: () => void;

  addAudit(action: string): void {
    this.audit_log.push(`${action} at ${new Date()}`);
    this.touch();
  }
}

@UsesTraits(Auditable)
class Document {
  addAudit!: (action: string) => void;
  audit_log!: string[];
  updated_at?: Date;
}

const doc = new Document();
doc.addAudit('Created');
doc.addAudit('Updated');
console.log(doc.audit_log);
console.log(doc.updated_at);

Comparison to PHP

| Feature | PHP Traits | @orchestr-sh/traits | |---------|-----------|---------------------| | Multiple traits | ✅ use A, B; | ✅ @UsesTraits(A, B) | | Conflict resolution | ✅ insteadof | ✅ @TraitInsteadOf | | Aliasing | ✅ as | ✅ @TraitAlias | | Composition | ✅ | ✅ | | Properties | ✅ | ✅ | | Type safety | ❌ | ✅ TypeScript! | | Runtime inspection | ⚠️ Limited | ✅ Full API |

Best Practices

1. Always Declare Trait Members

TypeScript needs to know about trait methods and properties:

@UsesTraits(Timestampable)
class User {
  // ✅ Good - TypeScript knows about these
  touch!: () => void;
  created_at?: Date;

  // ❌ Bad - TypeScript won't know these exist
}

2. Use Traits for Behavior, Not State

Traits work best for shared behavior:

// ✅ Good - Shared behavior
@Trait()
class Timestampable {
  touch() { /* ... */ }
}

// ⚠️ Consider carefully - Shared state
@Trait()
class HasSettings {
  settings: Map<string, any> = new Map();
}

3. Resolve Conflicts Explicitly

Don't rely on default conflict resolution:

// ⚠️ Implicit - Which save() is used?
@UsesTraits(TraitA, TraitB)
class MyClass { }

// ✅ Explicit - Clear which one wins
@UsesTraits(TraitA, TraitB)
@TraitInsteadOf(TraitA, 'save', TraitB)
class MyClass { }

4. Use Descriptive Aliases

Make aliases clear and meaningful:

// ✅ Good - Clear what greetB is
@TraitAlias(TraitB, 'greet', 'greetB')

// ❌ Bad - Unclear
@TraitAlias(TraitB, 'greet', 'g2')

TypeScript Tips

Declaring Trait Members

Use the ! operator for trait methods and properties:

@UsesTraits(MyTrait)
class MyClass {
  traitMethod!: () => void;  // Definite assignment
  traitProp?: string;         // Optional property
}

Creating a Base Trait Type

For shared trait interfaces:

interface ITimestampable {
  created_at?: Date;
  updated_at?: Date;
  touch(): void;
}

@Trait()
class Timestampable implements ITimestampable {
  created_at?: Date;
  updated_at?: Date;
  touch(): void {
    this.updated_at = new Date();
  }
}

Common Patterns

Builder Pattern with Traits

@Trait()
class Buildable<T> {
  static make<T>(this: new () => T, attributes: Partial<T>): T {
    const instance = new this();
    Object.assign(instance, attributes);
    return instance;
  }
}

Observable Pattern

@Trait()
class Observable {
  private observers: Function[] = [];

  observe(callback: Function): void {
    this.observers.push(callback);
  }

  notify(event: string): void {
    this.observers.forEach(cb => cb(event));
  }
}

Contributing

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

License

MIT © Orchestr

Related Projects


Made with ❤️ by the Orchestr team