f4def0c214
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>
297 lines
7.2 KiB
TypeScript
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." },
|
|
],
|
|
});
|