Case file

CASE FILE · SIM-01

Introduction

Behaviors and Dispatch — Developers

Audience: Engineers defining endpoint behaviors and calling dispatch.
Related: Overview · Adapters


Case file

CASE FILE · SIM-01

1. Fixed

Use fixed behavior for health endpoints, stable metadata, constants, and simple fixtures.

{
  id: 'capabilities',
  method: 'GET',
  path: '/capabilities',
  behavior: {
    type: 'fixed',
    response: {
      status: 200,
      headers: { 'x-simulator': 'true' },
      body: { features: ['read', 'search'] }
    }
  }
}

Case file

CASE FILE · SIM-01

2. Relative

Relative behavior starts with configured JSON, then resolves tokens against the matched request and the API's internal data object.

Supported token roots:

  • {{request.params.parentId}}
  • {{request.query.page}}
  • {{request.headers.x-tenant-id}}
  • {{request.body.customer.id}}
  • {{data.children}}
  • Array indexes, such as {{data.users[0].id}}
{
  id: 'children',
  method: 'GET',
  path: '/parents/:parentId/children',
  behavior: {
    type: 'relative',
    response: {
      body: {
        parentId: '{{request.params.parentId}}',
        items: '{{data.children}}'
      }
    }
  }
}

A token occupying the entire string preserves its original type. Therefore, {{request.body.limit}} can produce a number and {{data.children}} can produce an array. A token embedded inside text is converted to a string:

{ message: 'Children for parent {{request.params.parentId}}' }

Tokens inside API-local data are rendered too. This lets a reusable internal fixture inherit an identifier from the incoming request:

data: {
  children: [
    { id: 'child-1', parentId: '{{request.params.parentId}}' }
  ]
}

Relative behavior is strict by default. A missing token throws TemplateResolutionError. Set strict: false to leave unresolved tokens unchanged.


Case file

CASE FILE · SIM-01

3. Simulation function

Use a simulation function when output needs conditions, calculations, validation, generated values, branching, or error responses.

{
  id: 'quote',
  method: 'POST',
  path: '/quote',
  behavior: {
    type: 'simulation',
    handler: async ({ request, data, apiId, endpointId }) => {
      const quantity = Number((request.body as { quantity: number }).quantity);
      const unitPrice = Number(data.unitPrice);

      return {
        status: 201,
        body: {
          apiId,
          endpointId,
          quantity,
          unitPrice,
          total: quantity * unitPrice
        }
      };
    }
  }
}

Case file

CASE FILE · SIM-01

4. Dispatch

const result = await simulator.dispatch({
  method: 'GET',
  path: '/api/v1/parents/parent-42/children'
});

console.log(result.response.body);

dispatch() throws EndpointNotFoundError when nothing matches. tryDispatch() returns undefined instead.


Case file

CASE FILE · SIM-01

5. Endpoint contract

type EndpointDefinition = {
  id: string;
  method: HttpMethod;
  path: string;
  enabled?: boolean;
  delayMs?: number;
  behavior:
    | { type: 'fixed'; response: SimulatorResponse }
    | { type: 'relative'; response: SimulatorResponse; strict?: boolean }
    | { type: 'simulation'; handler: SimulationFunction };
};

The discriminated behavior union is the core guarantee: an endpoint cannot validly have two behaviors, and it cannot omit its behavior.


Case file

CASE FILE · SIM-01

6. Artificial latency

{
  id: 'slow-search',
  method: 'GET',
  path: '/search',
  delayMs: 750,
  behavior: {
    type: 'fixed',
    response: { body: { items: [] } }
  }
}

Case file

CASE FILE · SIM-01

7. Raw responses

import { RawResponseBody, createApiSimulator } from '@x12i/api-simulator';

// In a simulation handler:
return {
  body: new RawResponseBody('<h1>hello</h1>', 'text/html; charset=utf-8')
};

Transport adapters write RawResponseBody verbatim instead of JSON.stringify.


Case file

CASE FILE · SIM-01

8. Path patterns

Supports :id, :id(\\d+), and optional :slug? segments. Static routes are matched before parameter routes, so /users/me wins over /users/:id.