Skip to main content
The @repo/types package provides shared TypeScript type definitions that ensure type safety and consistency across all applications in the monorepo.

Installation

This package is internal to the monorepo and installed automatically:
"dependencies": {
  "@repo/types": "workspace:*"
}

Features

  • Type Safety: Shared types prevent inconsistencies across services
  • Zero Runtime: Pure TypeScript types with no runtime overhead
  • Centralized Definitions: Single source of truth for data structures
  • IDE Support: Full IntelliSense and autocomplete support

Available Types

PriceUpdate

Represents a real-time price update from market data feeds.
export type PriceUpdate = {
  asset: string;      // Trading pair symbol (e.g., "BTCUSDT")
  price: number;      // Current market price
  bidValue: number;   // Best bid price
  askValue: number;   // Best ask price
  decimal: number;    // Decimal precision for the asset
};

Usage Examples

Importing Types

import { PriceUpdate } from '@repo/types';

// Use in function signatures
function handlePriceUpdate(update: PriceUpdate): void {
  console.log(`${update.asset}: $${update.price}`);
  console.log(`Spread: ${update.askValue - update.bidValue}`);
}

WebSocket Price Updates

import { PriceUpdate } from '@repo/types';
import WebSocket from 'ws';

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
  // Type-safe price update
  const priceUpdate: PriceUpdate = {
    asset: 'BTCUSDT',
    price: 50000.00,
    bidValue: 49999.50,
    askValue: 50000.50,
    decimal: 2
  };
  
  ws.send(JSON.stringify(priceUpdate));
});

Redis Pub/Sub with Types

import { PriceUpdate } from '@repo/types';
import { pubsubClient, config, constant } from '@repo/config';

const pubsub = pubsubClient(config.REDIS_URL);
await pubsub.connect();

// Publisher
const publishPrice = async (update: PriceUpdate) => {
  await pubsub.publish(
    constant.pubsubKey,
    JSON.stringify(update)
  );
};

// Subscriber
await pubsub.subscriber(constant.pubsubKey, (data) => {
  // Type assertion for runtime data
  const update = data as PriceUpdate;
  
  console.log(`Price update for ${update.asset}:`);
  console.log(`  Price: $${update.price.toFixed(update.decimal)}`);
  console.log(`  Bid: $${update.bidValue}`);
  console.log(`  Ask: $${update.askValue}`);
});

React Components

import { PriceUpdate } from '@repo/types';
import { useState, useEffect } from 'react';

interface PriceTickerProps {
  symbol: string;
}

export function PriceTicker({ symbol }: PriceTickerProps) {
  const [price, setPrice] = useState<PriceUpdate | null>(null);
  
  useEffect(() => {
    const ws = new WebSocket(`ws://api.example.com/prices/${symbol}`);
    
    ws.onmessage = (event) => {
      const update: PriceUpdate = JSON.parse(event.data);
      setPrice(update);
    };
    
    return () => ws.close();
  }, [symbol]);
  
  if (!price) return <div>Loading...</div>;
  
  return (
    <div>
      <h2>{price.asset}</h2>
      <p className="price">${price.price.toFixed(price.decimal)}</p>
      <div className="spread">
        <span>Bid: ${price.bidValue}</span>
        <span>Ask: ${price.askValue}</span>
      </div>
    </div>
  );
}

API Routes (Next.js)

import { PriceUpdate } from '@repo/types';
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const symbol = request.nextUrl.searchParams.get('symbol');
  
  // Fetch from market data provider
  const marketData = await fetchMarketData(symbol);
  
  // Transform to PriceUpdate type
  const priceUpdate: PriceUpdate = {
    asset: marketData.symbol,
    price: marketData.lastPrice,
    bidValue: marketData.bestBid,
    askValue: marketData.bestAsk,
    decimal: getDecimalPrecision(symbol)
  };
  
  return NextResponse.json(priceUpdate);
}

Backend Services

import { PriceUpdate } from '@repo/types';
import { redisStreams, config, constant } from '@repo/config';

class PriceService {
  private streams = redisStreams(config.REDIS_URL);
  
  async initialize() {
    await this.streams.connect();
  }
  
  async processPriceUpdate(rawData: any): Promise<void> {
    // Validate and transform to PriceUpdate
    const update: PriceUpdate = {
      asset: rawData.s,  // Symbol from Binance
      price: parseFloat(rawData.p),
      bidValue: parseFloat(rawData.b),
      askValue: parseFloat(rawData.a),
      decimal: this.getDecimalPlaces(rawData.s)
    };
    
    // Store in Redis stream
    await this.streams.addToRedisStream(
      constant.redisStream,
      update
    );
    
    // Broadcast to connected clients
    this.broadcastToClients(update);
  }
  
  private getDecimalPlaces(symbol: string): number {
    const decimals: Record<string, number> = {
      'BTCUSDT': 2,
      'ETHUSDT': 2,
      'SOLUSDT': 3
    };
    return decimals[symbol] || 2;
  }
  
  private broadcastToClients(update: PriceUpdate): void {
    // WebSocket broadcast implementation
  }
}

Type Validation

Runtime Validation with Zod

While the types package doesn’t include runtime validation, you can combine it with Zod:
import { PriceUpdate } from '@repo/types';
import { z } from 'zod';

// Create Zod schema matching the type
const PriceUpdateSchema = z.object({
  asset: z.string(),
  price: z.number().positive(),
  bidValue: z.number().positive(),
  askValue: z.number().positive(),
  decimal: z.number().int().min(0).max(8)
}) satisfies z.ZodType<PriceUpdate>;

// Validate runtime data
function validatePriceUpdate(data: unknown): PriceUpdate {
  return PriceUpdateSchema.parse(data);
}

// Usage
try {
  const update = validatePriceUpdate(incomingData);
  // TypeScript knows update is PriceUpdate
  processPriceUpdate(update);
} catch (error) {
  console.error('Invalid price update:', error);
}

Type Guards

import { PriceUpdate } from '@repo/types';

function isPriceUpdate(data: any): data is PriceUpdate {
  return (
    typeof data === 'object' &&
    typeof data.asset === 'string' &&
    typeof data.price === 'number' &&
    typeof data.bidValue === 'number' &&
    typeof data.askValue === 'number' &&
    typeof data.decimal === 'number'
  );
}

// Usage
const data = JSON.parse(message);
if (isPriceUpdate(data)) {
  // TypeScript knows data is PriceUpdate
  console.log(`${data.asset}: $${data.price}`);
} else {
  console.error('Invalid data format');
}

Best Practices

Never duplicate type definitions across applications. Always import from the shared types package:
// Good
import { PriceUpdate } from '@repo/types';

// Bad - duplicating the type
type PriceUpdate = { asset: string; price: number; ... };
Define shared types for API request/response bodies to ensure consistency between frontend and backend:
// Add to @repo/types
export type PlaceOrderRequest = {
  symbol: string;
  side: 'buy' | 'sell';
  quantity: number;
  price: number;
};

// Use in both frontend and backend
Document types with JSDoc for better IDE support:
/**
 * Represents a real-time price update from market data feeds.
 * Includes bid/ask spread and decimal precision.
 */
export type PriceUpdate = {
  /** Trading pair symbol (e.g., "BTCUSDT") */
  asset: string;
  // ... rest of type
};
Each type should represent a single concept. Create separate types for different use cases:
// Good - focused types
type PriceUpdate = { ... };
type OrderRequest = { ... };
type TradeHistory = { ... };

// Avoid - kitchen sink type
type Everything = { prices: ..., orders: ..., trades: ... };

Extending Types

Creating Derived Types

import { PriceUpdate } from '@repo/types';

// Extend with additional properties
type EnrichedPriceUpdate = PriceUpdate & {
  timestamp: Date;
  source: 'binance' | 'coinbase';
  volumeUSD: number;
};

// Pick specific properties
type SimplePriceUpdate = Pick<PriceUpdate, 'asset' | 'price'>;

// Make properties optional
type PartialPriceUpdate = Partial<PriceUpdate>;

Union Types

import { PriceUpdate } from '@repo/types';

type WebSocketMessage =
  | { type: 'price'; data: PriceUpdate }
  | { type: 'trade'; data: TradeData }
  | { type: 'orderbook'; data: OrderBookData };

function handleMessage(msg: WebSocketMessage) {
  switch (msg.type) {
    case 'price':
      // TypeScript knows msg.data is PriceUpdate
      handlePriceUpdate(msg.data);
      break;
    // ... other cases
  }
}

Testing with Types

import { PriceUpdate } from '@repo/types';
import { describe, test, expect } from 'bun:test';

describe('Price Update Processing', () => {
  test('processes valid price update', () => {
    const update: PriceUpdate = {
      asset: 'BTCUSDT',
      price: 50000,
      bidValue: 49999.50,
      askValue: 50000.50,
      decimal: 2
    };
    
    const result = processPriceUpdate(update);
    expect(result).toBeDefined();
  });
  
  test('calculates spread correctly', () => {
    const update: PriceUpdate = {
      asset: 'ETHUSDT',
      price: 3000,
      bidValue: 2999.90,
      askValue: 3000.10,
      decimal: 2
    };
    
    const spread = calculateSpread(update);
    expect(spread).toBe(0.20);
  });
});

@repo/config

Uses types for configuration validation

@repo/db

Prisma generates database types

@repo/utils

May use shared types for utility functions

Build docs developers (and LLMs) love