ScoutQuest / Documentation / JavaScript/TypeScript SDK

JavaScript/TypeScript SDK

Complete guide to using ScoutQuest in JavaScript and TypeScript applications.

Version: 1.0.0 View on NPM

Installation

npm install scoutquest-js
yarn add scoutquest-js
pnpm add scoutquest-js
bun add scoutquest-js

Quick Start

Get started with ScoutQuest in just a few lines of code:

import { ScoutQuestClient } from 'scoutquest-js';

// Create client
const client = new ScoutQuestClient('http://localhost:8080');

// Register your service
const instance = await client.registerService('my-service', 'localhost', 3000, {
  tags: ['api', 'v1'],
  metadata: {
    version: '1.0.0',
    environment: 'production'
  }
});

console.log(`Service registered with ID: ${instance.id}`);

// Discover other services
const userService = await client.discoverService('user-service');
console.log(`User service found at: ${userService.host}:${userService.port}`);

// Make HTTP calls through ScoutQuest
const users = await client.get('user-service', '/api/users');
console.log('Users:', users.data);

Client Configuration

Basic Configuration

import { ScoutQuestClient } from 'scoutquest-js';

const client = new ScoutQuestClient('http://localhost:8080', {
  timeout: 5000,        // Request timeout in milliseconds
  retries: 3,          // Number of retry attempts
  retryDelay: 1000,    // Delay between retries in milliseconds
  headers: {           // Default headers for all requests
    'User-Agent': 'MyApp/1.0.0'
  }
});

Advanced Configuration

const client = new ScoutQuestClient('http://localhost:8080', {
  // HTTP client configuration
  timeout: 10000,
  retries: 5,
  retryDelay: 2000,

  // Custom retry logic
  retryCondition: (error) => {
    return error.response?.status >= 500 || error.code === 'ECONNRESET';
  },

  // Request/Response interceptors
  requestInterceptor: (config) => {
    config.headers['X-Request-ID'] = generateRequestId();
    return config;
  },

  responseInterceptor: (response) => {
    console.log(`Request to ${response.config.url} took ${response.duration}ms`);
    return response;
  },

  // Error handling
  errorHandler: (error) => {
    console.error('ScoutQuest error:', error);
    // Custom error reporting logic
  }
});

Service Registration

Basic Registration

// Simple registration
const instance = await client.registerService('api-service', 'localhost', 3000);

// With tags and metadata
const instance = await client.registerService('api-service', 'localhost', 3000, {
  tags: ['api', 'v1', 'production'],
  metadata: {
    version: '1.2.3',
    environment: 'production',
    region: 'us-east-1'
  }
});

Health Check Configuration

const instance = await client.registerService('api-service', 'localhost', 3000, {
  tags: ['api', 'v1'],
  healthCheck: {
    path: '/health',           // Health check endpoint
    interval: 30,              // Check interval in seconds
    timeout: 5,                // Timeout for health checks in seconds
    retries: 3,                // Number of failed checks before marking unhealthy
    successThreshold: 2        // Number of successful checks to mark healthy
  }
});

Service Deregistration

// Deregister specific instance
await client.deregisterService(instance.id);

// Deregister all instances of a service
await client.deregisterAllInstances('api-service');

// Graceful shutdown with cleanup
process.on('SIGTERM', async () => {
  console.log('Gracefully shutting down...');
  await client.deregisterService(instance.id);
  process.exit(0);
});

Service Discovery

Basic Discovery

// Discover a service (returns single instance)
const instance = await client.discoverService('user-service');
console.log(`Service at: ${instance.host}:${instance.port}`);

// List all services
const services = await client.listServices();
services.forEach(service => {
  console.log(`Service: ${service.name} - ${service.instanceCount} instances`);
});

// List instances of a specific service
const instances = await client.listServiceInstances('user-service');
instances.forEach(instance => {
  console.log(`Instance: ${instance.id} at ${instance.host}:${instance.port}`);
});

Advanced Discovery with Filtering

// Discover with tag filtering
const instance = await client.discoverService('user-service', {
  tags: ['v1', 'production'],    // Service must have these tags
  excludeTags: ['deprecated'],   // Service must not have these tags
  metadata: {                    // Service metadata filters
    region: 'us-east-1',
    version: '^1.2.0'           // Semver matching
  }
});

// Custom discovery logic
const instance = await client.discoverService('user-service', {
  filter: (instances) => {
    // Custom filtering logic
    return instances.filter(inst =>
      inst.metadata.load < 0.8 &&
      inst.metadata.responseTime < 100
    );
  }
});

HTTP Calls Through Service Discovery

Basic HTTP Methods

// GET request
const users = await client.get('user-service', '/api/users');
console.log(users.data);

// GET with query parameters
const user = await client.get('user-service', '/api/users/:id', {
  params: { id: '123' },
  query: { include: 'profile' }
});

// POST request
const newUser = await client.post('user-service', '/api/users', {
  data: {
    name: 'John Doe',
    email: 'john@example.com'
  }
});

// PUT request
const updatedUser = await client.put('user-service', '/api/users/:id', {
  params: { id: '123' },
  data: { name: 'John Smith' }
});

// DELETE request
await client.delete('user-service', '/api/users/:id', {
  params: { id: '123' }
});

Advanced HTTP Configuration

// Request with custom headers and timeout
const response = await client.get('user-service', '/api/users', {
  headers: {
    'Authorization': 'Bearer ' + token,
    'X-Client-Version': '1.0.0'
  },
  timeout: 30000,
  retries: 5,
  retryDelay: 1000
});

