TypeScript Quickstart

Build a complete PulseRPC service in TypeScript with our e-commerce checkout example.

Prerequisites

1. Define the Service (2 min)

Create checkout.pulse with your service definition:

namespace checkout

// Enums for order status and payment methods

enum OrderStatus {
    pending
    paid
    shipped
    delivered
    cancelled
}

enum PaymentMethod {
    credit_card
    debit_card
    paypal
    apple_pay
}

// Core domain entities

struct Product {
    productId    string
    name         string
    description  string
    price        float
    stock        int
    imageUrl     string  [optional]
}

struct CartItem {
    productId    string
    quantity     int
    price        float
}

struct Cart {
    cartId       string
    items        []CartItem
    subtotal     float
}

struct Address {
    street       string
    city         string
    state        string
    zipCode      string
    country      string
}

struct Order {
    orderId           string
    cart              Cart
    shippingAddress   Address
    paymentMethod     PaymentMethod
    status            OrderStatus
    total             float
    createdAt         int
}

// Request/Response structures

struct AddToCartRequest {
    cartId       string  [optional]
    productId    string
    quantity     int
}

struct CreateOrderRequest {
    cartId              string
    shippingAddress     Address
    paymentMethod       PaymentMethod
}

struct CheckoutResponse {
    orderId      string
    message      string  [optional]
}

// Error Codes for createOrder:
//   1001 - CartNotFound: Cart doesn't exist
//   1002 - CartEmpty: Cart has no items
//   1003 - PaymentFailed: Payment method rejected
//   1004 - OutOfStock: Insufficient inventory
//   1005 - InvalidAddress: Shipping address validation failed

// Service interfaces

interface CatalogService {
    // Returns a list of all available products
    listProducts() []Product

    // Returns details for a specific product, or null if not found
    getProduct(productId string) Product  [optional]
}

interface CartService {
    // Adds an item to the cart (creates cart if cartId not provided)
    addToCart(request AddToCartRequest) Cart

    // Returns the cart contents, or null if cart doesn't exist
    getCart(cartId string) Cart  [optional]

    // Removes all items from the cart, returns true if successful
    clearCart(cartId string) bool
}

interface OrderService {
    // Converts a cart to an order
    createOrder(request CreateOrderRequest) CheckoutResponse

    // Returns the order details, or null if order doesn't exist
    getOrder(orderId string) Order  [optional]
}

2. Generate Code (1 min)

Generate the TypeScript code from your IDL:

pulserpc -plugin ts-client-server checkout.pulse

This creates:

The IDL is embedded directly in server.ts for the pulserpc-idl RPC method.

3. Implement the Server (10-15 min)

Create my_server.ts that implements your service handlers:

import { PulseRPCServer, CatalogService, CartService, OrderService } from './server';
import { RPCError } from './pulserpc/rpc';

const products = [
  {
    productId: 'prod001',
    name: 'Wireless Mouse',
    description: 'Ergonomic mouse',
    price: 29.99,
    stock: 50,
    imageUrl: 'https://example.com/mouse.jpg'
  },
  {
    productId: 'prod002',
    name: 'Mechanical Keyboard',
    description: 'RGB keyboard',
    price: 89.99,
    stock: 25,
    imageUrl: 'https://example.com/keyboard.jpg'
  }
];

const carts = new Map<string, any>();
const orders = new Map<string, any>();

class CatalogServiceImpl extends CatalogService {
  listProducts(): any[] {
    return products;
  }

  getProduct(productId: string): any | null {
    return products.find((p: any) => p.productId === productId) || null;
  }
}

class CartServiceImpl extends CartService {
  addToCart(request: any): any {
    let cartId = request.cartId || `cart_${Math.floor(Math.random() * 9000 + 1000)}`;

    let cart = carts.get(cartId);
    if (!cart) {
      cart = {
        cartId,
        items: [],
        subtotal: 0
      };
      carts.set(cartId, cart);
    }

    const product = products.find((p: any) => p.productId === request.productId);
    if (!product) {
      throw new RPCError(-32602, 'Product not found');
    }

    cart.items.push({
      productId: request.productId,
      quantity: request.quantity,
      price: product.price
    });

    cart.subtotal = cart.items.reduce((sum: number, item: any) => sum + item.price * item.quantity, 0);
    return cart;
  }

