npm package: @rhino-ai/orchard9-coretsx
Description: A streamlined TypeScript library for building robust services and microservices with minimal boilerplate
Author: Rhino API
Latest version: 0.3.16

# @rhino-ai/orchard9-coretsx

A comprehensive TypeScript library for building robust Node.js services with Express, PostgreSQL, Redis, WebSockets, and more.

## Installation

```bash
npm install @rhino-ai/orchard9-coretsx
```

## Features

- **Configuration Management** - Load and validate configuration from YAML files and environment variables
- **Logging** - Structured logging with child loggers and HTTP request tracking
- **Database Operations** - Knex-based database operations with transaction support
- **Express Middleware** - Common Express middleware for security, request parsing, and more
- **Error Handling** - Standardized error handling with custom error classes
- **HTTP Responses** - Standardized HTTP response formatting
- **Validation** - Request validation with Zod schemas
- **Caching** - Redis-based caching with memoization and batch loading
- **WebSockets** - WebSocket server with room management and authentication
- **Event System** - Pub/Sub event system for async communication
- **Testing Utilities** - Mocks for testing database, HTTP, and WebSocket operations

## Usage

### Configuration Management

```typescript
import { loadConfig, getConfigValue } from '@rhino-ai/orchard9-coretsx';

const appConfig = loadConfig({
  files: ['./config/default.yaml', `./config/${process.env.NODE_ENV}.yaml`],
  env: process.env
});

const httpLogs = getConfigValue(appConfig, 'http.logs');
```

### Logging

```typescript
import { logger, createChildLogger } from '@rhino-ai/orchard9-coretsx';

// Standard logging
logger.info({ userId, action: 'login' }, 'User logged in');
logger.error({ err, taskId }, 'Error performing task');

// Child loggers for per-request context
const log = createChildLogger({ requestId: req.id });
log.info('Handling user upload');
```

### Database Operations

```typescript
import { db, transaction } from '@rhino-ai/orchard9-coretsx';

// Simple query
const users = await db('users').where('active', true);

// In-transaction
await transaction(async (trx) => {
  await trx('orders').insert({ /* … */ });
});
```

### Express Middleware

```typescript
import {
  queryParser,
  bodySanitizer,
  responseFormatter,
  pagination,
  validateRequest,
  securityHeaders,
  cors
} from '@rhino-ai/orchard9-coretsx';

app.use(queryParser());
app.use(bodySanitizer());
app.use(validateRequest({ /* schemas… */ }));
app.use(responseFormatter());
app.use(securityHeaders());
app.use(cors({ origin: ['https://example.com'] }));
```

### Error Handling

```typescript
import {
  NotFoundError,
  ValidationError,
  createErrorHandler,
  errorHandler,
  logger
} from '@rhino-ai/orchard9-coretsx';

// Register routes first
app.use('/api/users', userRoutes);
app.use('/api/todos', todoRoutes);

// Then add error handling middleware
app.use((req, res, next) => next(new NotFoundError('Route not found')));
app.use(createErrorHandler({ logger }));

// Alternatively, when using createServer:
// 1. Set registerErrorHandler: false in createServer options
// 2. Register your routes
// 3. Manually add the error handler afterward
app.use(errorHandler);
```

### HTTP Responses

```typescript
import { 
  sendResponse, 
  sendErrorResponse, 
  sendPaginatedResponse 
} from '@rhino-ai/orchard9-coretsx';

// Success response
sendResponse(res, { 
  data: todo, 
  message: 'Todo retrieved successfully' 
});

// Error response
sendErrorResponse(res, 'Todo not found', 404);

// Paginated response
sendPaginatedResponse(res, todos, totalCount, { page, limit, offset });
```

### Validation

```typescript
import { validation, validateRequest } from '@rhino-ai/orchard9-coretsx';
import { z } from 'zod';

const todoSchema = z.object({
  title: z.string().min(1).max(100),
  description: z.string().max(500).optional(),
  completed: z.boolean().default(false)
});

app.post('/todos', 
  validateRequest({ body: todoSchema }),
  (req, res) => {
    // Handle validated request
  }
);
```

### Caching

```typescript
import { cache } from '@rhino-ai/orchard9-coretsx';

// Set a value
await cache.set('todo:123', { id: '123', title: 'Buy groceries' }, { ttl: 300 });

// Get a value
const todo = await cache.get('todo:123');

// Delete a value
await cache.del('todo:123');
```

### WebSockets

```typescript
import { websocket } from '@rhino-ai/orchard9-coretsx';

// Initialize
websocket.initialize(httpServer, { path: '/ws' });

// Handle connections
websocket.onConnection((socket, req, metadata) => {
  console.log(`Client connected: ${metadata.clientId}`);
  
  // Send welcome message
  websocket.send(metadata.clientId, 'welcome', { 
    message: 'Welcome!' 
  });
});

// Handle messages
websocket.onMessage('todo:create', (message, socket, metadata) => {
  // Process message
});
```

