@rhino-ai/orchard9-coretsx
TypeScript icon, indicating that this package has built-in type declarations

0.3.16 • Public • Published

@rhino-ai/orchard9-coretsx

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

Installation

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

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

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

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

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

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

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

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

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

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:

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
// 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:

// 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

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

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

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

// 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

Package Sidebar

Install

npm i @rhino-ai/orchard9-coretsx

Weekly Downloads

9

Version

0.3.16

License

MIT

Unpacked Size

3.06 MB

Total Files

65

Last publish

Collaborators

  • jx12n