Production-ready Next.js boilerplate with: - Runtime env validation (fail-fast on missing vars) - Feature-gated config (S3, Stripe, email, OAuth) - Docker + Coolify deployment pipeline - PostgreSQL + pgvector, MinIO S3, Better Auth - TypeScript strict mode (no ignoreBuildErrors) - i18n (en/es), AI modules, billing, monitoring Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
77 lines
1.8 KiB
TypeScript
77 lines
1.8 KiB
TypeScript
import * as z from "zod";
|
|
|
|
import { isKey } from "@turbostarter/i18n";
|
|
import { getTranslation } from "@turbostarter/i18n/server";
|
|
import { captureException } from "@turbostarter/monitoring-web/server";
|
|
import { HttpStatusCode } from "@turbostarter/shared/constants";
|
|
import { logger } from "@turbostarter/shared/logger";
|
|
import { getStatusCode } from "@turbostarter/shared/utils";
|
|
|
|
import type { Context } from "hono";
|
|
|
|
const errorSchema = z.object({
|
|
code: z.string().optional(),
|
|
message: z.string(),
|
|
});
|
|
|
|
const isError = (e: unknown): e is z.infer<typeof errorSchema> => {
|
|
return errorSchema.safeParse(e).success;
|
|
};
|
|
|
|
export const onError = async (
|
|
e: unknown,
|
|
c?: Context<{
|
|
Bindings: { NODE_ENV: string };
|
|
Variables: { locale: string };
|
|
}>,
|
|
) => {
|
|
const { t, i18n } = await getTranslation({
|
|
locale: c?.var.locale,
|
|
request: c?.req.raw,
|
|
});
|
|
|
|
const status = getStatusCode(e);
|
|
const details = {
|
|
status,
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
},
|
|
};
|
|
|
|
const timestamp = new Date().toISOString();
|
|
const path = c?.req.raw.url ? new URL(c.req.raw.url).pathname : "/api";
|
|
|
|
if (status >= HttpStatusCode.INTERNAL_SERVER_ERROR) {
|
|
captureException(e, { path, status, timestamp });
|
|
}
|
|
|
|
if (isError(e)) {
|
|
logger.error(e.code, e.message);
|
|
return new Response(
|
|
JSON.stringify({
|
|
code: e.code,
|
|
message: e.message
|
|
? e.message
|
|
: e.code && isKey(e.code, i18n)
|
|
? t(e.code)
|
|
: ((e.message || e.code) ?? t("common:error.general")),
|
|
status,
|
|
timestamp,
|
|
path,
|
|
}),
|
|
details,
|
|
);
|
|
}
|
|
|
|
logger.error(e);
|
|
return new Response(
|
|
JSON.stringify({
|
|
code: "common:error.general",
|
|
message: t("common:error.general"),
|
|
status,
|
|
path,
|
|
}),
|
|
details,
|
|
);
|
|
};
|