Case file

CASE FILE · SIM-02

Introduction

Adapters and Helpers — Developers

Audience: Engineers exposing simulators over HTTP and composing helpers.
Related: Behaviors · Package simulators


Case file

CASE FILE · SIM-02

1. Node HTTP adapter

import { createServer } from 'node:http';
import { createNodeHttpHandler } from '@x12i/api-simulator/node';

createServer(createNodeHttpHandler(simulator)).listen(5520);

The adapter:

  • parses URL path and query values;
  • normalizes request headers to lowercase keys (values may be string | string[]);
  • parses JSON, urlencoded, and text bodies by default; returns Buffer for binary;
  • returns 404 when no simulated endpoint matches;
  • returns 500 when rendering or simulation fails;
  • limits request bodies to 1 MiB by default;
  • writes RawResponseBody verbatim (HTML/XML/CSV/binary).

The ./node adapter is transport-only. It does not bind ports, serve /_live, or print a compliance banner. Processes that listen on HTTP should wrap it with @x12i/core-service (next section).


Case file

CASE FILE · SIM-02

2. HTTP process compliance (local services)

Any example or package simulator that binds an HTTP port should use @x12i/core-service (pulls @x12i/api-live-view; uses @x12i/ports-manager).

Product contract (current @x12i/memorix-docs, not old memorix-ebooks):

  • Use case http-process-compliance
  • Building Services (developers) → HTTP process compliance (local services)
  • Deep reference: package READMEs for core-service, ports-manager, api-live-view

This repo applies that contract in zone api-simulator (5520–5539) — not Memorix’s 5100–5119.

Surface Role
Port from zone map scripts/api-simulator-ports.mjs / createSimulatorHttp
GET /health Process liveness
/_live In-process request rings (api-live-view)
x-correlation-id Request/response correlation
Startup banner origin · health · live · docs

Shared helper:

import { createSimulatorHttp } from '../../scripts/create-simulator-http.mjs';

const { core, announce } = createSimulatorHttp({
  id: 'my-simulator',
  title: 'My package simulator',
  portKey: 'api', // or flowstate / libraryDemo
  portEnv: 'API_SIMULATOR_API_PORT'
});

const server = createServer(async (req, res) => {
  if (await core.tryHandleCore(req, res)) return;
  await createNodeHttpHandler(simulator)(req, res);
});

await announce(server);

Published @x12i/api-simulator stays zero runtime dependencies. Remote clients do not install these kits; servers that bind do.


Case file

CASE FILE · SIM-02

3. Multiple APIs

A single simulator can expose many APIs. Each API has its own id, optional basePath, internal data, and endpoints.

const simulator = createApiSimulator({
  apis: [crmApi, billingApi, identityApi]
});

Duplicate METHOD + full path routes are rejected during startup. Structurally ambiguous routes such as /users/:id and /users/:name are also rejected. Static routes are matched before parameter routes.


Case file

CASE FILE · SIM-02

4. In-memory store

import { createStore } from '@x12i/api-simulator/store';

const users = createStore([{ id: '1', name: 'Ada' }]);
users.create({ name: 'Grace' });
users.list({ name: 'Ada' });
users.reset(); // restore seed — useful between tests

Case file

CASE FILE · SIM-02

5. OpenAPI skeletons

import { openapiToApiDefinition } from '@x12i/api-simulator/openapi';

const api = openapiToApiDefinition(parsedOpenApiObject, { id: 'pets-api', basePath: '/v1' });

Pass an already-parsed JS object (parse YAML yourself if needed).


Case file

CASE FILE · SIM-02

6. Fetch adapter

import { createFetchHandler } from '@x12i/api-simulator/fetch';

export default {
  fetch: createFetchHandler(simulator)
};

Case file

CASE FILE · SIM-02

7. Express and Fastify

// Express
app.use(async (req, res) => {
  const match = await simulator.tryDispatch({
    method: req.method,
    path: req.path,
    headers: req.headers,
    query: req.query,
    body: req.body
  });
  if (!match) return res.status(404).json({ error: 'NOT_FOUND' });
  res.status(match.response.status ?? 200).set(match.response.headers ?? {}).send(match.response.body);
});

Case file

CASE FILE · SIM-02

8. Auth and rate limits

import { requireBearerToken, rateLimit } from '@x12i/api-simulator/helpers';

handler: rateLimit({ windowMs: 60_000, max: 30 })(
  requireBearerToken((token) => token === 'secret')(
    ({ request }) => ({ body: { ok: true, path: request.path } })
  )
)

Case file

CASE FILE · SIM-02

9. CORS host and validators

{
  id: 'api',
  host: 'tenant.example.com',
  cors: { origins: '*', methods: ['GET', 'POST', 'OPTIONS'] },
  endpoints: [{
    id: 'create',
    method: 'POST',
    path: '/items',
    validate: ({ request }) => {
      if (!request.body) return { status: 400, body: { error: 'EMPTY' } };
    },
    behavior: { type: 'fixed', response: { status: 201, body: { ok: true } } }
  }]
}

Case file

CASE FILE · SIM-02

10. Record and replay

node scripts/record-replay.mjs --base https://httpbin.org --paths /get,/uuid --out captured.json

Case file

CASE FILE · SIM-02

11. Node body parsers

Built-in parsers: JSON, application/x-www-form-urlencoded, text/plain. Binary bodies are returned as Buffer. Plug in multipart yourself:

createNodeHttpHandler(simulator, {
  bodyParsers: {
    'multipart/form-data': async (raw, contentType) => {
      // use busboy / your parser of choice
      return { rawLength: raw.length, contentType };
    }
  }
});

Case file

CASE FILE · SIM-02

12. Simulator options

const simulator = createApiSimulator(definition, {
  recordHistory: { limit: 100 },
  validateTemplates: true // checks {{data.*}} at construction
});

simulator.getHistory();
simulator.clearHistory();