feat(shared): reorder AppError constructor to (message, code, status), add singleton logger, retry and TTL cache utilities

This commit is contained in:
MythEclipse
2026-06-02 21:06:42 +07:00
parent 9045e7e347
commit 2d79d8aefd
50 changed files with 401 additions and 449 deletions
@@ -1,7 +1,7 @@
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { config } from "../config/index.js";
import { createChildLogger } from "../logger/index.js";
import { createChildLogger } from "@bete/shared/logger";
const logger = createChildLogger("database");
@@ -1,65 +0,0 @@
export class AppError extends Error {
constructor(
message: string,
public code: string,
public statusCode: number = 500,
) {
super(message);
this.name = "AppError";
}
}
export class ValidationError extends AppError {
constructor(
message: string,
public details?: Record<string, unknown>,
) {
super(message, "VALIDATION_ERROR", 400);
this.name = "ValidationError";
}
}
export class NotFoundError extends AppError {
constructor(message: string) {
super(message, "NOT_FOUND", 404);
this.name = "NotFoundError";
}
}
export class UnauthorizedError extends AppError {
constructor(message: string = "Unauthorized") {
super(message, "UNAUTHORIZED", 401);
this.name = "UnauthorizedError";
}
}
export class ForbiddenError extends AppError {
constructor(message: string = "Forbidden") {
super(message, "FORBIDDEN", 403);
this.name = "ForbiddenError";
}
}
export class ConflictError extends AppError {
constructor(message: string) {
super(message, "CONFLICT", 409);
this.name = "ConflictError";
}
}
export class DatabaseError extends AppError {
constructor(
message: string,
public originalError?: Error,
) {
super(message, "DATABASE_ERROR", 500);
this.name = "DatabaseError";
}
}
export class ConfigError extends AppError {
constructor(message: string) {
super(message, "CONFIG_ERROR", 500);
this.name = "ConfigError";
}
}
@@ -1,24 +0,0 @@
import pino from "pino";
import { config } from "../config/index.js";
const isDev = config.NODE_ENV === "development";
export const logger = pino({
level: config.LOG_LEVEL,
transport: isDev
? {
target: "pino-pretty",
options: {
colorize: true,
translateTime: "SYS:standard",
ignore: "pid,hostname",
},
}
: undefined,
});
export function createChildLogger(context: string) {
return logger.child({ context });
}
export type Logger = ReturnType<typeof createChildLogger>;
@@ -1,6 +1,10 @@
import type { NextFunction, Request, Response } from "express";
import { AppError, UnauthorizedError } from "../errors/index.js";
import { createChildLogger } from "../logger/index.js";
import {
AppError,
UnauthorizedError,
ValidationError,
} from "@bete/shared/errors";
import { createChildLogger } from "@bete/shared/logger";
const logger = createChildLogger("middleware");
@@ -46,5 +50,13 @@ export function asyncHandler(
};
}
// Import ValidationError for type checking
import { ValidationError } from "../errors/index.js";
/**
* Validate that a value is a non-empty string, or throw a descriptive error.
* Use for both route params and query string values.
*/
export function requireParam(value: unknown, kind: string, name: string): string {
if (typeof value !== "string" || value.length === 0) {
throw new Error(`Missing ${kind}: ${name}`);
}
return value;
}
+1 -1
View File
@@ -1,7 +1,7 @@
import { randomUUID } from "node:crypto";
import Redis from "ioredis";
import { config } from "../config/index.js";
import { createChildLogger } from "../logger/index.js";
import { createChildLogger } from "@bete/shared/logger";
const logger = createChildLogger("redis.command-channel");