# `@x12i/api-simulator`

A transport-agnostic TypeScript library for defining and running simulated APIs.
Use it directly in tests, expose it through the included Node.js or Fetch
adapters, or integrate it with frameworks such as Express and Fastify.

## Features

- Fixed, template-based, and function-based endpoint behavior
- Path parameters, query strings, headers, and request-body matching
- Multiple APIs with independent base paths, hosts, and local data
- Runtime definition and template validation
- CORS, header authentication, bearer-token, and rate-limit helpers
- In-memory CRUD store, per-API mutable `context.state`, and OpenAPI skeletons
- Request history and raw text or binary responses
- Node.js HTTP, Fetch route-handler, and client `fetch` interceptors
- JSON/YAML endpoint packs with a simulation handler registry
- **Connector Framework (APS-CF):** conformance sessions, deterministic faults, redacted history, signed push, and a versioned scenario catalog for Memorix connector certification
- Zero runtime dependencies (CF push/signing uses Node built-ins only)

## Requirements

- Node.js 20 or newer
- TypeScript is optional; declarations are included

## Installation

```bash
npm install @x12i/api-simulator
```

Docs knowledge pack (devDependency only — **not** a substitute for the runtime at certification gates):

```bash
npm i -D @x12i/api-simulator-docs
```

## Quick start

```ts
import { createApiSimulator } from '@x12i/api-simulator';

const simulator = createApiSimulator({
  apis: [
    {
      id: 'users-api',
      basePath: '/api',
      data: {
        users: [{ id: 'user-1', name: 'Ada' }]
      },
      endpoints: [
        {
          id: 'health',
          method: 'GET',
          path: '/health',
          behavior: {
            type: 'fixed',
            response: { status: 200, body: { status: 'ok' } }
          }
        },
        {
          id: 'get-user',
          method: 'GET',
          path: '/users/:userId',
          behavior: {
            type: 'relative',
            response: {
              body: {
                id: '{{request.params.userId}}',
                users: '{{data.users}}'
              }
            }
          }
        },
        {
          id: 'sum',
          method: 'POST',
          path: '/sum',
          behavior: {
            type: 'simulation',
            handler: ({ request }) => {
              const values = (request.body as { values: number[] }).values;
              return {
                body: { total: values.reduce((sum, value) => sum + value, 0) }
              };
            }
          }
        }
      ]
    }
  ]
});

const match = await simulator.dispatch({
  method: 'GET',
  path: '/api/users/user-1'
});

// Worker clients can pass the absolute URL they already built:
await simulator.dispatch({
  method: 'GET',
  url: 'https://api.example/api/users/user-1'
});

console.log(match.response.body);
```

## Endpoint behaviors

Every endpoint defines exactly one behavior:

- **`fixed`** returns a configured response.
- **`relative`** renders a response from request values and API-local data.
- **`simulation`** runs a function for validation, branching, state changes, or
  computed responses.

The library never loads application data from disk, a database, or a remote
service. Pass data in the simulator definition, mutate `context.state`, or close
over `createStore` in a simulation handler. Optional `@x12i/api-simulator/packs`
loads **endpoint definitions** (not application data) from JSON/YAML.

## Package entry points

```ts
import { createApiSimulator } from '@x12i/api-simulator';
import { createNodeHttpHandler } from '@x12i/api-simulator/node';
import { createFetchHandler, createSimulatorFetch } from '@x12i/api-simulator/fetch';
import { createStore } from '@x12i/api-simulator/store';
import { createApiSimulatorFromFile } from '@x12i/api-simulator/packs';
import {
  rateLimit,
  requireBearerToken,
  requireHeader
} from '@x12i/api-simulator/helpers';
import { openapiToApiDefinition } from '@x12i/api-simulator/openapi';
import { createConformanceSession, listScenarios } from '@x12i/api-simulator/scenarios';
import { createFaultScheduler, createCursorPager } from '@x12i/api-simulator/cf';
import { signBodyBytes, APS_CF_TEST_SIGNING_KEY } from '@x12i/api-simulator/push';
```

## Connector Framework (APS-CF)

For Memorix connector conformance, start a named scenario session (no Mongo/Memorix/Credorix):

```ts
import { createConformanceSession } from '@x12i/api-simulator/scenarios';

const session = createConformanceSession({
  scenarioId: 'cf.pull.cursor-multipage',
  sessionId: 'ci-1',
  seed: 42
});

const page = await session.fetch('/v1/items');
session.injectFault({ kind: 'rate_limit', options: { retryAfterSec: 1 } });
const history = session.getHistory(); // secrets redacted
session.reset();
session.stop();
```

See the [Connector Framework](docs-library/05-connector-framework/developers/BOOK.md) book for the scenario catalog, push matrix, and Memorix compatibility matrix.

## Mutable simulation state

`api.data` stays read-only for templates. Action endpoints share a per-API bag on
`context.state` (single-process, not durable). `createStore` remains available
for typed CRUD collections.

```ts
{
  id: 'edr',
  data: { endpoints: [{ id: 'ep-1', hostname: 'workstation' }] },
  endpoints: [
    {
      id: 'isolate',
      method: 'POST',
      path: '/isolate',
      behavior: {
        type: 'simulation',
        handler: ({ request, state }) => {
          const isolated = state.get('isolated') ?? new Set();
          isolated.add(request.body.id);
          state.set('isolated', isolated);
          return { body: { reply: true } };
        }
      }
    },
    {
      id: 'list',
      method: 'GET',
      path: '/endpoints',
      behavior: {
        type: 'simulation',
        handler: ({ data, state }) => {
          const isolated = state.get('isolated') ?? new Set();
          return {
            body: {
              items: data.endpoints.map((item) => ({
                ...item,
                isolated: isolated.has(item.id)
              }))
            }
          };
        }
      }
    }
  ]
}

simulator.resetState(); // between tests
```

## Client fetch interceptor

```ts
import { createSimulatorFetch } from '@x12i/api-simulator/fetch';

const fetch = createSimulatorFetch(simulator, { unmatched: 'throw' });
await fetch('https://api.xdr.example/public_api/v1/healthcheck');

// Live passthrough when no endpoint matches:
const fetchOrLive = createSimulatorFetch(simulator, {
  unmatched: 'passthrough'
});
```

Inject the returned function as `fetch` (including undici's `fetch` option). Auth
and signing stay in the vendor client.

Publish the runtime:

```bash
npm run publish:runtime
```

## Examples and tools

- [Playground](apps/playground) — quickly define and dispatch requests
- [Studio](apps/studio) — local-first simulator authoring
- [REST tutorial](examples/library-demo/TUTORIAL.md)
- [FlowState integration tutorial](examples/flowstate-simulator/TUTORIAL.md)

## Documentation

Read the complete guides, behavior reference, adapter documentation, and use
cases at [docs.api-simulator.x12i.com](https://docs.api-simulator.x12i.com/).

## Development

```bash
npm install
npm run validate
```

Additional commands:

```bash
npm run dev:playground
npm run dev:studio
npm run docs
```

Local services use the `api-simulator` port zone (`5520`–`5539`).

## License

MIT