// Request with custom service selection
const response = await client.post('user-service', '/api/users', {
  data: userData,
  serviceOptions: {
    tags: ['v1'],              // Use only v1 instances
    preferLocal: true,         // Prefer instances on same host
    strategy: 'round-robin'    // Load balancing strategy
  }
});

// Raw HTTP request with full control
const response = await client.request('user-service', {
  method: 'POST',
  path: '/api/users',
  data: userData,
  headers: { 'Content-Type': 'application/json' },
  timeout: 15000
});

Error Handling

import {
  ScoutQuestClient,
  ServiceNotFoundError,
  ServiceUnavailableError,
  ScoutQuestError
} from 'scoutquest-js';

try {
  const instance = await client.discoverService('user-service');
  const users = await client.get('user-service', '/api/users');
} catch (error) {
  if (error instanceof ServiceNotFoundError) {
    console.error('Service not found:', error.serviceName);
    // Handle service not found
  } else if (error instanceof ServiceUnavailableError) {
    console.error('Service unavailable:', error.message);
    // Handle service down/unhealthy
  } else if (error instanceof ScoutQuestError) {
    console.error('ScoutQuest error:', error.message);
    // Handle other ScoutQuest-specific errors
  } else {
    console.error('Unexpected error:', error);
    // Handle other errors
  }
}

Custom Error Handler

const client = new ScoutQuestClient('http://localhost:8080', {
  errorHandler: (error, context) => {
    // Log error details
    console.error(`Error in ${context.operation}:`, {
      error: error.message,
      service: context.serviceName,
      endpoint: context.endpoint,
      timestamp: new Date().toISOString()
    });

    // Send to monitoring system
    monitoring.recordError(error, context);

    // Custom retry logic
    if (error.code === 'ECONNRESET' && context.retryCount < 3) {
      return { shouldRetry: true, delay: 2000 };
    }

    return { shouldRetry: false };
  }
});

TypeScript Support

ScoutQuest provides full TypeScript support with comprehensive type definitions:

Type Definitions

import {
  ScoutQuestClient,
  ServiceInstance,
  ServiceRegistration,
  ServiceDiscoveryOptions,
  HttpRequestOptions
} from 'scoutquest-js';

// Custom service interface
interface UserService {
  id: string;
  name: string;
  email: string;
}

// Typed HTTP calls
const users = await client.get<UserService[]>('user-service', '/api/users');
const user = await client.post<UserService>('user-service', '/api/users', {
  data: { name: 'John', email: 'john@example.com' }
});

// Typed service registration
const registration: ServiceRegistration = {
  tags: ['api', 'v1'],
  metadata: {
    version: '1.0.0',
    capabilities: ['users', 'auth']
  },
  healthCheck: {
    path: '/health',
    interval: 30
  }
};

const instance = await client.registerService('api-service', 'localhost', 3000, registration);

Custom Type Guards

import { ServiceInstance } from 'scoutquest-js';

// Type guard for service capabilities
function hasCapability(instance: ServiceInstance, capability: string): boolean {
  return instance.metadata?.capabilities?.includes(capability) ?? false;
}

// Usage
const instance = await client.discoverService('user-service');
if (hasCapability(instance, 'auth')) {
  const token = await client.post<{token: string}>('user-service', '/auth/login', {
    data: { username, password }
  });
}

Framework Integration

Express.js Integration

import express from 'express';
import { ScoutQuestClient } from 'scoutquest-js';

const app = express();
const client = new ScoutQuestClient('http://localhost:8080');

// Middleware for service discovery
app.use((req, res, next) => {
  req.scoutquest = client;
  next();
});

// Register service on startup
let serviceInstance;
app.listen(3000, async () => {
  serviceInstance = await client.registerService('api-service', 'localhost', 3000, {
    tags: ['api', 'express'],
    healthCheck: { path: '/health' }
  });
  console.log('Server started and registered with ScoutQuest');
});

// Route that calls another service
app.get('/users', async (req, res) => {
  try {
    const users = await req.scoutquest.get('user-service', '/api/users');
    res.json(users.data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch users' });
  }
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  if (serviceInstance) {
    await client.deregisterService(serviceInstance.id);
  }
  process.exit(0);
});

Next.js Integration

// lib/scoutquest.ts
import { ScoutQuestClient } from 'scoutquest-js';

let client: ScoutQuestClient;

if (typeof window === 'undefined') {
  // Server-side only
  client = new ScoutQuestClient(process.env.SCOUTQUEST_URL!);
}

export { client };

// pages/api/users.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { client } from '../../lib/scoutquest';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  try {
    const users = await client.get('user-service', '/api/users');
    res.status(200).json(users.data);
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch users' });
  }
}

Best Practices

Service Registration

  • Always register your service on startup
  • Include meaningful tags for service discovery
  • Implement proper health check endpoints
  • Deregister on graceful shutdown

Error Handling

  • Always wrap service calls in try-catch blocks
  • Implement retry logic for transient failures
  • Use circuit breaker patterns for downstream services
  • Log errors with sufficient context for debugging

Performance

  • Reuse ScoutQuestClient instances
  • Configure appropriate timeouts and retries
  • Use connection pooling for high-throughput scenarios
  • Cache service discovery results when appropriate

Examples

Check out these complete examples to see ScoutQuest in action: