Support Module

The Support module provides essential utilities, decorators, and helper functions that underpin Orchestr's dependency injection system, routing capabilities, and overall framework functionality.

Overview

The Support module (src/Support/) contains foundational utilities that enable core Orchestr features:

@Injectable() Decorator

Enables automatic dependency injection via TypeScript reflection

Helper Functions

Laravel-style global utilities for path resolution and route loading

Facade System

Static proxy pattern for elegant service access

These utilities work together to provide a familiar Laravel developer experience while leveraging TypeScript's type system and decorator metadata capabilities.

@Injectable() Decorator

The @Injectable() decorator is the cornerstone of Orchestr's dependency injection system. It marks classes for automatic constructor-based dependency injection.

TypeScript Decorators

The @Injectable decorator uses TypeScript's experimental decorator feature combined with reflect-metadata to automatically discover constructor dependencies.

Basic Usage

import { Injectable } from '@orchestr-sh/orchestr';

@Injectable()
export class UserService {
  constructor(
    private database: DatabaseService,
    private logger: Logger,
    private cache: CacheService
  ) {
    // Dependencies automatically injected
  }

  async createUser(userData: CreateUserDto) {
    this.logger.info('Creating user', userData);
    
    const user = await this.database.insert('users', userData);
    await this.cache.set(`user:${user.id}`, user);
    
    return user;
  }
}

Dependency Resolution

When the container resolves an @Injectable() class, it automatically:

  1. Inspects the constructor parameters using TypeScript metadata
  2. Resolves each dependency from the container
  3. Creates the class instance with all dependencies injected
  4. Caches the instance for singleton services

Configuration Options

// Basic injectable
@Injectable()
export class BasicService {}

// Injectable with specific binding name
@Injectable('user.service')
export class NamedService {}

// Injectable as singleton
@Injectable({ singleton: true })
export class SingletonService {}

// Injectable with factory function
@Injectable({ factory: true })
export class FactoryService {
  constructor(database: DatabaseService) {
    this.database = database;
  }

  static create(container: Container) {
    const database = container.make<DatabaseService>('database');
    return new FactoryService(database);
  }
}

Helper Functions

The Support module includes Laravel-style helper functions that make common operations easier:

Path Helpers

import { path, base_path, public_path, storage_path } from '@orchestr-sh/orchestr';

// Application root directory
const appPath = path(); // '/var/www/app'

// Application directory paths
const basePath = base_path(); // '/var/www/app/app'
const publicPath = public_path(); // '/var/www/app/public'
const storagePath = storage_path(); // '/var/www/app/storage'

// Relative path resolution
const configPath = path('config/app.json'); // '/var/www/app/config/app.json'
const viewsPath = base_path('views'); // '/var/www/app/app/views'

// Path joining and normalization
const fullPath = path.join(basePath(), 'storage', 'logs', 'app.log');

String Helpers

import { str } from '@orchestr-sh/orchestr';

// String manipulation
const slug = str.slug('Hello World!'); // 'hello-world'
const camel = str.camel('hello_world'); // 'helloWorld'
const snake = str.snake('HelloWorld'); // 'hello_world'
const studly = str.studly('hello_world'); // 'HelloWorld'

// String testing
const isEmail = str.isEmail('user@example.com'); // true
const isUrl = str.isUrl('https://example.com'); // true
const isUuid = str.isUuid('123e4567-e89b-12d3-a456-426614174000'); // true

// String generation
const random = str.random(32); // 'a1b2c3d4...'
const uuid = str.uuid(); // '550e8400-e29b-41d4...'
const limited = str.limit('Very long string', 10); // 'Very long...'

Array Helpers

import { arr } from '@orchestr-sh/orchestr';

// Array manipulation
const users = arr.add(users, newUser); // Add element
const unique = arr.unique(duplicateArray); // Remove duplicates
const shuffled = arr.shuffle(items); // Random shuffle
const first = arr.first(items); // First element
const last = arr.last(items); // Last element

// Array querying
const adults = arr.where(users, ['age', '>=', 18]);
const admins = arr.where(users, ['role', 'admin']);
const activeUsers = arr.where(users, ['status', 'active']);

// Array transformation
const names = arr.pluck(users, 'name'); // ['John', 'Jane', ...]
const grouped = arr.groupBy(users, 'department'); // Group by key
const sorted = arr.sortBy(users, 'name'); // Sort by key

Facade System

The Support module provides the foundation for the Facade pattern used throughout Orchestr:

Base Facade Class

export abstract class Facade {
  private static app: Application;
  private static resolvedInstances: Map<string, any> = new Map();

  static setFacadeApplication(app: Application): void {
    Facade.app = app;
  }

  protected static getFacadeAccessor(): string {
    throw new Error('Facade does not implement getFacadeAccessor method.');
  }

  protected static resolveFacadeInstance(name: string): any {
    if (Facade.resolvedInstances.has(name)) {
      return Facade.resolvedInstances.get(name);
    }

    if (Facade.app) {
      const instance = Facade.app.make(name);
      Facade.resolvedInstances.set(name, instance);
      return instance;
    }

    throw new Error('A facade root has not been set.');
  }

  protected static getFacadeRoot(): any {
    return Facade.resolveFacadeInstance(this.getFacadeAccessor());
  }
}

Creating Custom Facades

// 1. Create your service
export class CacheService {
  private cache: Map<string, any> = new Map();

  get(key: string): any {
    return this.cache.get(key);
  }

  set(key: string, value: any): void {
    this.cache.set(key, value);
  }
}

// 2. Create the facade
export class CacheFacade extends Facade {
  protected static getFacadeAccessor(): string {
    return 'cache';
  }
}

// 3. Create the proxy export
export const Cache = new Proxy(CacheFacade, {
  get(target, prop) {
    const instance = (target as any).getFacadeRoot();
    if (instance && typeof instance[prop] === 'function') {
      return (...args: any[]) => instance[prop](...args);
    }
    return instance[prop];
  }
}) as any;

Facade Benefits

Facades provide a clean, Laravel-like syntax for accessing services while maintaining testability and type safety.

Configuration

Configure reflection and decorator support in your TypeScript project:

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2017",
    "module": "commonjs",
    "lib": ["ES2017", "dom"],
    "strict": true
  },
  "include": ["src/**/*"]
}

// package.json
{
  "dependencies": {
    "reflect-metadata": "^0.1.13",
    "@orchestr-sh/orchestr": "^1.0.0"
  }
}

Decorator Support

Make sure to import 'reflect-metadata' at the top of your application entry file before any decorators are used.

Best Practices

Dependency Injection

Use constructor injection over manual service resolution

Type Safety

Leverage TypeScript types for all injected dependencies

Singleton Services

Mark long-lived services as singletons for performance

Factory Patterns

Use factory functions for complex initialization logic

Testing

Mock dependencies in tests using container instances

Configuration

Enable decorators and metadata in TypeScript configuration

Integration

The Support module works seamlessly with the Container, Routing, and Application modules to provide a complete development experience that feels familiar to Laravel developers while being fully type-safe.