CASE FILE · SIM-02
Audience: Engineers exposing simulators over HTTP and composing helpers.
Related: Behaviors · Package simulators
CASE FILE · SIM-02
import { createServer } from 'node:http';
import { createNodeHttpHandler } from '@x12i/api-simulator/node';
createServer(createNodeHttpHandler(simulator)).listen(5520);
The adapter:
string | string[]);Buffer for binary;404 when no simulated endpoint matches;500 when rendering or simulation fails;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 · SIM-02
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):
http-process-compliancecore-service, ports-manager, api-live-viewThis 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 · SIM-02
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 · SIM-02
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 · SIM-02
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 · SIM-02
import { createFetchHandler } from '@x12i/api-simulator/fetch';
export default {
fetch: createFetchHandler(simulator)
};
CASE FILE · SIM-02
// 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 · SIM-02
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 · SIM-02
{
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 · SIM-02
node scripts/record-replay.mjs --base https://httpbin.org --paths /get,/uuid --out captured.json
CASE FILE · SIM-02
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 · SIM-02
const simulator = createApiSimulator(definition, {
recordHistory: { limit: 100 },
validateTemplates: true // checks {{data.*}} at construction
});
simulator.getHistory();
simulator.clearHistory();