@htlkg/core 0.0.2 → 0.0.3

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.
Files changed (35) hide show
  1. package/README.md +51 -0
  2. package/dist/index.d.ts +219 -0
  3. package/dist/index.js +121 -0
  4. package/dist/index.js.map +1 -1
  5. package/package.json +30 -8
  6. package/src/amplify-astro-adapter/amplify-astro-adapter.md +167 -0
  7. package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.test.ts +296 -0
  8. package/src/amplify-astro-adapter/createCookieStorageAdapterFromAstroContext.ts +97 -0
  9. package/src/amplify-astro-adapter/createRunWithAmplifyServerContext.ts +84 -0
  10. package/src/amplify-astro-adapter/errors.test.ts +115 -0
  11. package/src/amplify-astro-adapter/errors.ts +105 -0
  12. package/src/amplify-astro-adapter/globalSettings.test.ts +78 -0
  13. package/src/amplify-astro-adapter/globalSettings.ts +16 -0
  14. package/src/amplify-astro-adapter/index.ts +14 -0
  15. package/src/amplify-astro-adapter/types.ts +55 -0
  16. package/src/auth/auth.md +178 -0
  17. package/src/auth/index.test.ts +180 -0
  18. package/src/auth/index.ts +294 -0
  19. package/src/constants/constants.md +132 -0
  20. package/src/constants/index.test.ts +116 -0
  21. package/src/constants/index.ts +98 -0
  22. package/src/core-exports.property.test.ts +186 -0
  23. package/src/errors/errors.md +177 -0
  24. package/src/errors/index.test.ts +153 -0
  25. package/src/errors/index.ts +134 -0
  26. package/src/index.ts +65 -0
  27. package/src/routes/index.ts +225 -0
  28. package/src/routes/routes.md +189 -0
  29. package/src/types/index.ts +94 -0
  30. package/src/types/types.md +144 -0
  31. package/src/utils/index.test.ts +257 -0
  32. package/src/utils/index.ts +112 -0
  33. package/src/utils/logger.ts +88 -0
  34. package/src/utils/utils.md +199 -0
  35. package/src/workspace.property.test.ts +235 -0
