# @digilogiclabs/platform-core

> Vendor-agnostic infrastructure abstraction layer for building portable, enterprise-grade applications.

[![Tests](https://img.shields.io/badge/tests-1270%2B%20passing-brightgreen)](./tests)
[![Adapters](https://img.shields.io/badge/adapters-50%2B-blue)](./src/adapters)
[![Interfaces](https://img.shields.io/badge/interfaces-34-blue)](./src/interfaces)

## Overview

Platform Core provides a unified API for common infrastructure services, allowing you to swap providers without changing application code. Build once, deploy anywhere.

## Features

### Core Infrastructure

- **Database** - Query builder abstraction (PostgreSQL, Supabase, Memory)
- **Cache** - Key-value caching (Redis, Upstash, Memory)
- **Storage** - File storage (S3/MinIO/R2, Supabase Storage, Memory)
- **Email** - Transactional email (SMTP, Resend, Console, Memory)
- **Queue** - Background jobs (BullMQ, Memory)
- **Tracing** - Distributed tracing (OpenTelemetry, Memory, Noop)
- **Health Checks** - Built-in health monitoring for all services

### Enterprise Patterns

- **Middleware** - Composable before/after hooks for all operations
- **Hooks** - Lifecycle events for database, cache, email, queue operations
- **Resilience** - Retry, circuit breaker, timeout, bulkhead, fallback patterns
- **Metrics** - Counters, gauges, histograms, timings with tag support
- **Security** - HTML escaping, URL detection, content sanitization for emails
- **API Utilities** - Structured errors, error classification, pagination helpers

## Installation

```bash
npm install @digilogiclabs/platform-core
# or
pnpm add @digilogiclabs/platform-core
```

### Optional Peer Dependencies

Install only the providers you need:

```bash
# For Supabase database
pnpm add @supabase/supabase-js

# For Upstash cache
pnpm add @upstash/redis

# For S3 storage
pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner

# For Resend email
pnpm add resend
```

## Quick Start

### Development (Memory Adapters)

```typescript
import { createPlatform } from "@digilogiclabs/platform-core";

// Uses memory adapters by default - no external services needed
const platform = createPlatform();

// Use the platform
const users = await platform.db
  .from("users")
  .where("active", "=", true)
  .execute();
await platform.cache.set("user:123", users.data[0], 3600);
await platform.email.send({
  to: "user@example.com",
  subject: "Welcome!",
  text: "Hello from Platform Core",
});
```

### Production (Real Adapters)

```typescript
import { createPlatformAsync } from "@digilogiclabs/platform-core";

// Use environment variables to configure providers
const platform = await createPlatformAsync();

// Or configure explicitly
const platform = await createPlatformAsync({
  database: { provider: "supabase" },
  cache: { provider: "upstash" },
  storage: { provider: "s3" },
  email: { provider: "resend" },
});
```

## Environment Variables

```bash
# Provider selection
PLATFORM_DB_PROVIDER=memory|supabase
PLATFORM_CACHE_PROVIDER=memory|upstash
PLATFORM_STORAGE_PROVIDER=memory|s3|minio|r2
PLATFORM_EMAIL_PROVIDER=memory|console|resend
PLATFORM_QUEUE_PROVIDER=memory

# Supabase
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

# Upstash
UPSTASH_REDIS_REST_URL=https://your-redis.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token

# S3 / MinIO / R2
S3_ENDPOINT=https://s3.amazonaws.com
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=your-key
S3_SECRET_ACCESS_KEY=your-secret
S3_BUCKET=your-bucket
S3_FORCE_PATH_STYLE=false

# Resend
RESEND_API_KEY=re_your_api_key
EMAIL_FROM=noreply@yourdomain.com
```

## API Reference

### Platform

```typescript
interface IPlatform {
  db: IDatabase;
  cache: ICache;
  storage: IStorage;
  email: IEmail;
  queue: IQueue;

  healthCheck(): Promise<PlatformHealthStatus>;
  close(): Promise<void>;
}
```

### Database

```typescript
// Query builder
const users = await platform.db
  .from<User>("users")
  .where("active", "=", true)
  .orderBy("created_at", "desc")
  .limit(10)
  .execute();

// Insert
await platform.db
  .from<User>("users")
  .insert({ name: "John", email: "john@example.com" })
  .execute();

// Update
await platform.db
  .from<User>("users")
  .where("id", "=", "123")
  .update({ name: "Jane" })
  .execute();

// Delete
await platform.db
  .from<User>("users")
  .where("id", "=", "123")
  .delete()
  .execute();
```

### Cache

```typescript
// Basic operations
await platform.cache.set("key", value, 3600); // TTL in seconds
const value = await platform.cache.get<User>("key");
await platform.cache.delete("key");

// Batch operations
const values = await platform.cache.mget(["key1", "key2", "key3"]);
await platform.cache.mset([
  { key: "a", value: 1 },
  { key: "b", value: 2, ttl: 60 },
]);

// Increment/Decrement
const count = await platform.cache.incr("counter");

// Pattern delete
await platform.cache.deletePattern("user:*");
```

### Storage

```typescript
// Upload
const file = await platform.storage.upload("path/to/file.txt", buffer, {
  contentType: "text/plain",
});

// Download
const data = await platform.storage.download("path/to/file.txt");

// Get signed URL
const url = await platform.storage.getSignedUrl("path/to/file.txt", 3600);

// Delete
await platform.storage.delete("path/to/file.txt");

// Check existence
const exists = await platform.storage.exists("path/to/file.txt");
```

### Email

```typescript
// Send email
const result = await platform.email.send({
  to: "user@example.com",
  from: "noreply@yourdomain.com",
  subject: "Hello",
  text: "Plain text body",
  html: "<h1>HTML body</h1>",
});

// Batch send
const results = await platform.email.sendBatch([
  { to: "a@example.com", subject: "A", text: "A" },
  { to: "b@example.com", subject: "B", text: "B" },
]);
```

### Queue

```typescript
// Add job
const job = await platform.queue.add("sendEmail", {
  userId: "123",
  template: "welcome",
});

// Process jobs
platform.queue.process(async (job) => {
  console.log("Processing:", job.name, job.data);
  return { success: true };
});

// Get job status
const job = await platform.queue.getJob("job_id");
```

### Health Checks

```typescript
const health = await platform.healthCheck();

console.log(health);
// {
//   healthy: true,
//   services: {
//     database: true,
//     cache: true,
//     storage: true,
//     email: true,
//     queue: true,
//   },
//   timestamp: 1701234567890,
// }
```

## Testing

Use memory adapters for fast, isolated tests:

```typescript
import {
  createPlatform,
  MemoryDatabase,
  MemoryCache,
} from "@digilogiclabs/platform-core";

describe("MyService", () => {
  const platform = createPlatform(); // Uses memory adapters

  beforeEach(async () => {
    // Reset state between tests
    await platform.close();
  });

  it("should create user", async () => {
    const result = await platform.db
      .from("users")
      .insert({ name: "Test" })
      .execute();

    expect(result.data).toHaveLength(1);
  });
});
```

## Direct Adapter Usage

You can also use adapters directly:

```typescript
import {
  MemoryDatabase,
  MemoryCache,
  SupabaseDatabase,
  UpstashCache,
  S3Storage,
  ResendEmail,
  ConsoleEmail,
} from "@digilogiclabs/platform-core";

// Create adapters directly
const db = new MemoryDatabase();
const cache = new MemoryCache();
const email = new ConsoleEmail(); // Logs to console
```

## Migration from Direct Provider Usage

### Before (Direct Supabase)

```typescript
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(url, key);
const { data } = await supabase.from("users").select("*").eq("active", true);
```

### After (Platform Core)

```typescript
import { createPlatformAsync } from "@digilogiclabs/platform-core";

const platform = await createPlatformAsync();
const result = await platform.db
  .from("users")
  .where("active", "=", true)
  .execute();
```

## Enterprise Patterns

### Middleware

Compose middleware for cross-cutting concerns:

```typescript
import {
  createMiddlewareChain,
  createLoggingMiddleware,
  createMetricsMiddleware,
} from "@digilogiclabs/platform-core";

const chain = createMiddlewareChain()
  .use(createLoggingMiddleware(logger))
  .use(createMetricsMiddleware(metrics))
  .use({
    name: "custom",
    before: (ctx) => console.log("Before:", ctx.operation),
    after: (ctx) => console.log("After:", ctx.operation, ctx.duration + "ms"),
    onError: (ctx, error) => console.error("Error:", error),
  });

// Execute with middleware
const result = await chain.execute(
  {
    service: "database",
    operation: "query",
    args: { table: "users" },
    logger,
    startTime: Date.now(),
    correlationId: "abc",
  },
  async () => db.from("users").execute(),
);
```

### Hooks

Register lifecycle hooks for platform events:

```typescript
import { createHookRegistry } from "@digilogiclabs/platform-core";

const hooks = createHookRegistry(logger);

hooks.register({
  onReady: () => console.log("Platform ready"),
  onShutdown: () => console.log("Shutting down"),
  beforeQuery: ({ table, operation }) =>
    console.log(`Query: ${operation} on ${table}`),
  afterQuery: ({ table, duration }) =>
    console.log(`Query completed in ${duration}ms`),
  onCacheHit: (key, value) => console.log(`Cache hit: ${key}`),
  onCacheMiss: (key) => console.log(`Cache miss: ${key}`),
  onError: ({ service, operation, error }) =>
    console.error(`Error in ${service}.${operation}:`, error),
});

// Execute hooks
await hooks.execute("onReady");
await hooks.execute("beforeQuery", { table: "users", operation: "select" });
```

### Resilience Patterns

#### Retry with Exponential Backoff

```typescript
import {
  withRetry,
  RetryConfigs,
  RetryPredicates,
} from "@digilogiclabs/platform-core";

const result = await withRetry(() => fetchData(), {
  maxAttempts: 3,
  baseDelay: 100,
  maxDelay: 5000,
  backoffMultiplier: 2,
  shouldRetry: RetryPredicates.networkErrors,
});

// Or use presets
const result = await withRetry(() => fetchData(), RetryConfigs.standard);
```

#### Circuit Breaker

```typescript
import {
  CircuitBreaker,
  CircuitBreakerRegistry,
} from "@digilogiclabs/platform-core";

const breaker = new CircuitBreaker({
  failureThreshold: 5,
  resetTimeout: 30000,
  halfOpenRequests: 3,
});

const result = await breaker.execute(() => externalApiCall());

// Or use a registry for named breakers
const registry = new CircuitBreakerRegistry();
const apiBreaker = registry.get("external-api");
```

#### Timeout

```typescript
import { withTimeout, TimeoutError } from "@digilogiclabs/platform-core";

try {
  const result = await withTimeout(() => slowOperation(), 5000);
} catch (error) {
  if (error instanceof TimeoutError) {
    console.log("Operation timed out");
  }
}
```

#### Bulkhead (Concurrency Isolation)

```typescript
import { Bulkhead } from "@digilogiclabs/platform-core";

const bulkhead = new Bulkhead({
  maxConcurrent: 10,
  maxQueued: 100,
  timeout: 30000,
});

const result = await bulkhead.execute(() => cpuIntensiveTask());
```

#### Fallback

```typescript
import { withFallback, FallbackStrategies } from "@digilogiclabs/platform-core";

// Static fallback
const result = await withFallback(
  () => fetchFromApi(),
  FallbackStrategies.value({ cached: true, data: [] }),
);

// Dynamic fallback
const result = await withFallback(
  () => fetchFromPrimary(),
  FallbackStrategies.execute(() => fetchFromSecondary()),
);
```

### Metrics

Track application metrics with a provider-agnostic interface:

```typescript
import {
  MemoryMetrics,
  createScopedMetrics,
} from "@digilogiclabs/platform-core";

const metrics = new MemoryMetrics();

// Counter
metrics.increment("api.requests", 1, { method: "GET", path: "/users" });
metrics.decrement("active.connections");

// Gauge
metrics.gauge("queue.size", 42, { queue: "emails" });

// Histogram
metrics.histogram("response.size", 1024, { endpoint: "/api/users" });

// Timer
const stopTimer = metrics.startTimer("api.request.duration", {
  method: "POST",
});
// ... do work ...
stopTimer(); // Records duration

// Timing
metrics.timing("db.query.duration", 42, { table: "users" });

// Scoped metrics with prefix
const dbMetrics = createScopedMetrics(metrics, "database", {
  service: "users",
});
dbMetrics.timing("query", 50); // Records as 'database.query' with service=users tag

// Get summary (for testing)
const summary = metrics.getSummary();
console.log(summary.counters, summary.timings);
```

## Security Utilities

Helpers for safe rendering of user content in emails and HTML output:

```typescript
import {
  escapeHtml,
  containsUrls,
  containsHtml,
  stripHtml,
  defangUrl,
  sanitizeForEmail,
} from "@digilogiclabs/platform-core";

// Escape HTML special characters
escapeHtml('<script>alert("xss")</script>');
// → '&lt;script&gt;alert(&quot;xss&quot;)&lt;/script&gt;'

// Detect URLs or HTML in user input
containsUrls("visit https://evil.com"); // true
containsUrls("visit evil.com"); // true (bare domain detection)
containsHtml("<b>bold</b>"); // true

// Strip HTML tags
stripHtml("<p>Hello <b>world</b></p>"); // "Hello world"

// Defang URLs to prevent auto-linking in email clients
defangUrl("https://evil.com/path"); // "hxxps://evil[com]/path"

// Sanitize user content for HTML email templates
sanitizeForEmail(userInput); // Escapes all HTML entities
```

## API Utilities

Framework-agnostic error handling and response helpers:

```typescript
import {
  ApiError,
  ApiErrorCode,
  CommonApiErrors,
  classifyError,
  buildPagination,
  isApiError,
} from "@digilogiclabs/platform-core";
import type {
  ApiSuccessResponse,
  ApiPaginatedResponse,
} from "@digilogiclabs/platform-core";

// Pre-built error factories
throw CommonApiErrors.notFound("User"); // 404
throw CommonApiErrors.unauthorized(); // 401
throw CommonApiErrors.forbidden(); // 403
throw CommonApiErrors.validationError(details); // 400
throw CommonApiErrors.rateLimitExceeded(); // 429

// Custom API errors
throw new ApiError(422, "Invalid input", ApiErrorCode.VALIDATION_ERROR, {
  field: "email",
  message: "Invalid format",
});

// Classify any error into { status, body }
// Handles: ApiError, Zod validation errors, PostgreSQL errors, generic errors
const { status, body } = classifyError(error, isDev);
// → { status: 409, body: { error: "Resource already exists", code: "CONFLICT" } }

// Pagination helper
const pagination = buildPagination(1, 20, 100);
// → { page: 1, limit: 20, total: 100, totalPages: 5, hasMore: true }
```

## Local Development

Start the development infrastructure:

```bash
# Start core services (PostgreSQL, Redis, MinIO, MailHog)
./infrastructure/scripts/dev.sh start

# Start with observability (adds Prometheus, Grafana)
./infrastructure/scripts/dev.sh start:obs

# Start with dev tools (adds PgAdmin, RedisInsight)
./infrastructure/scripts/dev.sh start:tools

# Check service health
./infrastructure/scripts/dev.sh health

# Connect to databases
./infrastructure/scripts/dev.sh psql
./infrastructure/scripts/dev.sh redis-cli
```

Service URLs:

- PostgreSQL: `localhost:5432` (user: dll, pass: development)
- Redis: `localhost:6379` (pass: development)
- MinIO: `localhost:9000` (console: 9001)
- MailHog: `localhost:8025` (SMTP: 1025)
- Prometheus: `localhost:9090` (with observability profile)
- Grafana: `localhost:3001` (with observability profile)

## License

MIT
