@typokit/errors 0.1.4

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.
@@ -0,0 +1,38 @@
1
+ import type { ErrorResponse } from "@typokit/types";
2
+ /**
3
+ * Base error class for all TypoKit errors.
4
+ * Extends native Error with structured fields for status, code, and details.
5
+ */
6
+ export declare class AppError extends Error {
7
+ readonly code: string;
8
+ readonly status: number;
9
+ readonly details?: Record<string, unknown> | undefined;
10
+ constructor(code: string, status: number, message: string, details?: Record<string, unknown> | undefined);
11
+ /** Serialize to the ErrorResponse schema from @typokit/types */
12
+ toJSON(): ErrorResponse;
13
+ }
14
+ /** 404 Not Found */
15
+ export declare class NotFoundError extends AppError {
16
+ constructor(code: string, message: string, details?: Record<string, unknown>);
17
+ }
18
+ /** 400 Bad Request / Validation */
19
+ export declare class ValidationError extends AppError {
20
+ constructor(code: string, message: string, details?: Record<string, unknown>);
21
+ }
22
+ /** 401 Unauthorized */
23
+ export declare class UnauthorizedError extends AppError {
24
+ constructor(code: string, message: string, details?: Record<string, unknown>);
25
+ }
26
+ /** 403 Forbidden */
27
+ export declare class ForbiddenError extends AppError {
28
+ constructor(code: string, message: string, details?: Record<string, unknown>);
29
+ }
30
+ /** 409 Conflict */
31
+ export declare class ConflictError extends AppError {
32
+ constructor(code: string, message: string, details?: Record<string, unknown>);
33
+ }
34
+ /**
35
+ * Factory function that returns the correct AppError subclass based on status code.
36
+ */
37
+ export declare function createAppError(status: number, code: string, message: string, details?: Record<string, unknown>): AppError;
38
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAEpD;;;GAGG;AACH,qBAAa,QAAS,SAAQ,KAAK;aAEf,IAAI,EAAE,MAAM;aACZ,MAAM,EAAE,MAAM;aAEd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;gBAHjC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EAC9B,OAAO,EAAE,MAAM,EACC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,YAAA;IAOnD,gEAAgE;IAChE,MAAM,IAAI,aAAa;CASxB;AAED,oBAAoB;AACpB,qBAAa,aAAc,SAAQ,QAAQ;gBAEvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKpC;AAED,mCAAmC;AACnC,qBAAa,eAAgB,SAAQ,QAAQ;gBAEzC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKpC;AAED,uBAAuB;AACvB,qBAAa,iBAAkB,SAAQ,QAAQ;gBAE3C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKpC;AAED,oBAAoB;AACpB,qBAAa,cAAe,SAAQ,QAAQ;gBAExC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKpC;AAED,mBAAmB;AACnB,qBAAa,aAAc,SAAQ,QAAQ;gBAEvC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAKpC;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,QAAQ,CAeV"}
package/dist/index.js ADDED
@@ -0,0 +1,83 @@
1
+ // @typokit/errors — Structured Error Class Hierarchy
2
+ /**
3
+ * Base error class for all TypoKit errors.
4
+ * Extends native Error with structured fields for status, code, and details.
5
+ */
6
+ export class AppError extends Error {
7
+ code;
8
+ status;
9
+ details;
10
+ constructor(code, status, message, details) {
11
+ super(message);
12
+ this.code = code;
13
+ this.status = status;
14
+ this.details = details;
15
+ this.name = "AppError";
16
+ Object.setPrototypeOf(this, new.target.prototype);
17
+ }
18
+ /** Serialize to the ErrorResponse schema from @typokit/types */
19
+ toJSON() {
20
+ return {
21
+ error: {
22
+ code: this.code,
23
+ message: this.message,
24
+ ...(this.details !== undefined ? { details: this.details } : {}),
25
+ },
26
+ };
27
+ }
28
+ }
29
+ /** 404 Not Found */
30
+ export class NotFoundError extends AppError {
31
+ constructor(code, message, details) {
32
+ super(code, 404, message, details);
33
+ this.name = "NotFoundError";
34
+ }
35
+ }
36
+ /** 400 Bad Request / Validation */
37
+ export class ValidationError extends AppError {
38
+ constructor(code, message, details) {
39
+ super(code, 400, message, details);
40
+ this.name = "ValidationError";
41
+ }
42
+ }
43
+ /** 401 Unauthorized */
44
+ export class UnauthorizedError extends AppError {
45
+ constructor(code, message, details) {
46
+ super(code, 401, message, details);
47
+ this.name = "UnauthorizedError";
48
+ }
49
+ }
50
+ /** 403 Forbidden */
51
+ export class ForbiddenError extends AppError {
52
+ constructor(code, message, details) {
53
+ super(code, 403, message, details);
54
+ this.name = "ForbiddenError";
55
+ }
56
+ }
57
+ /** 409 Conflict */
58
+ export class ConflictError extends AppError {
59
+ constructor(code, message, details) {
60
+ super(code, 409, message, details);
61
+ this.name = "ConflictError";
62
+ }
63
+ }
64
+ /**
65
+ * Factory function that returns the correct AppError subclass based on status code.
66
+ */
67
+ export function createAppError(status, code, message, details) {
68
+ switch (status) {
69
+ case 400:
70
+ return new ValidationError(code, message, details);
71
+ case 401:
72
+ return new UnauthorizedError(code, message, details);
73
+ case 403:
74
+ return new ForbiddenError(code, message, details);
75
+ case 404:
76
+ return new NotFoundError(code, message, details);
77
+ case 409:
78
+ return new ConflictError(code, message, details);
79
+ default:
80
+ return new AppError(code, status, message, details);
81
+ }
82
+ }
83
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@typokit/errors",
3
+ "exports": {
4
+ ".": {
5
+ "import": "./dist/index.js",
6
+ "types": "./dist/index.d.ts"
7
+ }
8
+ },
9
+ "version": "0.1.4",
10
+ "type": "module",
11
+ "files": [
12
+ "dist",
13
+ "src"
14
+ ],
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "dependencies": {
18
+ "@typokit/types": "0.1.4"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/KyleBastien/typokit",
23
+ "directory": "packages/errors"
24
+ },
25
+ "scripts": {
26
+ "test": "rstest run --passWithNoTests"
27
+ }
28
+ }
@@ -0,0 +1,168 @@
1
+ import { describe, it, expect } from "@rstest/core";
2
+ import {
3
+ AppError,
4
+ NotFoundError,
5
+ ValidationError,
6
+ UnauthorizedError,
7
+ ForbiddenError,
8
+ ConflictError,
9
+ createAppError,
10
+ } from "./index.js";
11
+
12
+ describe("AppError", () => {
13
+ it("should set code, status, message, and details", () => {
14
+ const err = new AppError("TEST_ERROR", 500, "Something failed", {
15
+ key: "value",
16
+ });
17
+ expect(err.code).toBe("TEST_ERROR");
18
+ expect(err.status).toBe(500);
19
+ expect(err.message).toBe("Something failed");
20
+ expect(err.details).toEqual({ key: "value" });
21
+ expect(err.name).toBe("AppError");
22
+ });
23
+
24
+ it("should be an instance of Error", () => {
25
+ const err = new AppError("CODE", 500, "msg");
26
+ expect(err instanceof Error).toBe(true);
27
+ expect(err instanceof AppError).toBe(true);
28
+ });
29
+
30
+ it("should serialize to ErrorResponse JSON", () => {
31
+ const err = new AppError("INTERNAL", 500, "Internal error", { foo: "bar" });
32
+ const json = err.toJSON();
33
+ expect(json).toEqual({
34
+ error: {
35
+ code: "INTERNAL",
36
+ message: "Internal error",
37
+ details: { foo: "bar" },
38
+ },
39
+ });
40
+ });
41
+
42
+ it("should omit details from JSON when undefined", () => {
43
+ const err = new AppError("INTERNAL", 500, "Internal error");
44
+ const json = err.toJSON();
45
+ expect(json).toEqual({
46
+ error: {
47
+ code: "INTERNAL",
48
+ message: "Internal error",
49
+ },
50
+ });
51
+ expect("details" in json.error).toBe(false);
52
+ });
53
+ });
54
+
55
+ describe("NotFoundError", () => {
56
+ it("should have status 404", () => {
57
+ const err = new NotFoundError("NOT_FOUND", "Resource not found");
58
+ expect(err.status).toBe(404);
59
+ expect(err.code).toBe("NOT_FOUND");
60
+ expect(err.message).toBe("Resource not found");
61
+ expect(err.name).toBe("NotFoundError");
62
+ expect(err instanceof AppError).toBe(true);
63
+ });
64
+
65
+ it("should serialize correctly", () => {
66
+ const err = new NotFoundError("USER_NOT_FOUND", "User not found", {
67
+ id: "123",
68
+ });
69
+ expect(err.toJSON()).toEqual({
70
+ error: {
71
+ code: "USER_NOT_FOUND",
72
+ message: "User not found",
73
+ details: { id: "123" },
74
+ },
75
+ });
76
+ });
77
+ });
78
+
79
+ describe("ValidationError", () => {
80
+ it("should have status 400", () => {
81
+ const err = new ValidationError("VALIDATION_FAILED", "Invalid input", {
82
+ field: "email",
83
+ });
84
+ expect(err.status).toBe(400);
85
+ expect(err.code).toBe("VALIDATION_FAILED");
86
+ expect(err.message).toBe("Invalid input");
87
+ expect(err.details).toEqual({ field: "email" });
88
+ expect(err.name).toBe("ValidationError");
89
+ expect(err instanceof AppError).toBe(true);
90
+ });
91
+ });
92
+
93
+ describe("UnauthorizedError", () => {
94
+ it("should have status 401", () => {
95
+ const err = new UnauthorizedError("UNAUTHORIZED", "Not authenticated");
96
+ expect(err.status).toBe(401);
97
+ expect(err.code).toBe("UNAUTHORIZED");
98
+ expect(err.name).toBe("UnauthorizedError");
99
+ expect(err instanceof AppError).toBe(true);
100
+ });
101
+ });
102
+
103
+ describe("ForbiddenError", () => {
104
+ it("should have status 403", () => {
105
+ const err = new ForbiddenError("FORBIDDEN", "Access denied");
106
+ expect(err.status).toBe(403);
107
+ expect(err.code).toBe("FORBIDDEN");
108
+ expect(err.name).toBe("ForbiddenError");
109
+ expect(err instanceof AppError).toBe(true);
110
+ });
111
+ });
112
+
113
+ describe("ConflictError", () => {
114
+ it("should have status 409", () => {
115
+ const err = new ConflictError("CONFLICT", "Already exists");
116
+ expect(err.status).toBe(409);
117
+ expect(err.code).toBe("CONFLICT");
118
+ expect(err.name).toBe("ConflictError");
119
+ expect(err instanceof AppError).toBe(true);
120
+ });
121
+ });
122
+
123
+ describe("createAppError", () => {
124
+ it("should return ValidationError for status 400", () => {
125
+ const err = createAppError(400, "BAD_REQUEST", "Invalid");
126
+ expect(err instanceof ValidationError).toBe(true);
127
+ expect(err.status).toBe(400);
128
+ });
129
+
130
+ it("should return UnauthorizedError for status 401", () => {
131
+ const err = createAppError(401, "UNAUTH", "Unauthorized");
132
+ expect(err instanceof UnauthorizedError).toBe(true);
133
+ expect(err.status).toBe(401);
134
+ });
135
+
136
+ it("should return ForbiddenError for status 403", () => {
137
+ const err = createAppError(403, "FORBIDDEN", "Forbidden");
138
+ expect(err instanceof ForbiddenError).toBe(true);
139
+ expect(err.status).toBe(403);
140
+ });
141
+
142
+ it("should return NotFoundError for status 404", () => {
143
+ const err = createAppError(404, "NOT_FOUND", "Not found");
144
+ expect(err instanceof NotFoundError).toBe(true);
145
+ expect(err.status).toBe(404);
146
+ });
147
+
148
+ it("should return ConflictError for status 409", () => {
149
+ const err = createAppError(409, "CONFLICT", "Conflict");
150
+ expect(err instanceof ConflictError).toBe(true);
151
+ expect(err.status).toBe(409);
152
+ });
153
+
154
+ it("should return generic AppError for other status codes", () => {
155
+ const err = createAppError(503, "SERVICE_UNAVAILABLE", "Down", {
156
+ retry: true,
157
+ });
158
+ expect(err instanceof AppError).toBe(true);
159
+ expect(err.status).toBe(503);
160
+ expect(err.code).toBe("SERVICE_UNAVAILABLE");
161
+ expect(err.details).toEqual({ retry: true });
162
+ });
163
+
164
+ it("should pass details through", () => {
165
+ const err = createAppError(400, "CODE", "msg", { field: "name" });
166
+ expect(err.details).toEqual({ field: "name" });
167
+ });
168
+ });
package/src/index.ts ADDED
@@ -0,0 +1,116 @@
1
+ // @typokit/errors — Structured Error Class Hierarchy
2
+
3
+ import type { ErrorResponse } from "@typokit/types";
4
+
5
+ /**
6
+ * Base error class for all TypoKit errors.
7
+ * Extends native Error with structured fields for status, code, and details.
8
+ */
9
+ export class AppError extends Error {
10
+ constructor(
11
+ public readonly code: string,
12
+ public readonly status: number,
13
+ message: string,
14
+ public readonly details?: Record<string, unknown>,
15
+ ) {
16
+ super(message);
17
+ this.name = "AppError";
18
+ Object.setPrototypeOf(this, new.target.prototype);
19
+ }
20
+
21
+ /** Serialize to the ErrorResponse schema from @typokit/types */
22
+ toJSON(): ErrorResponse {
23
+ return {
24
+ error: {
25
+ code: this.code,
26
+ message: this.message,
27
+ ...(this.details !== undefined ? { details: this.details } : {}),
28
+ },
29
+ };
30
+ }
31
+ }
32
+
33
+ /** 404 Not Found */
34
+ export class NotFoundError extends AppError {
35
+ constructor(
36
+ code: string,
37
+ message: string,
38
+ details?: Record<string, unknown>,
39
+ ) {
40
+ super(code, 404, message, details);
41
+ this.name = "NotFoundError";
42
+ }
43
+ }
44
+
45
+ /** 400 Bad Request / Validation */
46
+ export class ValidationError extends AppError {
47
+ constructor(
48
+ code: string,
49
+ message: string,
50
+ details?: Record<string, unknown>,
51
+ ) {
52
+ super(code, 400, message, details);
53
+ this.name = "ValidationError";
54
+ }
55
+ }
56
+
57
+ /** 401 Unauthorized */
58
+ export class UnauthorizedError extends AppError {
59
+ constructor(
60
+ code: string,
61
+ message: string,
62
+ details?: Record<string, unknown>,
63
+ ) {
64
+ super(code, 401, message, details);
65
+ this.name = "UnauthorizedError";
66
+ }
67
+ }
68
+
69
+ /** 403 Forbidden */
70
+ export class ForbiddenError extends AppError {
71
+ constructor(
72
+ code: string,
73
+ message: string,
74
+ details?: Record<string, unknown>,
75
+ ) {
76
+ super(code, 403, message, details);
77
+ this.name = "ForbiddenError";
78
+ }
79
+ }
80
+
81
+ /** 409 Conflict */
82
+ export class ConflictError extends AppError {
83
+ constructor(
84
+ code: string,
85
+ message: string,
86
+ details?: Record<string, unknown>,
87
+ ) {
88
+ super(code, 409, message, details);
89
+ this.name = "ConflictError";
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Factory function that returns the correct AppError subclass based on status code.
95
+ */
96
+ export function createAppError(
97
+ status: number,
98
+ code: string,
99
+ message: string,
100
+ details?: Record<string, unknown>,
101
+ ): AppError {
102
+ switch (status) {
103
+ case 400:
104
+ return new ValidationError(code, message, details);
105
+ case 401:
106
+ return new UnauthorizedError(code, message, details);
107
+ case 403:
108
+ return new ForbiddenError(code, message, details);
109
+ case 404:
110
+ return new NotFoundError(code, message, details);
111
+ case 409:
112
+ return new ConflictError(code, message, details);
113
+ default:
114
+ return new AppError(code, status, message, details);
115
+ }
116
+ }