@@ -0,0 +1,116 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { ROUTES, PRODUCTS, PERMISSIONS, USER_ROLES } from "./index";
3
+
4
+ describe("Constants", () => {
5
+ describe("ROUTES", () => {
6
+ it("should have admin routes", () => {
7
+ expect(ROUTES.ADMIN.HOME).toBe("/admin");
8
+ expect(ROUTES.ADMIN.BRANDS).toBe("/admin/brands");
9
+ expect(ROUTES.ADMIN.ACCOUNTS).toBe("/admin/accounts");
10
+ expect(ROUTES.ADMIN.USERS).toBe("/admin/users");
11
+ expect(ROUTES.ADMIN.PRODUCTS).toBe("/admin/products");
12
+ expect(ROUTES.ADMIN.SETTINGS).toBe("/admin/settings");
13
+ });
14
+
15
+ it("should have brand route functions", () => {
16
+ expect(ROUTES.BRAND.HOME("test-brand")).toBe("/test-brand");
17
+ expect(ROUTES.BRAND.ADMIN("test-brand")).toBe("/test-brand/admin");
18
+ expect(ROUTES.BRAND.TEMPLATES("test-brand")).toBe(
19
+ "/test-brand/admin/templates",
20
+ );
21
+ expect(ROUTES.BRAND.SETTINGS("test-brand")).toBe(
22
+ "/test-brand/admin/settings",
23
+ );
24
+ });
25
+
26
+ it("should have auth routes", () => {
27
+ expect(ROUTES.AUTH.LOGIN).toBe("/login");
28
+ expect(ROUTES.AUTH.LOGOUT).toBe("/logout");
29
+ expect(ROUTES.AUTH.SIGNUP).toBe("/signup");
30
+ expect(ROUTES.AUTH.FORGOT_PASSWORD).toBe("/forgot-password");
31
+ });
32
+
33
+ it("should be defined as const", () => {
34
+ // Note: TypeScript 'as const' provides compile-time immutability
35
+ // but doesn't prevent runtime mutations. This test just verifies the structure exists.
36
+ expect(ROUTES.ADMIN).toBeDefined();
37
+ });
38
+ });
39
+
40
+ describe("PRODUCTS", () => {
41
+ it("should have WiFi Portal product", () => {
42
+ expect(PRODUCTS.WIFI_PORTAL.id).toBe("wifi-portal");
43
+ expect(PRODUCTS.WIFI_PORTAL.name).toBe("WiFi Portal");
44
+ expect(PRODUCTS.WIFI_PORTAL.description).toContain("Captive portal");
45
+ });
46
+
47
+ it("should have WhatsApp CRM product", () => {
48
+ expect(PRODUCTS.WHATSAPP_CRM.id).toBe("whatsapp-crm");
49
+ expect(PRODUCTS.WHATSAPP_CRM.name).toBe("WhatsApp CRM");
50
+ expect(PRODUCTS.WHATSAPP_CRM.description).toContain("WhatsApp");
51
+ });
52
+
53
+ it("should have Analytics product", () => {
54
+ expect(PRODUCTS.ANALYTICS.id).toBe("analytics");
55
+ expect(PRODUCTS.ANALYTICS.name).toBe("Analytics");
56
+ expect(PRODUCTS.ANALYTICS.description).toContain("Analytics");
57
+ });
58
+
59
+ it("should be defined as const", () => {
60
+ expect(PRODUCTS.WIFI_PORTAL).toBeDefined();
61
+ });
62
+ });
63
+
64
+ describe("PERMISSIONS", () => {
65
+ it("should have brand permissions", () => {
66
+ expect(PERMISSIONS.BRAND_VIEW).toBe("brand:view");
67
+ expect(PERMISSIONS.BRAND_EDIT).toBe("brand:edit");
68
+ expect(PERMISSIONS.BRAND_DELETE).toBe("brand:delete");
69
+ expect(PERMISSIONS.BRAND_CREATE).toBe("brand:create");
70
+ });
71
+
72
+ it("should have account permissions", () => {
73
+ expect(PERMISSIONS.ACCOUNT_VIEW).toBe("account:view");
74
+ expect(PERMISSIONS.ACCOUNT_EDIT).toBe("account:edit");
75
+ expect(PERMISSIONS.ACCOUNT_DELETE).toBe("account:delete");
76
+ expect(PERMISSIONS.ACCOUNT_CREATE).toBe("account:create");
77
+ });
78
+
79
+ it("should have user permissions", () => {
80
+ expect(PERMISSIONS.USER_VIEW).toBe("user:view");
81
+ expect(PERMISSIONS.USER_EDIT).toBe("user:edit");
82
+ expect(PERMISSIONS.USER_DELETE).toBe("user:delete");
83
+ expect(PERMISSIONS.USER_CREATE).toBe("user:create");
84
+ });
85
+
86
+ it("should have product permissions", () => {
87
+ expect(PERMISSIONS.PRODUCT_VIEW).toBe("product:view");
88
+ expect(PERMISSIONS.PRODUCT_EDIT).toBe("product:edit");
89
+ expect(PERMISSIONS.PRODUCT_ENABLE).toBe("product:enable");
90
+ expect(PERMISSIONS.PRODUCT_DISABLE).toBe("product:disable");
91
+ });
92
+
93
+ it("should have admin permissions", () => {
94
+ expect(PERMISSIONS.ADMIN_ACCESS).toBe("admin:access");
95
+ expect(PERMISSIONS.SUPER_ADMIN_ACCESS).toBe("super_admin:access");
96
+ });
97
+
98
+ it("should be defined as const", () => {
99
+ expect(PERMISSIONS.BRAND_VIEW).toBeDefined();
100
+ });
101
+ });
102
+
103
+ describe("USER_ROLES", () => {
104
+ it("should have all user roles", () => {
105
+ expect(USER_ROLES.SUPER_ADMIN).toBe("SUPER_ADMINS");
106
+ expect(USER_ROLES.ADMIN).toBe("ADMINS");
107
+ expect(USER_ROLES.BRAND_ADMIN).toBe("BRAND_ADMINS");
108
+ expect(USER_ROLES.BRAND_USER).toBe("BRAND_USERS");
109
+ expect(USER_ROLES.USER).toBe("USERS");
110
+ });
111
+
112
+ it("should be defined as const", () => {
113
+ expect(USER_ROLES.SUPER_ADMIN).toBeDefined();
114
+ });
115
+ });
116
+ });
@@ -0,0 +1,98 @@
1
+ /**
2
+ * @htlkg/core/constants
3
+ * Core constants for Hotelinking applications
4
+ */
5
+
6
+ /**
7
+ * Application routes
8
+ */
9
+ export const ROUTES = {
10
+ // Admin routes
11
+ ADMIN: {
12
+ HOME: "/admin",
13
+ BRANDS: "/admin/brands",
14
+ ACCOUNTS: "/admin/accounts",
15
+ USERS: "/admin/users",
16
+ PRODUCTS: "/admin/products",
17
+ SETTINGS: "/admin/settings",
18
+ },
19
+ // Brand routes
20
+ BRAND: {
21
+ HOME: (brandId: string) => `/${brandId}`,
22
+ ADMIN: (brandId: string) => `/${brandId}/admin`,
23
+ TEMPLATES: (brandId: string) => `/${brandId}/admin/templates`,
24
+ SETTINGS: (brandId: string) => `/${brandId}/admin/settings`,
25
+ },
26
+ // Auth routes
27
+ AUTH: {
28
+ LOGIN: "/login",
29
+ LOGOUT: "/logout",
30
+ SIGNUP: "/signup",
31
+ FORGOT_PASSWORD: "/forgot-password",
32
+ },
33
+ } as const;
34
+
35
+ /**
36
+ * Product definitions
37
+ */
38
+ export const PRODUCTS = {
39
+ WIFI_PORTAL: {
40
+ id: "wifi-portal",
41
+ name: "WiFi Portal",
42
+ description: "Captive portal for WiFi authentication",
43
+ },
44
+ WHATSAPP_CRM: {
45
+ id: "whatsapp-crm",
46
+ name: "WhatsApp CRM",
47
+ description: "WhatsApp integration for customer relationship management",
48
+ },
49
+ ANALYTICS: {
50
+ id: "analytics",
51
+ name: "Analytics",
52
+ description: "Analytics and reporting dashboard",
53
+ },
54
+ } as const;
55
+
56
+ /**
57
+ * Permission definitions
58
+ */
59
+ export const PERMISSIONS = {
60
+ // Brand permissions
61
+ BRAND_VIEW: "brand:view",
62
+ BRAND_EDIT: "brand:edit",
63
+ BRAND_DELETE: "brand:delete",
64
+ BRAND_CREATE: "brand:create",
65
+
66
+ // Account permissions
67
+ ACCOUNT_VIEW: "account:view",
68
+ ACCOUNT_EDIT: "account:edit",
69
+ ACCOUNT_DELETE: "account:delete",
70
+ ACCOUNT_CREATE: "account:create",
71
+
72
+ // User permissions
73
+ USER_VIEW: "user:view",
74
+ USER_EDIT: "user:edit",
75
+ USER_DELETE: "user:delete",
76
+ USER_CREATE: "user:create",
77
+
78
+ // Product permissions
79
+ PRODUCT_VIEW: "product:view",
80
+ PRODUCT_EDIT: "product:edit",
81
+ PRODUCT_ENABLE: "product:enable",
82
+ PRODUCT_DISABLE: "product:disable",
83
+
84
+ // Admin permissions
85
+ ADMIN_ACCESS: "admin:access",
86
+ SUPER_ADMIN_ACCESS: "super_admin:access",
87
+ } as const;
88
+
89
+ /**
90
+ * User role definitions
91
+ */
92
+ export const USER_ROLES = {
93
+ SUPER_ADMIN: "SUPER_ADMINS",
94
+ ADMIN: "ADMINS",
95
+ BRAND_ADMIN: "BRAND_ADMINS",
96
+ BRAND_USER: "BRAND_USERS",
97
+ USER: "USERS",
98
+ } as const;
@@ -0,0 +1,186 @@
1
+ /**
2
+ * Property-based tests for @htlkg/core module exports
3
+ * Feature: htlkg-modular-architecture, Property 1: Module exports are accessible
4
+ * Validates: Requirements 2.1, 2.2, 2.3, 2.4, 2.5
5
+ */
6
+ import { describe, it, expect } from "vitest";
7
+
8
+ describe("Property 1: Module exports are accessible", () => {
9
+ it("should export all auth functions from main module", async () => {
10
+ // Import from main module
11
+ const mainModule = await import("./index.js");
12
+ expect(mainModule.getUser).toBeDefined();
13
+ expect(mainModule.getClientUser).toBeDefined();
14
+ expect(mainModule.hasAccessToBrand).toBeDefined();
15
+ expect(mainModule.hasAccessToAccount).toBeDefined();
16
+ expect(mainModule.isAdminUser).toBeDefined();
17
+ expect(mainModule.isSuperAdminUser).toBeDefined();
18
+ expect(mainModule.requireAuth).toBeDefined();
19
+ expect(mainModule.requireAdminAccess).toBeDefined();
20
+ expect(mainModule.requireBrandAccess).toBeDefined();
21
+ });
22
+
23
+ it("should export all auth functions from auth subpath", async () => {
24
+ // Import from auth subpath
25
+ const authModule = await import("./auth/index.js");
26
+ expect(authModule.getUser).toBeDefined();
27
+ expect(authModule.getClientUser).toBeDefined();
28
+ expect(authModule.hasAccessToBrand).toBeDefined();
29
+ expect(authModule.hasAccessToAccount).toBeDefined();
30
+ expect(authModule.isAdminUser).toBeDefined();
31
+ expect(authModule.isSuperAdminUser).toBeDefined();
32
+ expect(authModule.requireAuth).toBeDefined();
33
+ expect(authModule.requireAdminAccess).toBeDefined();
34
+ expect(authModule.requireBrandAccess).toBeDefined();
35
+ });
36
+
37
+ it("should export all utility functions from main module", async () => {
38
+ const mainModule = await import("./index.js");
39
+ expect(mainModule.formatDate).toBeDefined();
40
+ expect(mainModule.truncateText).toBeDefined();
41
+ expect(mainModule.groupBy).toBeDefined();
42
+ expect(mainModule.debounce).toBeDefined();
43
+ expect(mainModule.throttle).toBeDefined();
44
+ });
45
+
46
+ it("should export all utility functions from utils subpath", async () => {
47
+ const utilsModule = await import("./utils/index.js");
48
+ expect(utilsModule.formatDate).toBeDefined();
49
+ expect(utilsModule.truncateText).toBeDefined();
50
+ expect(utilsModule.groupBy).toBeDefined();
51
+ expect(utilsModule.debounce).toBeDefined();
52
+ expect(utilsModule.throttle).toBeDefined();
53
+ });
54
+
55
+ it("should export all constants from main module", async () => {
56
+ const mainModule = await import("./index.js");
57
+ expect(mainModule.ROUTES).toBeDefined();
58
+ expect(mainModule.PRODUCTS).toBeDefined();
59
+ expect(mainModule.PERMISSIONS).toBeDefined();
60
+ expect(mainModule.USER_ROLES).toBeDefined();
61
+ });
62
+
63
+ it("should export all constants from constants subpath", async () => {
64
+ const constantsModule = await import("./constants/index.js");
65
+ expect(constantsModule.ROUTES).toBeDefined();
66
+ expect(constantsModule.PRODUCTS).toBeDefined();
67
+ expect(constantsModule.PERMISSIONS).toBeDefined();
68
+ expect(constantsModule.USER_ROLES).toBeDefined();
69
+ });
70
+
71
+ it("should export all error classes from main module", async () => {
72
+ const mainModule = await import("./index.js");
73
+ expect(mainModule.AppError).toBeDefined();
74
+ expect(mainModule.AuthError).toBeDefined();
75
+ expect(mainModule.ValidationError).toBeDefined();
76
+ expect(mainModule.NotFoundError).toBeDefined();
77
+ });
78
+
79
+ it("should export all error classes from errors subpath", async () => {
80
+ const errorsModule = await import("./errors/index.js");
81
+ expect(errorsModule.AppError).toBeDefined();
82
+ expect(errorsModule.AuthError).toBeDefined();
83
+ expect(errorsModule.ValidationError).toBeDefined();
84
+ expect(errorsModule.NotFoundError).toBeDefined();
85
+ });
86
+
87
+ it("should have working utility functions for various inputs", async () => {
88
+ const { formatDate, truncateText, groupBy } = await import("./index.js");
89
+
90
+ // Test with multiple different inputs to verify the property holds
91
+ const testCases = [
92
+ {
93
+ date: new Date("2024-01-15"),
94
+ text: "Hello World",
95
+ items: [
96
+ { id: 1, category: "A" },
97
+ { id: 2, category: "B" },
98
+ ],
99
+ },
100
+ {
101
+ date: new Date("2023-12-25"),
102
+ text: "Short",
103
+ items: [{ id: 1, category: "A" }],
104
+ },
105
+ {
106
+ date: new Date(),
107
+ text: "A very long text that should be truncated properly",
108
+ items: [],
109
+ },
110
+ ];
111
+
112
+ for (const testCase of testCases) {
113
+ // formatDate should not throw
114
+ expect(() => formatDate(testCase.date)).not.toThrow();
115
+
116
+ // truncateText should not throw
117
+ expect(() => truncateText(testCase.text, 50)).not.toThrow();
118
+
119
+ // groupBy should not throw
120
+ expect(() =>
121
+ groupBy(testCase.items, (item) => item.category),
122
+ ).not.toThrow();
123
+ }
124
+ });
125
+
126
+ it("should have working error classes for various inputs", async () => {
127
+ const { AppError, AuthError, ValidationError, NotFoundError } =
128
+ await import("./index.js");
129
+
130
+ const testCases = [
131
+ { message: "Error 1", code: "CODE1", statusCode: 400 },
132
+ { message: "Error 2", code: "CODE2", statusCode: 500 },
133
+ { message: "Error 3", code: "CODE3", statusCode: 404 },
134
+ ];
135
+
136
+ for (const testCase of testCases) {
137
+ // All error classes should be instantiable
138
+ const appError = new AppError(
139
+ testCase.message,
140
+ testCase.code,
141
+ testCase.statusCode,
142
+ );
143
+ expect(appError).toBeInstanceOf(Error);
144
+ expect(appError.message).toBe(testCase.message);
145
+
146
+ const authError = new AuthError(
147
+ testCase.message,
148
+ testCase.code,
149
+ testCase.statusCode,
150
+ );
151
+ expect(authError).toBeInstanceOf(Error);
152
+ expect(authError.message).toBe(testCase.message);
153
+
154
+ const validationError = new ValidationError(testCase.message);
155
+ expect(validationError).toBeInstanceOf(Error);
156
+ expect(validationError.message).toBe(testCase.message);
157
+
158
+ const notFoundError = new NotFoundError("Resource", "123");
159
+ expect(notFoundError).toBeInstanceOf(Error);
160
+ }
161
+ });
162
+
163
+ it("should have constants that are defined and accessible", async () => {
164
+ const { ROUTES, PRODUCTS, PERMISSIONS, USER_ROLES } = await import(
165
+ "./index.js"
166
+ );
167
+
168
+ // Constants should be defined and have expected structure
169
+ expect(ROUTES).toBeDefined();
170
+ expect(ROUTES.ADMIN).toBeDefined();
171
+ expect(ROUTES.BRAND).toBeDefined();
172
+ expect(ROUTES.AUTH).toBeDefined();
173
+
174
+ expect(PRODUCTS).toBeDefined();
175
+ expect(PRODUCTS.WIFI_PORTAL).toBeDefined();
176
+ expect(PRODUCTS.WHATSAPP_CRM).toBeDefined();
177
+
178
+ expect(PERMISSIONS).toBeDefined();
179
+ expect(PERMISSIONS.BRAND_VIEW).toBeDefined();
180
+ expect(PERMISSIONS.ADMIN_ACCESS).toBeDefined();
181
+
182
+ expect(USER_ROLES).toBeDefined();
183
+ expect(USER_ROLES.SUPER_ADMIN).toBeDefined();
184
+ expect(USER_ROLES.ADMIN).toBeDefined();
185
+ });
186
+ });
@@ -0,0 +1,177 @@
1
+ # Errors Module
2
+
3
+ Standardized error classes for consistent error handling across applications.
4
+
5
+ ## Installation
6
+
7
+ ```typescript
8
+ import {
9
+ AppError,
10
+ AuthError,
11
+ ValidationError,
12
+ NotFoundError
13
+ } from '@htlkg/core/errors';
14
+ ```
15
+
16
+ ## Error Classes
17
+
18
+ ### AppError
19
+
20
+ Base error class for all application errors.
21
+
22
+ ```typescript
23
+ class AppError extends Error {
24
+ code: string;
25
+ statusCode: number;
26
+ details?: Record<string, any>;
27
+ }
28
+ ```
29
+
30
+ ```typescript
31
+ throw new AppError(
32
+ 'Something went wrong',
33
+ 'CUSTOM_ERROR', // code
34
+ 500, // statusCode
35
+ { context: 'data' } // details
36
+ );
37
+ ```
38
+
39
+ ### AuthError
40
+
41
+ Authentication and authorization errors.
42
+
43
+ ```typescript
44
+ class AuthError extends AppError {
45
+ // Defaults: code = 'AUTH_ERROR', statusCode = 401
46
+ }
47
+ ```
48
+
49
+ ```typescript
50
+ throw new AuthError('Invalid credentials');
51
+ throw new AuthError('Token expired', 'TOKEN_EXPIRED', 401);
52
+ throw new AuthError('Insufficient permissions', 'FORBIDDEN', 403);
53
+ ```
54
+
55
+ ### ValidationError
56
+
57
+ Input validation errors with field-level details.
58
+
59
+ ```typescript
60
+ class ValidationError extends AppError {
61
+ errors: Array<{ field: string; message: string }>;
62
+ // Defaults: code = 'VALIDATION_ERROR', statusCode = 400
63
+ }
64
+ ```
65
+
66
+ ```typescript
67
+ throw new ValidationError('Validation failed', [
68
+ { field: 'email', message: 'Invalid email format' },
69
+ { field: 'password', message: 'Password too short' },
70
+ ]);
71
+ ```
72
+
73
+ ### NotFoundError
74
+
75
+ Resource not found errors.
76
+
77
+ ```typescript
78
+ class NotFoundError extends AppError {
79
+ resource: string;
80
+ resourceId?: string;
81
+ // Defaults: code = 'NOT_FOUND', statusCode = 404
82
+ }
83
+ ```
84
+
85
+ ```typescript
86
+ throw new NotFoundError('Brand', '123');
87
+ // Message: "Brand with id '123' not found"
88
+
89
+ throw new NotFoundError('User');
90
+ // Message: "User not found"
91
+
92
+ throw new NotFoundError('Brand', '123', 'Custom message');
93
+ ```
94
+
95
+ ## JSON Serialization
96
+
97
+ All errors support `toJSON()` for API responses:
98
+
99
+ ```typescript
100
+ const error = new ValidationError('Invalid input', [
101
+ { field: 'email', message: 'Required' }
102
+ ]);
103
+
104
+ console.log(JSON.stringify(error));
105
+ // {
106
+ // "name": "ValidationError",
107
+ // "message": "Invalid input",
108
+ // "code": "VALIDATION_ERROR",
109
+ // "statusCode": 400,
110
+ // "details": { "errors": [...] },
111
+ // "errors": [{ "field": "email", "message": "Required" }]
112
+ // }
113
+ ```
114
+
115
+ ## API Route Usage
116
+
117
+ ```typescript
118
+ import type { APIContext } from 'astro';
119
+ import { NotFoundError, ValidationError, AuthError } from '@htlkg/core/errors';
120
+
121
+ export async function GET({ params }: APIContext) {
122
+ try {
123
+ const brand = await getBrand(params.id);
124
+
125
+ if (!brand) {
126
+ throw new NotFoundError('Brand', params.id);
127
+ }
128
+
129
+ return new Response(JSON.stringify(brand));
130
+ } catch (error) {
131
+ if (error instanceof AppError) {
132
+ return new Response(JSON.stringify(error.toJSON()), {
133
+ status: error.statusCode,
134
+ headers: { 'Content-Type': 'application/json' },
135
+ });
136
+ }
137
+
138
+ // Unknown error
139
+ return new Response(JSON.stringify({ error: 'Internal server error' }), {
140
+ status: 500,
141
+ });
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Error Handling Middleware
147
+
148
+ ```typescript
149
+ function handleError(error: unknown): Response {
150
+ if (error instanceof AuthError) {
151
+ return new Response(JSON.stringify(error.toJSON()), { status: 401 });
152
+ }
153
+
154
+ if (error instanceof ValidationError) {
155
+ return new Response(JSON.stringify({
156
+ message: error.message,
157
+ errors: error.errors,
158
+ }), { status: 400 });
159
+ }
160
+
161
+ if (error instanceof NotFoundError) {
162
+ return new Response(JSON.stringify({
163
+ message: error.message,
164
+ resource: error.resource,
165
+ }), { status: 404 });
166
+ }
167
+
168
+ if (error instanceof AppError) {
169
+ return new Response(JSON.stringify(error.toJSON()), {
170
+ status: error.statusCode
171
+ });
172
+ }
173
+
174
+ // Fallback
175
+ return new Response('Internal Server Error', { status: 500 });
176
+ }
177
+ ```