npm package: @stratly/core
Description: Clean architecture foundation for Stratly
Latest version: 0.0.8

# @stratly/core

[![Version](https://img.shields.io/npm/v/@stratly/core?style=flat-square)](https://www.npmjs.com/package/@stratly/core)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)](https://www.typescriptlang.org/)

Enterprise-grade Clean Architecture foundation for the Stratly AI-powered business management platform. This core package provides battle-tested abstractions, domain patterns, and architectural utilities that enable consistent, scalable, and maintainable development across different firebase codebases.

## ✨ What is @stratly/core?

`@stratly/core` is the foundational architecture package that powers Stratly's multi-tenant, multi-domain business platform. It implements Clean Architecture principles with Domain-Driven Design (DDD) patterns, specifically designed for enterprise-scale applications requiring strict tenant isolation, role-based access control, and event-driven capabilities.

## 🚀 Key Benefits

- **🏢 Multi-Tenant by Design** - Built-in organizational isolation and secure tenant context propagation
- **🛡️ Enterprise Security** - Role-based access control (RBAC) with granular permissions
- **⚡ Type-Safe** - Complete TypeScript coverage with strict typing and validation
- **🔄 Event-Driven** - Domain events with correlation IDs and audit trails
- **🧪 Testable** - Clean architecture enables easy unit and integration testing
- **📈 Scalable** - Proven patterns for complex business domains

## 🏗️ Architecture Overview

This package implements Clean Architecture principles with Domain-Driven Design (DDD) patterns, specifically designed for a multi-tenant, multi-domain business platform.

### Core Principles

- **Dependency Inversion**: Inner layers don't depend on outer layers
- **Multi-tenancy**: Built-in organizational isolation (`orgId`)
- **Domain-Driven Design**: Rich domain models with business logic
- **CQRS Ready**: Separate command and query patterns
- **Event-Driven**: Domain events for loose coupling
- **Type Safety**: Full TypeScript support with strict typing

## 📦 Installation

### Prerequisites

- **Node.js** 20.x or higher
- **TypeScript** 5.0 or higher
- **npm** 9.x or higher

### Install via npm

```bash
# Install the core package
npm install @stratly/core

# Or with yarn
yarn add @stratly/core

# Or with pnpm
pnpm add @stratly/core
```

### Peer Dependencies

This package requires the following peer dependencies:

```bash
npm install firebase-admin firebase-functions
```

## 🚀 Quick Start

### Basic Example

Here's a complete example showing how to build a feature using clean architecture patterns:

```typescript
import {
  Entity,
  ValueObject,
  UseCase,
  Result,
  Repository,
  UseCaseContext,
  EntityProps,
  RepositoryOptions,
  DomainError,
  ValidationError,
} from "@stratly/core";

// 1. Define a Value Object with validation
class Email extends ValueObject<string> {
  protected validate(value: string): void {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      throw new ValidationError("Invalid email format");
    }
  }

  get domain(): string {
    return this.value.split("@")[1];
  }
}

// 2. Define an Entity with business logic
interface UserProps extends EntityProps {
  email: Email;
  name: string;
  isActive: boolean;
}

class User extends Entity<UserProps> {
  static create(
    props: Omit<UserProps, keyof EntityProps>,
    orgId: string,
  ): User {
    return new User({
      ...props,
      orgId,
      createdAt: new Date(),
      updatedAt: new Date(),
      createdBy: "system",
      updatedBy: "system",
    });
  }

  get email(): Email {
    return this._props.email;
  }

  get name(): string {
    return this._props.name;
  }

  get isActive(): boolean {
    return this._props.isActive;
  }

  activate(updatedBy: string): void {
    this._props.isActive = true;
    this.updateAudit(updatedBy);
  }

  deactivate(updatedBy: string): void {
    this._props.isActive = false;
    this.updateAudit(updatedBy);
  }
}

// 3. Define Repository interface
interface UserRepository extends Repository<User> {
  findByEmail(
    email: Email,
    options: RepositoryOptions,
  ): Promise<Result<User | null>>;

  findActiveUsers(options: RepositoryOptions): Promise<Result<User[]>>;
}

// 4. Define Use Case with proper error handling
interface CreateUserRequest {
  email: string;
  name: string;
}

class CreateUserUseCase extends UseCase<CreateUserRequest, User> {
  constructor(private userRepository: UserRepository) {
    super();
  }

  async execute(
    request: CreateUserRequest,
    context: UseCaseContext,
  ): Promise<Result<User>> {
    try {
      // Validate input
      const email = new Email(request.email);

      // Check if user already exists
      const existingUser = await this.userRepository.findByEmail(email, {
        orgId: context.tenant.orgId,
      });

      if (existingUser.isFailure) {
        return Result.failure(existingUser.error);
      }

      if (existingUser.value) {
        return Result.failure(
          new DomainError("User with this email already exists", "USER_EXISTS"),
        );
      }

      // Create new user
      const user = User.create(
        {
          email,
          name: request.name,
          isActive: true,
        },
        context.tenant.orgId,
      );

      // Save to repository
      const saveResult = await this.userRepository.save(user, {
        orgId: context.tenant.orgId,
      });

      return saveResult;
    } catch (error) {
      return Result.failure(
        new DomainError("Failed to create user", "CREATE_USER_ERROR", error),
      );
    }
  }
}
```

### Usage in Firebase Functions

```typescript
import { https } from "firebase-functions/v2";
import { FirebaseUserRepository } from "./infrastructure/repositories";
import { CreateUserUseCase } from "./application/usecases";

const userRepository = new FirebaseUserRepository();
const createUserUseCase = new CreateUserUseCase(userRepository);

export const createUser = https.onRequest(async (req, res) => {
  try {
    const context: UseCaseContext = {
      tenant: {
        orgId: req.headers["x-org-id"] as string,
        userId: req.headers["x-user-id"] as string,
        roles: ["ADMIN"],
        permissions: ["USER_CREATE"],
      },
    };

    const result = await createUserUseCase.execute(req.body, context);

    result.match(
      (user) => res.status(201).json(user.toJSON()),
      (error) => res.status(400).json({ error: error.message }),
    );
  } catch (error) {
    res.status(500).json({ error: "Internal server error" });
  }
});
```

## Domain Patterns

### Entities

Base class for domain entities with built-in audit fields and tenant isolation:

```typescript
interface CustomerProps extends EntityProps {
  name: string;
  email: string;
}

class Customer extends Entity<CustomerProps> {
  // Business logic methods
  updateEmail(newEmail: string, updatedBy: string): void {
    this._props = { ...this._props, email: newEmail };
    this.updateAudit(updatedBy);
  }
}
```

### Value Objects

Immutable objects that represent concepts with validation:

```typescript
class Money extends ValueObject<{ amount: number; currency: string }> {
  protected validate(value: { amount: number; currency: string }): void {
    if (value.amount < 0) {
      throw new ValidationError("Amount cannot be negative");
    }
  }

  add(other: Money): Money {
    // Implementation with currency validation
  }
}
```

### Aggregate Roots

Entities that can raise domain events:

```typescript
class Order extends AggregateRoot<OrderProps> {
  placeOrder(): void {
    // Business logic
    this.addDomainEvent(
      new OrderPlacedEvent({
        aggregateId: this.id,
        orgId: this.orgId,
        // event data
      }),
    );
  }
}
```

## Application Patterns

### Use Cases

Orchestrate domain logic with cross-cutting concerns:

```typescript
class CreateOrderUseCase extends UseCase<CreateOrderRequest, Order> {
  async execute(
    request: CreateOrderRequest,
    context: UseCaseContext,
  ): Promise<Result<Order, DomainError>> {
    // Validate permissions
    const permissionCheck = this.validatePermissions(context, [
      Permission.SALES_WRITE,
    ]);
    if (permissionCheck.isFailure) {
      return Result.failure(permissionCheck.error);
    }

    // Execute business logic
    // ...
  }
}
```

### Commands vs Queries

Separate read and write operations:

```typescript
// Command (writes data)
class CreateCustomerCommand extends BaseCommand<
  CreateCustomerRequest,
  Customer
> {
  async execute(request: CreateCustomerRequest, context: UseCaseContext) {
    // Implementation
  }
}

// Query (reads data)
class GetCustomersQuery extends PaginatedQuery<GetCustomersRequest, Customer> {
  async execute(request: GetCustomersRequest, context: UseCaseContext) {
    // Implementation with pagination
  }
}
```

## Infrastructure Patterns

### Repositories

Data access abstraction with tenant isolation:

```typescript
class FirebaseCustomerRepository implements CustomerRepository {
  async findById(
    id: string,
    options: RepositoryOptions,
  ): Promise<Result<Customer | null>> {
    // Firebase implementation with orgId filtering
  }
}
```

### Event Bus

Decouple domain events from handlers:

```typescript
class OrderPlacedHandler extends BaseEventHandler<OrderPlacedEvent> {
  async handle(event: OrderPlacedEvent): Promise<Result<void>> {
    // Handle the event (send email, update inventory, etc.)
  }

  getEventName(): string {
    return "OrderPlaced";
  }
}
```

## Dependency Injection

Built-in DI container for managing dependencies:

```typescript
import { DIContainer, Injectable } from "@stratly/core";

@Injectable("userRepository")
class FirebaseUserRepository implements UserRepository {
  // Implementation
}

// Setup container
const container = new DIContainer();
container.register({
  identifier: "userRepository",
  implementation: FirebaseUserRepository,
  singleton: true,
});

// Resolve dependencies
const repository = container.resolve<UserRepository>("userRepository");
```

## Multi-Tenant Security

### Tenant Context

Every operation carries tenant information:

```typescript
const context: UseCaseContext = {
  tenant: {
    orgId: "org-123",
    userId: "user-456",
    roles: [Role.MANAGER],
    permissions: [Permission.SALES_READ, Permission.SALES_WRITE],
  },
};
```

### Permission Validation

Built-in permission checking:

```typescript
class SalesUseCase extends UseCase<any, any> {
  async execute(request: any, context: UseCaseContext) {
    // Automatic permission validation
    const permissionCheck = this.validatePermissions(context, [
      Permission.SALES_WRITE,
    ]);
    if (permissionCheck.isFailure) {
      return Result.failure(permissionCheck.error);
    }
    // ... rest of implementation
  }
}
```

## Error Handling

Structured error handling with context:

```typescript
try {
  const result = await useCase.execute(request, context);

  result.match(
    (success) => {
      // Handle success
    },
    (error) => {
      // Handle error with full context
      logger.error("Use case failed", error, {
        orgId: context.tenant.orgId,
        userId: context.tenant.userId,
      });
    },
  );
} catch (error) {
  // Handle unexpected errors
}
```

## Testing

The package provides utilities for testing domain logic:

```typescript
describe("Customer Entity", () => {
  it("should create customer with valid data", () => {
    const customer = new Customer({
      orgId: "org-123",
      name: "John Doe",
      email: "john@example.com",
    });

    expect(customer.name).toBe("John Doe");
    expect(customer.email).toBe("john@example.com");
    expect(customer.orgId).toBe("org-123");
  });
});
```

## 📋 API Reference

### Core Exports

| Export                         | Type           | Description                                      |
| ------------------------------ | -------------- | ------------------------------------------------ |
| `Entity<T>`                    | Abstract Class | Base class for domain entities with audit fields |
| `AggregateRoot<T>`             | Abstract Class | Entity that can raise domain events              |
| `ValueObject<T>`               | Abstract Class | Immutable value objects with validation          |
| `UseCase<TRequest, TResponse>` | Abstract Class | Base class for application use cases             |
| `Repository<T>`                | Interface      | Data access abstraction with tenant isolation    |
| `Result<T, E>`                 | Class          | Railway-oriented programming result type         |
| `DomainError`                  | Class          | Structured error handling for domain operations  |
| `UseCaseContext`               | Interface      | Tenant and user context for operations           |

### Utility Functions

```typescript
// Result helpers
Result.success<T>(value: T): Result<T>
Result.failure<E>(error: E): Result<never, E>

// Validation utilities
validateRequired(value: any, fieldName: string): void
validateEmail(email: string): void
validateId(id: string): void
```

## 🧪 Testing

### Running Tests

```bash
# Install dependencies
npm install

# Run all tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

# Run specific test file
npm test -- user.test.ts
```