#### WebSocket Message Format

Messages sent to and from the WebSocket server follow a standard format:

```typescript
interface WebSocketMessage<T = any> {
  // Message type: 'system', 'user', 'error', 'notification', or 'control'
  type: MessageType;
  
  // Event name that identifies the action or topic
  event: string;
  
  // Message payload containing the data
  payload: T;
  
  // Optional unique message ID for correlation
  id?: string;
  
  // Optional timestamp (defaults to Date.now())
  timestamp?: number;
  
  // Optional message version
  version?: string;
}
```

##### Message Types

- `system` - System-level messages for connection management, heartbeats, and room operations
- `user` - Regular application-specific messages (default if not specified)
- `error` - Error notifications
- `notification` - Notifications that don't require a response
- `control` - Messages that control connection state or behavior

##### Event Names

- For system messages: `ping`, `room:join`, `room:leave`, `room:list`
- For custom messages: Use descriptive names in the format `resource:action` (e.g., `user:update`, `message:send`)

##### Example Client Message

```javascript
// Send a chat message
socket.send(JSON.stringify({
  type: 'user',
  event: 'chat:message',
  payload: {
    text: 'Hello world!',
    roomId: 'room-123'
  }
}));
```

#### WebSocket Error Handling

The WebSocket server provides detailed error information when messages are invalid:

```javascript
// Example error response for invalid JSON
{
  "type": "error",
  "event": "error",
  "payload": {
    "code": "invalid_json",
    "message": "Failed to parse JSON",
    "details": {
      "error": "Unexpected token { in JSON at position 12",
      "data": "{malformed{json"
    }
  },
  "id": "err-123",
  "timestamp": 1619712345678
}
```

Common error codes include:
- `invalid_data_type` - Message data is not a string, Buffer, or ArrayBuffer
- `invalid_json` - Message contains malformed JSON
- `missing_event` - Message is missing the required "event" property
- `event_not_found` - No handlers registered for the specified event
- `handler_error` - Error occurred while processing the message

### Event System

```typescript
import { events } from '@rhino-ai/orchard9-coretsx';

// Publish an event
await events.publish('user:created', {
  id: 'user-123',
  name: 'John Doe'
});

// Subscribe to an event
events.subscribe('user:created', async (payload) => {
  console.log(`New user created: ${payload.name}`);
});
```

### Testing Utilities

```typescript
import {
  createMockDbClient,
  createMockHttpRequest,
  createMockResponse
} from '@rhino-ai/orchard9-coretsx';

// Setup test mocks
const dbMock = createMockDbClient([{ id: 1, name: 'Alice' }]);
const req = createMockHttpRequest({ method: 'GET', url: '/users' });
const res = createMockResponse();

// Test your handler
await listUsers(req, res, () => {});
expect(res.jsonSpy).toHaveBeenCalledWith([{ id: 1, name: 'Alice' }]);
```

## Complete Server Setup

```typescript
import { 
  createServer, 
  initializeDatabase,
  errorHandler,
  logger,
  config
} from '@rhino-ai/orchard9-coretsx';

import routes from './api/routes';

async function bootstrap() {
  try {
    // Initialize database
    initializeDatabase({
      client: 'pg',
      connection: {
        host: config.get('db.host', 'localhost'),
        port: config.get('db.port', 5432),
        user: config.get('db.user', 'postgres'),
        password: config.get('db.password', 'postgres'),
        database: config.get('db.database', 'app')
      }
    });
    
    // Create and start the server
    const { app, server } = await createServer({
      port: config.get('port', 8080),
      host: config.get('host', '0.0.0.0'),
      httpLogs: true,
      autoStart: true,
      registerErrorHandler: false, // Don't register error handler yet
      health: {
        path: '/health',
        includeConnections: true
      }
    });
    
    // Register API routes
    app.use('/api', routes);
    
    // Register error handler after all routes
    app.use(errorHandler);
    
    logger.info({ port: config.get('port', 8080) }, 'Service started');
    
    return { app, server };
  } catch (error) {
    logger.error({ error }, 'Failed to start service');
    process.exit(1);
  }
}

bootstrap();
```

## Best Practices

### Import Order

```typescript
// 1. Built-ins (node, express, etc.)
import { Router } from 'express';

// 2. Third-party (e.g. lodash)
import { z } from 'zod';

// 3. Core library
import { 
  logger, 
  db, 
  sendResponse 
} from '@rhino-ai/orchard9-coretsx';

// 4. Local modules
import { todoService } from './services';
```

### Naming Conventions

- Classes / Types: `PascalCase`
- Functions / Methods: `camelCase`
- Variables: `camelCase`
- Files: `kebab-case.ts`

## License

MIT