  getCart(cartId: string): any | null {
    return carts.get(cartId) || null;
  }

  clearCart(cartId: string): boolean {
    const cart = carts.get(cartId);
    if (cart) {
      cart.items = [];
      cart.subtotal = 0;
      return true;
    }
    return false;
  }
}

class OrderServiceImpl extends OrderService {
  createOrder(request: any): any {
    const cart = carts.get(request.cartId);
    if (!cart) {
      throw new RPCError(1001, 'CartNotFound: Cart does not exist');
    }

    if (!cart.items || cart.items.length === 0) {
      throw new RPCError(1002, 'CartEmpty: Cannot create order from empty cart');
    }

    const orderId = `order_${Math.floor(Math.random() * 90000 + 10000)}`;
    const order = {
      orderId,
      cart,
      shippingAddress: request.shippingAddress,
      paymentMethod: request.paymentMethod,
      status: 'pending',
      total: cart.subtotal,
      createdAt: Math.floor(Date.now() / 1000)
    };

    orders.set(orderId, order);
    return { orderId, message: 'Order created successfully' };
  }

  getOrder(orderId: string): any | null {
    return orders.get(orderId) || null;
  }
}

const server = new PulseRPCServer('0.0.0.0', 8080);
server.register('CatalogService', new CatalogServiceImpl());
server.register('CartService', new CartServiceImpl());
server.register('OrderService', new OrderServiceImpl());
server.serveForever();

Create a package.json file in the same directory:

{
  "name": "checkout-service",
  "version": "1.0.0",
  "type": "commonjs",
  "scripts": {
    "build": "tsc",
    "start": "node dist/my_server.js"
  },
  "dependencies": {
    "pulserpc-ts-runtime": "file:./pulserpc"
  },
  "devDependencies": {
    "@types/node": "^18.0.0",
    "typescript": "^5.0.0"
  }
}

Create a tsconfig.json file in the same directory:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "lib": ["ES2020"],
    "types": ["node"],
    "moduleResolution": "node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": false,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "."
  },
  "include": ["*.ts"],
  "exclude": ["node_modules", "dist"]
}

Build and start your server:

npm install
npm run build
npm start

4. Implement the Client (5-10 min)

Create my_client.ts to call your service:

import { HTTPTransport, CatalogServiceClient, CartServiceClient, OrderServiceClient } from './client';

const transport = new HTTPTransport('http://localhost:8080');
const catalog = new CatalogServiceClient(transport);
const cart = new CartServiceClient(transport);
const orders = new OrderServiceClient(transport);

async function main() {
  const products = await catalog.listProducts();
  console.log('=== Products ===');
  for (const p of products) {
    console.log(`${p.name} - $${p.price}`);
  }

  const result = await cart.addToCart({
    cartId: null,
    productId: products[0].productId,
    quantity: 2
  });
  console.log(`\nCart: ${result.cartId}`);

  const response = await orders.createOrder({
    cartId: result.cartId,
    shippingAddress: {
      street: '123 Main St',
      city: 'San Francisco',
      state: 'CA',
      zipCode: '94105',
      country: 'USA'
    },
    paymentMethod: 'credit_card'
  });
  console.log(`✓ Order created: ${response.orderId}`);
}

main().catch(console.error);

Build and run your client:

npm run build
node dist/my_client.js

Error Codes

Throw RPCError with custom error codes:

throw new RPCError(1002, 'CartEmpty: Cannot create order from empty cart');
Code Name
1001 CartNotFound
1002 CartEmpty
1003 PaymentFailed
1004 OutOfStock
1005 InvalidAddress

Next Steps