Files
arbpulse/src/interfaces/http/openapi.ts
T
Mauricio Barragan f4def0c214 Refactor API contracts to Zod, zod-to-openapi, and Scalar.
Replace hand-written OpenAPI and Swagger UI with schema-driven docs at /api-docs and Zod validation on request bodies.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-11 00:04:12 -06:00

297 lines
7.2 KiB
TypeScript

/**
* OpenAPI 3.0 document generated from Zod schemas via @asteasolutions/zod-to-openapi.
* Served interactively at /api-docs through Scalar.
*/
import {
OpenAPIRegistry,
OpenApiGeneratorV3,
} from "@asteasolutions/zod-to-openapi";
import {
ErrorEnvelopeSchema,
SuccessEnvelopeNoDataSchema,
} from "./schemas/common.js";
import {
ConfigPatchSchema,
DemoControlBodySchema,
MaxTradeControlBodySchema,
RecordControlBodySchema,
ThresholdControlBodySchema,
} from "./schemas/requests.js";
import {
ConfigResponseSchema,
DemoModeResponseSchema,
HealthResponseSchema,
MaxTradeResponseSchema,
RecordFeedResponseSchema,
StateResponseSchema,
ThresholdResponseSchema,
} from "./schemas/responses.js";
import { StateSnapshotSchema } from "./schemas/domain.js";
const registry = new OpenAPIRegistry();
registry.register("ErrorEnvelope", ErrorEnvelopeSchema);
registry.register("SuccessEnvelopeNoData", SuccessEnvelopeNoDataSchema);
registry.register("StateSnapshot", StateSnapshotSchema);
const jsonError = {
description: "Invalid request body or out-of-range value.",
content: { "application/json": { schema: ErrorEnvelopeSchema } },
};
const jsonServerError = {
description: "Unexpected server error.",
content: { "application/json": { schema: ErrorEnvelopeSchema } },
};
registry.registerPath({
method: "get",
path: "/api/health",
tags: ["Monitoring"],
summary: "Health check",
description:
"Lightweight endpoint used by Fly.io (and any uptime monitor) to verify the server is alive.",
operationId: "getHealth",
responses: {
200: {
description: "Server is healthy.",
content: { "application/json": { schema: HealthResponseSchema } },
},
500: jsonServerError,
},
});
registry.registerPath({
method: "get",
path: "/api/state",
tags: ["Monitoring"],
summary: "Full state snapshot",
description:
"Returns the complete in-memory engine state as a single JSON document.",
operationId: "getState",
responses: {
200: {
description: "Current engine state.",
content: { "application/json": { schema: StateResponseSchema } },
},
500: jsonServerError,
},
});
registry.registerPath({
method: "get",
path: "/api/stream",
tags: ["Streaming"],
summary: "SSE real-time feed",
description:
"Opens a persistent Server-Sent Events connection. Each event carries a JSON-encoded StateSnapshot.",
operationId: "getStream",
responses: {
200: {
description: "SSE stream opened.",
content: {
"text/event-stream": {
schema: {
type: "string",
description: "Newline-delimited SSE events.",
},
},
},
},
},
});
registry.registerPath({
method: "get",
path: "/api/config",
tags: ["Configuration"],
summary: "Get engine configuration",
operationId: "getConfig",
responses: {
200: {
description: "Current configuration.",
content: { "application/json": { schema: ConfigResponseSchema } },
},
500: jsonServerError,
},
});
registry.registerPath({
method: "patch",
path: "/api/config",
tags: ["Configuration"],
summary: "Update engine configuration",
operationId: "patchConfig",
request: {
body: {
content: { "application/json": { schema: ConfigPatchSchema } },
required: true,
},
},
responses: {
200: {
description: "Updated configuration.",
content: { "application/json": { schema: ConfigResponseSchema } },
},
400: jsonError,
500: jsonServerError,
},
});
function registerControlPost(
path: string,
operationId: string,
summary: string,
description: string,
): void {
registry.registerPath({
method: "post",
path,
tags: ["Control"],
summary,
description,
operationId,
responses: {
200: {
description: summary,
content: {
"application/json": { schema: SuccessEnvelopeNoDataSchema },
},
},
500: jsonServerError,
},
});
}
registerControlPost(
"/api/control/pause",
"controlPause",
"Pause the arbitrage engine",
"Manually pauses the engine until resume is called.",
);
registerControlPost(
"/api/control/resume",
"controlResume",
"Resume the arbitrage engine",
"Resumes opportunity evaluation after pause or circuit-breaker trip.",
);
registerControlPost(
"/api/control/reset",
"controlReset",
"Reset simulated state",
"Resets wallets, trade history, and cumulative P&L.",
);
registry.registerPath({
method: "post",
path: "/api/control/demo",
tags: ["Control"],
summary: "Enable / disable demo feed",
operationId: "controlDemo",
request: {
body: {
content: { "application/json": { schema: DemoControlBodySchema } },
required: true,
},
},
responses: {
200: {
description: "Demo mode updated.",
content: { "application/json": { schema: DemoModeResponseSchema } },
},
400: jsonError,
500: jsonServerError,
},
});
registry.registerPath({
method: "post",
path: "/api/control/record",
tags: ["Control"],
summary: "Enable / disable feed recording",
operationId: "controlRecord",
request: {
body: {
content: { "application/json": { schema: RecordControlBodySchema } },
required: true,
},
},
responses: {
200: {
description: "Recording state updated.",
content: { "application/json": { schema: RecordFeedResponseSchema } },
},
400: jsonError,
500: jsonServerError,
},
});
registry.registerPath({
method: "post",
path: "/api/control/threshold",
tags: ["Control"],
summary: "Set minimum net profit threshold",
operationId: "controlThreshold",
request: {
body: {
content: { "application/json": { schema: ThresholdControlBodySchema } },
required: true,
},
},
responses: {
200: {
description: "Threshold updated.",
content: { "application/json": { schema: ThresholdResponseSchema } },
},
400: jsonError,
500: jsonServerError,
},
});
registry.registerPath({
method: "post",
path: "/api/control/max-trade",
tags: ["Control"],
summary: "Set maximum trade volume",
operationId: "controlMaxTrade",
request: {
body: {
content: { "application/json": { schema: MaxTradeControlBodySchema } },
required: true,
},
},
responses: {
200: {
description: "Max trade volume updated.",
content: { "application/json": { schema: MaxTradeResponseSchema } },
},
400: jsonError,
500: jsonServerError,
},
});
const generator = new OpenApiGeneratorV3(registry.definitions);
export const openapiDocument = generator.generateDocument({
openapi: "3.0.3",
info: {
title: "Arb Pulse API",
version: "1.0.0",
description:
"Real-time BTC/USDT arbitrage detection and simulation engine. Simulated only — no real funds are committed.",
contact: { name: "Arb Pulse" },
license: { name: "MIT" },
},
servers: [{ url: "/", description: "Current host (Fly.io / local)" }],
tags: [
{ name: "Monitoring", description: "Health and state inspection." },
{ name: "Streaming", description: "Server-Sent Events real-time feed." },
{
name: "Configuration",
description: "Read and update engine parameters.",
},
{ name: "Control", description: "Pause, resume, reset and mode switches." },
],
});