From 79e896ba317c2f5b3e478ea8abe5c167eeadbf97 Mon Sep 17 00:00:00 2001 From: signature18632 Date: Thu, 2 Oct 2025 05:05:55 +0700 Subject: [PATCH 1/2] feat: add TIMEOUT_ERROR to timeout middleware --- packages/indexer/src/index.ts | 3 +- packages/predicate/src/index.ts | 3 +- packages/shared/src/lib/error.ts | 51 +++++++++++++++++-- .../src/middlewares/cache.middleware.ts | 12 +---- packages/shared/src/types/txMap.ts | 11 ++++ packages/token/src/index.ts | 3 +- packages/tx-map/src/index.ts | 3 +- 7 files changed, 67 insertions(+), 19 deletions(-) diff --git a/packages/indexer/src/index.ts b/packages/indexer/src/index.ts index 739033a..960e707 100644 --- a/packages/indexer/src/index.ts +++ b/packages/indexer/src/index.ts @@ -11,6 +11,7 @@ import { NotFoundError, requestMiddleware, shutdown, + TIMEOUT_ERROR, } from "@intmax2-function/shared"; import { Hono } from "hono"; import { prettyJSON } from "hono/pretty-json"; @@ -26,7 +27,7 @@ const app = new Hono(); app.use(corsMiddleware); app.use(secureHeaders()); app.use(limiter); -app.use(timeout(APP_TIMEOUT)); +app.use(timeout(APP_TIMEOUT, TIMEOUT_ERROR)); app.use(requestMiddleware); app.use(prettyJSON()); diff --git a/packages/predicate/src/index.ts b/packages/predicate/src/index.ts index a613f14..5fb8b57 100644 --- a/packages/predicate/src/index.ts +++ b/packages/predicate/src/index.ts @@ -10,6 +10,7 @@ import { NotFoundError, requestMiddleware, shutdown, + TIMEOUT_ERROR, } from "@intmax2-function/shared"; import { Hono } from "hono"; import { prettyJSON } from "hono/pretty-json"; @@ -25,7 +26,7 @@ const app = new Hono(); app.use(corsMiddleware); app.use(secureHeaders()); app.use(limiter); -app.use(timeout(APP_TIMEOUT)); +app.use(timeout(APP_TIMEOUT, TIMEOUT_ERROR)); app.use(requestMiddleware); app.use(prettyJSON()); diff --git a/packages/shared/src/lib/error.ts b/packages/shared/src/lib/error.ts index c439dec..99cb6cd 100644 --- a/packages/shared/src/lib/error.ts +++ b/packages/shared/src/lib/error.ts @@ -1,4 +1,5 @@ import type { Context } from "hono"; +import { HTTPException } from "hono/http-exception"; import { ContentfulStatusCode } from "hono/utils/http-status"; import { ZodError } from "zod"; import { isProduction } from "../config"; @@ -11,6 +12,45 @@ export enum ErrorCode { VALIDATION_ERROR = "VALIDATION_ERROR", } +interface HasStatus { + status: number; +} + +const hasStatus = (err: unknown): err is HasStatus => { + return typeof err === "object" && err !== null && "status" in err; +}; + +const mapError = (status: ContentfulStatusCode, _: unknown) => { + if (status === 504) { + return { + code: "GATEWAY_TIMEOUT", + message: "Gateway Timeout", + }; + } + if (status >= 400 && status < 500) { + return { + code: "BAD_REQUEST", + message: "Bad Request", + }; + } + + return { + code: "INTERNAL_SERVER_ERROR", + message: "Internal Server Error", + }; +}; + +const getStatus = (err: unknown): number => { + if (hasStatus(err)) { + return err.status; + } + return 500; +}; + +export const TIMEOUT_ERROR = new HTTPException(504, { + message: "Request timed out", +}); + export class AppError extends Error { constructor( public statusCode: number, @@ -45,7 +85,11 @@ export class InternalServerError extends AppError { } } -const IGNORE_ERROR_MESSAGES = ["URI malformed"]; +const IGNORE_ERROR_MESSAGES = [ + "URI malformed", + "Request timed out", + "Response.clone: Body has already been consumed.", +]; export const handleError = (err: unknown, c: Context): Response => { if (err instanceof ZodError) { @@ -73,9 +117,8 @@ export const handleError = (err: unknown, c: Context): Response => { return c.json({ code: err.code, message: err.message }, err.statusCode as ContentfulStatusCode); } - const statusCode = err instanceof Error ? 500 : 400; - const code = err instanceof Error ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST"; - const message = err instanceof Error ? "Internal Server Error" : "Bad Request"; + const statusCode = getStatus(err) as ContentfulStatusCode; + const { code, message } = mapError(statusCode, err); return c.json( { diff --git a/packages/shared/src/middlewares/cache.middleware.ts b/packages/shared/src/middlewares/cache.middleware.ts index 67522a9..46f2df0 100644 --- a/packages/shared/src/middlewares/cache.middleware.ts +++ b/packages/shared/src/middlewares/cache.middleware.ts @@ -1,16 +1,6 @@ import type { Context, Next } from "hono"; import { cache } from "../lib"; - -export type CachedResponse = { - body: string; - headers: Record; - status: number; -}; - -type CacheOptions = { - expire: number; - key?: string; -}; +import type { CachedResponse, CacheOptions } from "../types"; const validateKeys = ["perPage", "cursor", "tokenIndexes", "contractAddresses"]; diff --git a/packages/shared/src/types/txMap.ts b/packages/shared/src/types/txMap.ts index b5748f2..aa7c19e 100644 --- a/packages/shared/src/types/txMap.ts +++ b/packages/shared/src/types/txMap.ts @@ -3,3 +3,14 @@ export type TxMapData = { data: string; expiresAt: Date; }; + +export interface CachedResponse { + body: string; + headers: Record; + status: number; +} + +export interface CacheOptions { + expire: number; + key?: string; +} diff --git a/packages/token/src/index.ts b/packages/token/src/index.ts index 315dd83..f135737 100644 --- a/packages/token/src/index.ts +++ b/packages/token/src/index.ts @@ -11,6 +11,7 @@ import { NotFoundError, requestMiddleware, shutdown, + TIMEOUT_ERROR, } from "@intmax2-function/shared"; import { Hono } from "hono"; import { compress } from "hono/compress"; @@ -31,7 +32,7 @@ bootstrap(); app.use(corsMiddleware); app.use(secureHeaders()); app.use(limiter); -app.use(timeout(APP_TIMEOUT)); +app.use(timeout(APP_TIMEOUT, TIMEOUT_ERROR)); app.use(requestMiddleware); app.use(compress()); diff --git a/packages/tx-map/src/index.ts b/packages/tx-map/src/index.ts index 739033a..960e707 100644 --- a/packages/tx-map/src/index.ts +++ b/packages/tx-map/src/index.ts @@ -11,6 +11,7 @@ import { NotFoundError, requestMiddleware, shutdown, + TIMEOUT_ERROR, } from "@intmax2-function/shared"; import { Hono } from "hono"; import { prettyJSON } from "hono/pretty-json"; @@ -26,7 +27,7 @@ const app = new Hono(); app.use(corsMiddleware); app.use(secureHeaders()); app.use(limiter); -app.use(timeout(APP_TIMEOUT)); +app.use(timeout(APP_TIMEOUT, TIMEOUT_ERROR)); app.use(requestMiddleware); app.use(prettyJSON()); From 71235366d45feb052a40873b416cfadc518f60ef Mon Sep 17 00:00:00 2001 From: signature18632 Date: Thu, 2 Oct 2025 05:07:04 +0700 Subject: [PATCH 2/2] style: clean up import statements --- packages/indexer/src/services/indexer.service.ts | 2 +- packages/shared/src/blockchain/wallet.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/indexer/src/services/indexer.service.ts b/packages/indexer/src/services/indexer.service.ts index 7d8bf74..bae0311 100644 --- a/packages/indexer/src/services/indexer.service.ts +++ b/packages/indexer/src/services/indexer.service.ts @@ -1,6 +1,6 @@ import { FIRESTORE_DOCUMENTS, getIndexer } from "@intmax2-function/shared"; import { BUILDER_SELECTION_MODE } from "../constants"; -import { getBuildersByMode } from "./../lib/builder"; +import { getBuildersByMode } from "../lib/builder"; export const listBlockBuilderNodes = async () => { const indexerInstance = getIndexer(FIRESTORE_DOCUMENTS.BUILDERS); diff --git a/packages/shared/src/blockchain/wallet.ts b/packages/shared/src/blockchain/wallet.ts index f4a6c77..b4083a1 100644 --- a/packages/shared/src/blockchain/wallet.ts +++ b/packages/shared/src/blockchain/wallet.ts @@ -1,7 +1,7 @@ import { createWalletClient, fallback } from "viem"; import { mnemonicToAccount, privateKeyToAccount } from "viem/accounts"; import { config } from "../config"; -import type { NetworkLayer } from "./../types"; +import type { NetworkLayer } from "../types"; import { getClientConfig } from "./client"; type MockWalletType = "mockMessenger";