types: include different possible status codes + associated payloads
vi did:web:vt3e.cat
Fri, 19 Jun 2026 08:59:48 +0100
3 files changed,
65 insertions(+),
22 deletions(-)
M
pkgs/types/package.json
→
pkgs/types/package.json
@@ -1,6 +1,6 @@
{ "name": "@purrkit/types", - "version": "1,0,0", + "version": "1.1.0", "files": [ "dist", "README.md",
M
pkgs/types/scripts/generate.ts
→
pkgs/types/scripts/generate.ts
@@ -1,7 +1,7 @@
-import * as fs from "fs/promises"; -import * as path from "path"; -import { OpenAPISpec } from "../src/generator/types"; +import * as fs from "node:fs/promises"; +import * as path from "node:path"; import { generateModelsSpec, generateRoutesSpec } from "../src/generator/generator"; +import type { OpenAPISpec } from "../src/generator/types"; const SPEC_URL = "https://raw.githubusercontent.com/discord/discord-api-spec/main/specs/openapi.json";
M
pkgs/types/src/generator/generator.ts
→
pkgs/types/src/generator/generator.ts
@@ -1,8 +1,18 @@
-import { JSONSchema } from "./types"; +import type { JSONSchema } from "./types"; + +const RESPONSE_REFS_MAP: Record<string, string> = { + ClientErrorResponse: "ErrorResponse", + ClientRatelimitedResponse: "RatelimitedResponse", + BadRequest: "ErrorResponse", + Unauthorized: "ErrorResponse", + Forbidden: "ErrorResponse", + NotFound: "ErrorResponse", +}; export function generateTypeScriptSpec( schemas: Record<string, JSONSchema>, paths: Record<string, any>, + responses: Record<string, any> = {}, ): string { const output: string[] = [];@@ -35,7 +45,7 @@ continue;
} output.push(` ${method.toLowerCase()}: {`); - const opDetails = resolvePathOperation(operation); + const opDetails = resolvePathOperation(operation, "", responses); output.push(opDetails); output.push(" };"); }@@ -69,7 +79,11 @@
return output.join("\n"); } -export function generateRoutesSpec(paths: Record<string, any>, modelNamespace = "Models"): string { +export function generateRoutesSpec( + paths: Record<string, any>, + modelNamespace = "Models", + responses: Record<string, any> = {}, +): string { const output: string[] = []; output.push("// this file is auto-generated. don;t edit it manually.");@@ -88,7 +102,7 @@ continue;
} output.push(` ${method.toLowerCase()}: {`); - const opDetails = resolvePathOperation(operation, modelNamespace); + const opDetails = resolvePathOperation(operation, modelNamespace, responses); output.push(opDetails); output.push(" };"); }@@ -183,9 +197,11 @@ export function resolvePropertyType(schema: JSONSchema, namespace = ""): string {
if (!schema) return "any"; if (schema.$ref) { - const refName = schema.$ref.split("/").pop() || "unknown"; + let refName = schema.$ref.split("/").pop() || "unknown"; if (refName.toLowerCase().includes("snowflake")) return namespace ? `${namespace}.Snowflake` : "Snowflake"; + + if (RESPONSE_REFS_MAP[refName]) refName = RESPONSE_REFS_MAP[refName]; return namespace ? `${namespace}.${refName}` : refName; }@@ -213,7 +229,35 @@
return "any"; } -function resolvePathOperation(operation: any, namespace = ""): string { +function resolveResponse( + responseObj: any, + namespace = "", + responses: Record<string, any> = {}, +): string { + if (!responseObj) return "unknown"; + + if (responseObj.$ref) { + const refName = responseObj.$ref.split("/").pop(); + if (refName && responses[refName]) + return resolveResponse(responses[refName], namespace, responses); + + return resolvePropertyType(responseObj, namespace); + } + + const content = responseObj.content; + if (content?.["application/json"]) { + const schema = content["application/json"].schema; + return resolvePropertyType(schema, namespace); + } + + return "void"; +} + +function resolvePathOperation( + operation: any, + namespace = "", + responses: Record<string, any> = {}, +): string { const parts: string[] = []; const parameters = operation.parameters || []; const pathParams = parameters.filter((p: any) => p.in === "path");@@ -248,26 +292,25 @@
const requestBody = operation.requestBody; if (requestBody) { const content = requestBody.content; - if (content && content["application/json"]) { + if (content?.["application/json"]) { const schema = content["application/json"].schema; parts.push(` requestBody: ${resolvePropertyType(schema, namespace)};`); } } - const responses = operation.responses || {}; - const successCode = Object.keys(responses).find((code) => code.startsWith("2")); - if (successCode) { - const responseObj = responses[successCode]; - const content = responseObj.content; + const opsResponses = operation.responses || {}; + const responseKeys = Object.keys(opsResponses); - if (content && content["application/json"]) { - const schema = content["application/json"].schema; - parts.push(` response: ${resolvePropertyType(schema, namespace)};`); - } else { - parts.push(" response: void;"); + if (responseKeys.length > 0) { + parts.push(" responses: {"); + for (const [code, responseObj] of Object.entries(opsResponses)) { + const formattedKey = /^\d+$/.test(code) ? code : `"${code}"`; + const resolvedType = resolveResponse(responseObj, namespace, responses); + parts.push(` ${formattedKey}: ${resolvedType};`); } + parts.push(" };"); } else { - parts.push(" response: unknown;"); + parts.push(" responses: {};"); } return parts.join("\n");