feat(errors): add @bytelyst/errors package
- ServiceError base class with statusCode, message, details - HTTP errors: BadRequest, Unauthorized, Forbidden, NotFound, Conflict, TooManyRequests - 10 tests passing (vitest) - Superset of all 4 service error files in LysnrAI
This commit is contained in:
parent
d875df09a3
commit
9c0ab36171
18
packages/errors/package.json
Normal file
18
packages/errors/package.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "@bytelyst/errors",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"files": ["dist"],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "vitest run"
|
||||
}
|
||||
}
|
||||
56
packages/errors/src/__tests__/errors.test.ts
Normal file
56
packages/errors/src/__tests__/errors.test.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
ServiceError,
|
||||
BadRequestError,
|
||||
UnauthorizedError,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
ConflictError,
|
||||
TooManyRequestsError,
|
||||
} from "../index.js";
|
||||
|
||||
describe("ServiceError", () => {
|
||||
it("sets statusCode and message", () => {
|
||||
const err = new ServiceError(500, "boom");
|
||||
expect(err.statusCode).toBe(500);
|
||||
expect(err.message).toBe("boom");
|
||||
expect(err).toBeInstanceOf(Error);
|
||||
expect(err).toBeInstanceOf(ServiceError);
|
||||
});
|
||||
|
||||
it("supports optional details", () => {
|
||||
const err = new ServiceError(500, "boom", { field: "name" });
|
||||
expect(err.details).toEqual({ field: "name" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("HTTP error classes", () => {
|
||||
const cases: [string, new () => ServiceError, number, string][] = [
|
||||
["BadRequestError", BadRequestError, 400, "Bad request"],
|
||||
["UnauthorizedError", UnauthorizedError, 401, "Unauthorized"],
|
||||
["ForbiddenError", ForbiddenError, 403, "Forbidden"],
|
||||
["NotFoundError", NotFoundError, 404, "Not found"],
|
||||
["ConflictError", ConflictError, 409, "Conflict"],
|
||||
["TooManyRequestsError", TooManyRequestsError, 429, "Too many requests"],
|
||||
];
|
||||
|
||||
for (const [name, Ctor, expectedStatus, expectedMessage] of cases) {
|
||||
it(`${name} has status ${expectedStatus}`, () => {
|
||||
const err = new Ctor();
|
||||
expect(err.statusCode).toBe(expectedStatus);
|
||||
expect(err.message).toBe(expectedMessage);
|
||||
expect(err).toBeInstanceOf(ServiceError);
|
||||
});
|
||||
}
|
||||
|
||||
it("accepts custom message", () => {
|
||||
const err = new NotFoundError("User not found");
|
||||
expect(err.message).toBe("User not found");
|
||||
expect(err.statusCode).toBe(404);
|
||||
});
|
||||
|
||||
it("accepts details", () => {
|
||||
const err = new TooManyRequestsError("Rate limited", { retryAfter: 60 });
|
||||
expect(err.details).toEqual({ retryAfter: 60 });
|
||||
});
|
||||
});
|
||||
37
packages/errors/src/http-errors.ts
Normal file
37
packages/errors/src/http-errors.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import { ServiceError } from "./service-error.js";
|
||||
|
||||
export class BadRequestError extends ServiceError {
|
||||
constructor(message = "Bad request", details?: Record<string, unknown>) {
|
||||
super(400, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends ServiceError {
|
||||
constructor(message = "Unauthorized", details?: Record<string, unknown>) {
|
||||
super(401, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class ForbiddenError extends ServiceError {
|
||||
constructor(message = "Forbidden", details?: Record<string, unknown>) {
|
||||
super(403, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotFoundError extends ServiceError {
|
||||
constructor(message = "Not found", details?: Record<string, unknown>) {
|
||||
super(404, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConflictError extends ServiceError {
|
||||
constructor(message = "Conflict", details?: Record<string, unknown>) {
|
||||
super(409, message, details);
|
||||
}
|
||||
}
|
||||
|
||||
export class TooManyRequestsError extends ServiceError {
|
||||
constructor(message = "Too many requests", details?: Record<string, unknown>) {
|
||||
super(429, message, details);
|
||||
}
|
||||
}
|
||||
9
packages/errors/src/index.ts
Normal file
9
packages/errors/src/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export { ServiceError } from "./service-error.js";
|
||||
export {
|
||||
BadRequestError,
|
||||
UnauthorizedError,
|
||||
ForbiddenError,
|
||||
NotFoundError,
|
||||
ConflictError,
|
||||
TooManyRequestsError,
|
||||
} from "./http-errors.js";
|
||||
14
packages/errors/src/service-error.ts
Normal file
14
packages/errors/src/service-error.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Base error class for typed HTTP service errors.
|
||||
* All specific error types extend this class.
|
||||
*/
|
||||
export class ServiceError extends Error {
|
||||
constructor(
|
||||
public statusCode: number,
|
||||
message: string,
|
||||
public details?: Record<string, unknown>,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "ServiceError";
|
||||
}
|
||||
}
|
||||
9
packages/errors/tsconfig.json
Normal file
9
packages/errors/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src"
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["src/**/*.test.ts"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user