create-charcole 1.1.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yml +26 -0
- package/CHANGELOG.md +25 -0
- package/README.md +11 -1
- package/bin/index.js +90 -71
- package/bin/lib/pkgManager.js +66 -0
- package/bin/lib/templateHandler.js +70 -0
- package/package.json +4 -1
- package/template/js/basePackage.json +28 -0
- package/template/{package-lock.json → js/package-lock.json} +1253 -1253
- package/template/{package.json → js/package.json} +28 -28
- package/template/ts/.env.example +8 -0
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +283 -0
- package/template/ts/CHECKLIST.md +279 -0
- package/template/ts/COMPLETE.md +405 -0
- package/template/ts/ERROR_HANDLING.md +393 -0
- package/template/ts/IMPLEMENTATION.md +368 -0
- package/template/ts/IMPLEMENTATION_COMPLETE.md +363 -0
- package/template/ts/INDEX.md +290 -0
- package/template/ts/QUICK_REFERENCE.md +270 -0
- package/template/ts/README.md +855 -0
- package/template/ts/basePackage.json +36 -0
- package/template/ts/package-lock.json +2428 -0
- package/template/ts/package.json +32 -0
- package/template/ts/src/app.js +75 -0
- package/template/ts/src/app.ts +66 -0
- package/template/ts/src/config/constants.js +20 -0
- package/template/ts/src/config/constants.ts +27 -0
- package/template/ts/src/config/env.js +26 -0
- package/template/ts/src/config/env.ts +40 -0
- package/template/ts/src/middlewares/errorHandler.js +180 -0
- package/template/ts/src/middlewares/errorHandler.ts +209 -0
- package/template/ts/src/middlewares/requestLogger.js +33 -0
- package/template/ts/src/middlewares/requestLogger.ts +38 -0
- package/template/ts/src/middlewares/validateRequest.js +42 -0
- package/template/ts/src/middlewares/validateRequest.ts +46 -0
- package/template/ts/src/modules/health/controller.js +50 -0
- package/template/ts/src/modules/health/controller.ts +64 -0
- package/template/ts/src/routes.js +17 -0
- package/template/ts/src/routes.ts +16 -0
- package/template/ts/src/server.js +38 -0
- package/template/ts/src/server.ts +42 -0
- package/template/ts/src/types/express.d.ts +9 -0
- package/template/ts/src/utils/AppError.js +182 -0
- package/template/ts/src/utils/AppError.ts +220 -0
- package/template/ts/src/utils/logger.js +73 -0
- package/template/ts/src/utils/logger.ts +55 -0
- package/template/ts/src/utils/response.js +51 -0
- package/template/ts/src/utils/response.ts +100 -0
- package/template/ts/test-api.js +100 -0
- package/template/ts/tsconfig.json +19 -0
- /package/template/{.env.example → js/.env.example} +0 -0
- /package/template/{ARCHITECTURE_DIAGRAMS.md → js/ARCHITECTURE_DIAGRAMS.md} +0 -0
- /package/template/{CHECKLIST.md → js/CHECKLIST.md} +0 -0
- /package/template/{COMPLETE.md → js/COMPLETE.md} +0 -0
- /package/template/{ERROR_HANDLING.md → js/ERROR_HANDLING.md} +0 -0
- /package/template/{IMPLEMENTATION.md → js/IMPLEMENTATION.md} +0 -0
- /package/template/{IMPLEMENTATION_COMPLETE.md → js/IMPLEMENTATION_COMPLETE.md} +0 -0
- /package/template/{INDEX.md → js/INDEX.md} +0 -0
- /package/template/{QUICK_REFERENCE.md → js/QUICK_REFERENCE.md} +0 -0
- /package/template/{README.md → js/README.md} +0 -0
- /package/template/{src → js/src}/app.js +0 -0
- /package/template/{src → js/src}/config/constants.js +0 -0
- /package/template/{src → js/src}/config/env.js +0 -0
- /package/template/{src → js/src}/middlewares/errorHandler.js +0 -0
- /package/template/{src → js/src}/middlewares/requestLogger.js +0 -0
- /package/template/{src → js/src}/middlewares/validateRequest.js +0 -0
- /package/template/{src → js/src}/modules/health/controller.js +0 -0
- /package/template/{src → js/src}/routes.js +0 -0
- /package/template/{src → js/src}/server.js +0 -0
- /package/template/{src → js/src}/utils/AppError.js +0 -0
- /package/template/{src → js/src}/utils/logger.js +0 -0
- /package/template/{src → js/src}/utils/response.js +0 -0
- /package/template/{test-api.js → js/test-api.js} +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { logger } from "../utils/logger.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Request logging middleware
|
|
5
|
+
* Logs all HTTP requests with method, path, status, duration, and IP
|
|
6
|
+
*/
|
|
7
|
+
export const requestLogger = (req, res, next) => {
|
|
8
|
+
const start = Date.now();
|
|
9
|
+
|
|
10
|
+
res.on("finish", () => {
|
|
11
|
+
const duration = Date.now() - start;
|
|
12
|
+
const statusCode = res.statusCode;
|
|
13
|
+
const isError = statusCode >= 400;
|
|
14
|
+
|
|
15
|
+
const logData = {
|
|
16
|
+
method: req.method,
|
|
17
|
+
path: req.path,
|
|
18
|
+
statusCode,
|
|
19
|
+
durationMs: duration,
|
|
20
|
+
ip: req.ip,
|
|
21
|
+
userAgent: req.get("user-agent"),
|
|
22
|
+
...(isError && { error: true }),
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
if (isError) {
|
|
26
|
+
logger.warn(`${req.method} ${req.path}`, logData);
|
|
27
|
+
} else {
|
|
28
|
+
logger.debug(`${req.method} ${req.path}`, logData);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
next();
|
|
33
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { logger } from "../utils/logger.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Request logging middleware
|
|
6
|
+
* Logs all HTTP requests with method, path, status, duration, and IP
|
|
7
|
+
*/
|
|
8
|
+
export const requestLogger = (
|
|
9
|
+
req: Request,
|
|
10
|
+
res: Response,
|
|
11
|
+
next: NextFunction,
|
|
12
|
+
): void => {
|
|
13
|
+
const start = Date.now();
|
|
14
|
+
|
|
15
|
+
res.on("finish", () => {
|
|
16
|
+
const duration = Date.now() - start;
|
|
17
|
+
const statusCode = res.statusCode;
|
|
18
|
+
const isError = statusCode >= 400;
|
|
19
|
+
|
|
20
|
+
const logData = {
|
|
21
|
+
method: req.method,
|
|
22
|
+
path: req.path,
|
|
23
|
+
statusCode,
|
|
24
|
+
durationMs: duration,
|
|
25
|
+
ip: req.ip,
|
|
26
|
+
userAgent: req.get("user-agent") || undefined,
|
|
27
|
+
...(isError && { error: true }),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (isError) {
|
|
31
|
+
logger.warn(`${req.method} ${req.path}`, logData);
|
|
32
|
+
} else {
|
|
33
|
+
logger.debug(`${req.method} ${req.path}`, logData);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
next();
|
|
38
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ValidationError } from "../utils/AppError.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Request validation middleware
|
|
5
|
+
*
|
|
6
|
+
* Validates request body, query, and params against a Zod schema
|
|
7
|
+
* Throws ValidationError if validation fails
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* const schema = z.object({
|
|
11
|
+
* body: z.object({ name: z.string() }),
|
|
12
|
+
* query: z.object({ page: z.coerce.number().optional() }),
|
|
13
|
+
* });
|
|
14
|
+
*
|
|
15
|
+
* router.post('/items', validateRequest(schema), handler)
|
|
16
|
+
*/
|
|
17
|
+
export const validateRequest = (schema) => {
|
|
18
|
+
return async (req, res, next) => {
|
|
19
|
+
try {
|
|
20
|
+
// Validate request
|
|
21
|
+
const validated = await schema.parseAsync({
|
|
22
|
+
body: req.body,
|
|
23
|
+
query: req.query,
|
|
24
|
+
params: req.params,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Attach validated data
|
|
28
|
+
req.validatedData = validated;
|
|
29
|
+
next();
|
|
30
|
+
} catch (error) {
|
|
31
|
+
if (error.name === "ZodError") {
|
|
32
|
+
const errors = error.errors.map((e) => ({
|
|
33
|
+
field: e.path.join("."),
|
|
34
|
+
message: e.message,
|
|
35
|
+
code: e.code,
|
|
36
|
+
}));
|
|
37
|
+
throw new ValidationError("Request validation failed", errors);
|
|
38
|
+
}
|
|
39
|
+
next(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
import { z, ZodError } from "zod";
|
|
3
|
+
import { ValidationError } from "../utils/AppError.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Request validation middleware
|
|
7
|
+
*
|
|
8
|
+
* Validates request body, query, and params against a Zod schema
|
|
9
|
+
* Throws ValidationError if validation fails
|
|
10
|
+
*
|
|
11
|
+
* Example:
|
|
12
|
+
* const schema = z.object({
|
|
13
|
+
* body: z.object({ name: z.string() }),
|
|
14
|
+
* query: z.object({ page: z.coerce.number().optional() }),
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* router.post('/items', validateRequest(schema), handler)
|
|
18
|
+
*/
|
|
19
|
+
export const validateRequest = (schema: z.AnyZodObject) => {
|
|
20
|
+
return async (
|
|
21
|
+
req: Request,
|
|
22
|
+
res: Response,
|
|
23
|
+
next: NextFunction,
|
|
24
|
+
): Promise<void> => {
|
|
25
|
+
try {
|
|
26
|
+
const validated = await schema.parseAsync({
|
|
27
|
+
body: req.body,
|
|
28
|
+
query: req.query,
|
|
29
|
+
params: req.params,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
req.validatedData = validated;
|
|
33
|
+
next();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error instanceof ZodError) {
|
|
36
|
+
const errors = error.errors.map((e) => ({
|
|
37
|
+
field: e.path.join("."),
|
|
38
|
+
message: e.message,
|
|
39
|
+
code: e.code,
|
|
40
|
+
}));
|
|
41
|
+
throw new ValidationError("Request validation failed", errors);
|
|
42
|
+
}
|
|
43
|
+
next(error);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sendSuccess } from "../../utils/response.js";
|
|
3
|
+
import { asyncHandler } from "../../middlewares/errorHandler.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Health check endpoint
|
|
7
|
+
* Always returns healthy status (ping endpoint)
|
|
8
|
+
*/
|
|
9
|
+
export const getHealth = asyncHandler(async (req, res) => {
|
|
10
|
+
sendSuccess(
|
|
11
|
+
res,
|
|
12
|
+
{
|
|
13
|
+
status: "healthy",
|
|
14
|
+
uptime: process.uptime(),
|
|
15
|
+
timestamp: new Date().toISOString(),
|
|
16
|
+
},
|
|
17
|
+
200,
|
|
18
|
+
"Service is healthy",
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Example POST endpoint with validation
|
|
24
|
+
* Demonstrates proper error handling with Zod validation
|
|
25
|
+
*/
|
|
26
|
+
export const createItemSchema = z.object({
|
|
27
|
+
body: z.object({
|
|
28
|
+
name: z.string().min(1, "Name is required").max(100),
|
|
29
|
+
description: z.string().optional(),
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const createItem = asyncHandler(async (req, res) => {
|
|
34
|
+
const { name, description } = req.validatedData.body;
|
|
35
|
+
|
|
36
|
+
// Simulate some async work
|
|
37
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
38
|
+
|
|
39
|
+
sendSuccess(
|
|
40
|
+
res,
|
|
41
|
+
{
|
|
42
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
43
|
+
name,
|
|
44
|
+
description: description || null,
|
|
45
|
+
createdAt: new Date().toISOString(),
|
|
46
|
+
},
|
|
47
|
+
201,
|
|
48
|
+
"Item created successfully",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { sendSuccess } from "../../utils/response.js";
|
|
4
|
+
import { asyncHandler } from "../../middlewares/errorHandler.js";
|
|
5
|
+
import { validateRequest } from "../../middlewares/validateRequest.js";
|
|
6
|
+
|
|
7
|
+
const healthCheckSchema = z.object({
|
|
8
|
+
query: z.object({}),
|
|
9
|
+
params: z.object({}),
|
|
10
|
+
body: z.object({}),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const createItemSchema = z.object({
|
|
14
|
+
body: z.object({
|
|
15
|
+
name: z.string().min(1, "Name is required").max(100),
|
|
16
|
+
description: z.string().optional(),
|
|
17
|
+
}),
|
|
18
|
+
query: z.object({}),
|
|
19
|
+
params: z.object({}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
type CreateItemBody = z.infer<typeof createItemSchema>["body"];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Health check endpoint
|
|
26
|
+
* Always returns healthy status (ping endpoint)
|
|
27
|
+
*/
|
|
28
|
+
export const getHealth = [
|
|
29
|
+
validateRequest(healthCheckSchema),
|
|
30
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
31
|
+
const response = {
|
|
32
|
+
status: "healthy" as const,
|
|
33
|
+
uptime: process.uptime(),
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
sendSuccess(res, response, 200, "Service is healthy");
|
|
38
|
+
}),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Example POST endpoint with validation
|
|
43
|
+
* Demonstrates proper error handling with Zod validation
|
|
44
|
+
*/
|
|
45
|
+
export const createItem = [
|
|
46
|
+
validateRequest(createItemSchema),
|
|
47
|
+
asyncHandler(async (req: Request, res: Response) => {
|
|
48
|
+
const validatedData = req.validatedData as { body: CreateItemBody };
|
|
49
|
+
const { name, description } = validatedData.body;
|
|
50
|
+
|
|
51
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
52
|
+
|
|
53
|
+
const response = {
|
|
54
|
+
id: Math.random().toString(36).substr(2, 9),
|
|
55
|
+
name,
|
|
56
|
+
description: description || null,
|
|
57
|
+
createdAt: new Date().toISOString(),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
sendSuccess(res, response, 201, "Item created successfully");
|
|
61
|
+
}),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
export { createItemSchema };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
import {
|
|
3
|
+
getHealth,
|
|
4
|
+
createItem,
|
|
5
|
+
createItemSchema,
|
|
6
|
+
} from "./modules/health/controller.js";
|
|
7
|
+
import { validateRequest } from "./middlewares/validateRequest.js";
|
|
8
|
+
|
|
9
|
+
const router = Router();
|
|
10
|
+
|
|
11
|
+
// Health check
|
|
12
|
+
router.get("/health", getHealth);
|
|
13
|
+
|
|
14
|
+
// Example: Create item with validation
|
|
15
|
+
router.post("/items", validateRequest(createItemSchema), createItem);
|
|
16
|
+
|
|
17
|
+
export default router;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Router } from "express";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getHealth,
|
|
5
|
+
createItem,
|
|
6
|
+
createItemSchema,
|
|
7
|
+
} from "./modules/health/controller.js";
|
|
8
|
+
import { validateRequest } from "./middlewares/validateRequest.js";
|
|
9
|
+
|
|
10
|
+
const router = Router();
|
|
11
|
+
|
|
12
|
+
router.get("/health", getHealth);
|
|
13
|
+
|
|
14
|
+
router.post("/items", validateRequest(createItemSchema), createItem);
|
|
15
|
+
|
|
16
|
+
export default router;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
|
|
3
|
+
import { app } from "./app.js";
|
|
4
|
+
import { env } from "./config/env.js";
|
|
5
|
+
import { logger } from "./utils/logger.js";
|
|
6
|
+
|
|
7
|
+
const PORT = env.PORT;
|
|
8
|
+
|
|
9
|
+
const server = app.listen(PORT, () => {
|
|
10
|
+
logger.info(`🔥 Server running in ${env.NODE_ENV} mode`, {
|
|
11
|
+
url: `http://localhost:${PORT}`,
|
|
12
|
+
port: PORT,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Graceful shutdown
|
|
17
|
+
const gracefulShutdown = (signal) => {
|
|
18
|
+
logger.warn(`${signal} signal received: closing HTTP server`);
|
|
19
|
+
server.close(() => {
|
|
20
|
+
logger.info("HTTP server closed");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
26
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
27
|
+
|
|
28
|
+
// Unhandled promise rejections
|
|
29
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
30
|
+
logger.error("Unhandled Rejection at:", { promise, reason });
|
|
31
|
+
process.exit(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Uncaught exceptions
|
|
35
|
+
process.on("uncaughtException", (error) => {
|
|
36
|
+
logger.error("Uncaught Exception:", { error: error.message });
|
|
37
|
+
process.exit(1);
|
|
38
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
|
|
3
|
+
import { app } from "./app.js";
|
|
4
|
+
import { env } from "./config/env.js";
|
|
5
|
+
import { logger } from "./utils/logger.js";
|
|
6
|
+
|
|
7
|
+
const PORT = env.PORT;
|
|
8
|
+
|
|
9
|
+
const server = app.listen(PORT, () => {
|
|
10
|
+
logger.info(`🔥 Server running in ${env.NODE_ENV} mode`, {
|
|
11
|
+
url: `http://localhost:${PORT}`,
|
|
12
|
+
port: PORT,
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const gracefulShutdown = (signal: string): void => {
|
|
17
|
+
logger.warn(`${signal} signal received: closing HTTP server`);
|
|
18
|
+
|
|
19
|
+
server.close(() => {
|
|
20
|
+
logger.info("HTTP server closed");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
|
|
26
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
|
|
27
|
+
|
|
28
|
+
process.on(
|
|
29
|
+
"unhandledRejection",
|
|
30
|
+
(reason: unknown, promise: Promise<unknown>) => {
|
|
31
|
+
logger.error("Unhandled Rejection at:", { promise, reason });
|
|
32
|
+
process.exit(1);
|
|
33
|
+
},
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
process.on("uncaughtException", (error: Error) => {
|
|
37
|
+
logger.error("Uncaught Exception:", {
|
|
38
|
+
message: error.message,
|
|
39
|
+
stack: error.stack,
|
|
40
|
+
});
|
|
41
|
+
process.exit(1);
|
|
42
|
+
});
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operational Error Class
|
|
3
|
+
*
|
|
4
|
+
* Use for expected errors that can be handled gracefully
|
|
5
|
+
* Examples: validation errors, resource not found, auth failures
|
|
6
|
+
*
|
|
7
|
+
* These errors are logged and sent back to client as JSON
|
|
8
|
+
*/
|
|
9
|
+
export class AppError extends Error {
|
|
10
|
+
constructor(
|
|
11
|
+
message,
|
|
12
|
+
statusCode = 500,
|
|
13
|
+
{ isOperational = true, code = null, context = null, cause = null } = {},
|
|
14
|
+
) {
|
|
15
|
+
super(message);
|
|
16
|
+
|
|
17
|
+
// Operational or Programmer error
|
|
18
|
+
this.isOperational = isOperational;
|
|
19
|
+
|
|
20
|
+
// HTTP status code
|
|
21
|
+
this.statusCode = statusCode;
|
|
22
|
+
|
|
23
|
+
// Error code for client handling (e.g., 'INVALID_EMAIL', 'USER_NOT_FOUND')
|
|
24
|
+
this.code = code;
|
|
25
|
+
|
|
26
|
+
// Additional context data
|
|
27
|
+
this.context = context || {};
|
|
28
|
+
|
|
29
|
+
// Original error that caused this
|
|
30
|
+
this.cause = cause;
|
|
31
|
+
|
|
32
|
+
// Timestamp
|
|
33
|
+
this.timestamp = new Date().toISOString();
|
|
34
|
+
|
|
35
|
+
// Preserve stack trace
|
|
36
|
+
Error.captureStackTrace(this, this.constructor);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Convert to JSON response format
|
|
41
|
+
*/
|
|
42
|
+
toJSON() {
|
|
43
|
+
return {
|
|
44
|
+
success: false,
|
|
45
|
+
message: this.message,
|
|
46
|
+
code: this.code,
|
|
47
|
+
statusCode: this.statusCode,
|
|
48
|
+
...(Object.keys(this.context).length > 0 && { context: this.context }),
|
|
49
|
+
timestamp: this.timestamp,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get full error details for logging
|
|
55
|
+
*/
|
|
56
|
+
getFullDetails() {
|
|
57
|
+
return {
|
|
58
|
+
message: this.message,
|
|
59
|
+
statusCode: this.statusCode,
|
|
60
|
+
code: this.code,
|
|
61
|
+
isOperational: this.isOperational,
|
|
62
|
+
context: this.context,
|
|
63
|
+
cause: this.cause?.message || null,
|
|
64
|
+
stack: this.stack,
|
|
65
|
+
timestamp: this.timestamp,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validation Error - extends AppError
|
|
72
|
+
* Use for input validation failures
|
|
73
|
+
*/
|
|
74
|
+
export class ValidationError extends AppError {
|
|
75
|
+
constructor(message, errors = [], context = null) {
|
|
76
|
+
super(message, 422, {
|
|
77
|
+
isOperational: true,
|
|
78
|
+
code: "VALIDATION_ERROR",
|
|
79
|
+
context,
|
|
80
|
+
});
|
|
81
|
+
this.errors = errors;
|
|
82
|
+
this.name = "ValidationError";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
toJSON() {
|
|
86
|
+
return {
|
|
87
|
+
...super.toJSON(),
|
|
88
|
+
errors: this.errors,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Authentication Error
|
|
95
|
+
* Use for auth/permission failures
|
|
96
|
+
*/
|
|
97
|
+
export class AuthenticationError extends AppError {
|
|
98
|
+
constructor(message = "Unauthorized", context = null) {
|
|
99
|
+
super(message, 401, {
|
|
100
|
+
isOperational: true,
|
|
101
|
+
code: "AUTHENTICATION_ERROR",
|
|
102
|
+
context,
|
|
103
|
+
});
|
|
104
|
+
this.name = "AuthenticationError";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Authorization Error
|
|
110
|
+
* Use for permission denied scenarios
|
|
111
|
+
*/
|
|
112
|
+
export class AuthorizationError extends AppError {
|
|
113
|
+
constructor(message = "Forbidden", context = null) {
|
|
114
|
+
super(message, 403, {
|
|
115
|
+
isOperational: true,
|
|
116
|
+
code: "AUTHORIZATION_ERROR",
|
|
117
|
+
context,
|
|
118
|
+
});
|
|
119
|
+
this.name = "AuthorizationError";
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Not Found Error
|
|
125
|
+
* Use when resource doesn't exist
|
|
126
|
+
*/
|
|
127
|
+
export class NotFoundError extends AppError {
|
|
128
|
+
constructor(resource = "Resource", context = null) {
|
|
129
|
+
super(`${resource} not found`, 404, {
|
|
130
|
+
isOperational: true,
|
|
131
|
+
code: "NOT_FOUND",
|
|
132
|
+
context,
|
|
133
|
+
});
|
|
134
|
+
this.name = "NotFoundError";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Conflict Error
|
|
140
|
+
* Use for duplicate resources, business logic conflicts
|
|
141
|
+
*/
|
|
142
|
+
export class ConflictError extends AppError {
|
|
143
|
+
constructor(message, context = null) {
|
|
144
|
+
super(message, 409, {
|
|
145
|
+
isOperational: true,
|
|
146
|
+
code: "CONFLICT",
|
|
147
|
+
context,
|
|
148
|
+
});
|
|
149
|
+
this.name = "ConflictError";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Bad Request Error
|
|
155
|
+
* Use for malformed requests
|
|
156
|
+
*/
|
|
157
|
+
export class BadRequestError extends AppError {
|
|
158
|
+
constructor(message, context = null) {
|
|
159
|
+
super(message, 400, {
|
|
160
|
+
isOperational: true,
|
|
161
|
+
code: "BAD_REQUEST",
|
|
162
|
+
context,
|
|
163
|
+
});
|
|
164
|
+
this.name = "BadRequestError";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Internal Server Error
|
|
170
|
+
* Use for unexpected server-side errors (programmer errors)
|
|
171
|
+
*/
|
|
172
|
+
export class InternalServerError extends AppError {
|
|
173
|
+
constructor(message = "Internal server error", cause = null, context = null) {
|
|
174
|
+
super(message, 500, {
|
|
175
|
+
isOperational: false,
|
|
176
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
177
|
+
cause,
|
|
178
|
+
context,
|
|
179
|
+
});
|
|
180
|
+
this.name = "InternalServerError";
|
|
181
|
+
}
|
|
182
|
+
}
|