@bloomneo/appkit 1.5.1 โ 1.5.2
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/AGENTS.md +195 -0
- package/CHANGELOG.md +253 -0
- package/README.md +147 -799
- package/bin/commands/generate.js +7 -7
- package/cookbook/README.md +26 -0
- package/cookbook/api-key-service.ts +106 -0
- package/cookbook/auth-protected-crud.ts +112 -0
- package/cookbook/file-upload-pipeline.ts +113 -0
- package/cookbook/multi-tenant-saas.ts +87 -0
- package/cookbook/real-time-chat.ts +121 -0
- package/dist/auth/auth.d.ts +21 -4
- package/dist/auth/auth.d.ts.map +1 -1
- package/dist/auth/auth.js +56 -44
- package/dist/auth/auth.js.map +1 -1
- package/dist/auth/defaults.d.ts +1 -1
- package/dist/auth/defaults.js +35 -35
- package/dist/cache/cache.d.ts +29 -6
- package/dist/cache/cache.d.ts.map +1 -1
- package/dist/cache/cache.js +72 -44
- package/dist/cache/cache.js.map +1 -1
- package/dist/cache/defaults.js +25 -25
- package/dist/cache/index.d.ts +19 -10
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +21 -18
- package/dist/cache/index.js.map +1 -1
- package/dist/config/defaults.d.ts +1 -1
- package/dist/config/defaults.js +8 -8
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.js +4 -4
- package/dist/database/adapters/mongoose.js +2 -2
- package/dist/database/adapters/prisma.js +2 -2
- package/dist/database/defaults.d.ts +1 -1
- package/dist/database/defaults.js +4 -4
- package/dist/database/index.js +2 -2
- package/dist/database/index.js.map +1 -1
- package/dist/email/defaults.js +20 -20
- package/dist/error/defaults.d.ts +1 -1
- package/dist/error/defaults.js +12 -12
- package/dist/error/error.d.ts +12 -0
- package/dist/error/error.d.ts.map +1 -1
- package/dist/error/error.js +19 -0
- package/dist/error/error.js.map +1 -1
- package/dist/error/index.d.ts +14 -3
- package/dist/error/index.d.ts.map +1 -1
- package/dist/error/index.js +14 -3
- package/dist/error/index.js.map +1 -1
- package/dist/event/defaults.js +30 -30
- package/dist/logger/defaults.d.ts +1 -1
- package/dist/logger/defaults.js +40 -40
- package/dist/logger/index.d.ts +1 -0
- package/dist/logger/index.d.ts.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/logger/logger.d.ts +8 -0
- package/dist/logger/logger.d.ts.map +1 -1
- package/dist/logger/logger.js +13 -3
- package/dist/logger/logger.js.map +1 -1
- package/dist/logger/transports/console.js +1 -1
- package/dist/logger/transports/http.d.ts +1 -1
- package/dist/logger/transports/http.js +1 -1
- package/dist/logger/transports/webhook.d.ts +1 -1
- package/dist/logger/transports/webhook.js +1 -1
- package/dist/queue/defaults.d.ts +2 -2
- package/dist/queue/defaults.js +38 -38
- package/dist/security/defaults.d.ts +1 -1
- package/dist/security/defaults.js +29 -29
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +3 -3
- package/dist/security/security.d.ts +1 -1
- package/dist/security/security.js +4 -4
- package/dist/storage/defaults.js +19 -19
- package/dist/util/defaults.d.ts +1 -1
- package/dist/util/defaults.js +34 -34
- package/dist/util/env.d.ts +35 -0
- package/dist/util/env.d.ts.map +1 -0
- package/dist/util/env.js +50 -0
- package/dist/util/env.js.map +1 -0
- package/dist/util/errors.d.ts +52 -0
- package/dist/util/errors.d.ts.map +1 -0
- package/dist/util/errors.js +82 -0
- package/dist/util/errors.js.map +1 -0
- package/examples/.env.example +80 -0
- package/examples/README.md +16 -0
- package/examples/auth.ts +228 -0
- package/examples/cache.ts +36 -0
- package/examples/config.ts +45 -0
- package/examples/database.ts +69 -0
- package/examples/email.ts +53 -0
- package/examples/error.ts +50 -0
- package/examples/event.ts +42 -0
- package/examples/logger.ts +41 -0
- package/examples/queue.ts +58 -0
- package/examples/security.ts +46 -0
- package/examples/storage.ts +44 -0
- package/examples/util.ts +47 -0
- package/llms.txt +591 -0
- package/package.json +19 -10
- package/src/auth/README.md +850 -0
- package/src/cache/README.md +756 -0
- package/src/config/README.md +604 -0
- package/src/database/README.md +818 -0
- package/src/email/README.md +759 -0
- package/src/error/README.md +660 -0
- package/src/event/README.md +729 -0
- package/src/logger/README.md +435 -0
- package/src/queue/README.md +851 -0
- package/src/security/README.md +612 -0
- package/src/storage/README.md +1008 -0
- package/src/util/README.md +955 -0
- package/bin/templates/backend/docs/APPKIT_CLI.md +0 -507
- package/bin/templates/backend/docs/APPKIT_COMMENTS_GUIDELINES.md +0 -61
- package/bin/templates/backend/docs/APPKIT_LLM_GUIDE.md +0 -2539
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
# @bloomneo/appkit - Error Module โ ๏ธ
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@bloomneo/appkit)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
> Ultra-simple semantic error handling with HTTP status codes, Express
|
|
7
|
+
> middleware, and environment-aware smart defaults.
|
|
8
|
+
|
|
9
|
+
**One function** returns an error object with semantic methods. Built-in
|
|
10
|
+
middleware handles everything automatically. Works with any Express-compatible
|
|
11
|
+
framework.
|
|
12
|
+
|
|
13
|
+
## ๐ Why Choose This?
|
|
14
|
+
|
|
15
|
+
- **โก One Function** - Just `error.get()`, everything else is automatic
|
|
16
|
+
- **๐ฏ Semantic HTTP Codes** - `badRequest(400)`, `unauthorized(401)`,
|
|
17
|
+
`notFound(404)`
|
|
18
|
+
- **๐ง Zero Configuration** - Smart defaults for development vs production
|
|
19
|
+
- **๐ Environment-First** - Auto-detects dev/prod behavior
|
|
20
|
+
- **๐ก๏ธ Production-Safe** - Hides stack traces and sensitive info in production
|
|
21
|
+
- **๐ Framework Agnostic** - Express, Fastify, Koa, any Node.js framework
|
|
22
|
+
- **๐ค AI-Ready** - Optimized for LLM code generation
|
|
23
|
+
|
|
24
|
+
## ๐ฆ Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @bloomneo/appkit
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## ๐โโ๏ธ Quick Start (30 seconds)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import express from 'express';
|
|
34
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
35
|
+
|
|
36
|
+
const app = express();
|
|
37
|
+
const error = errorClass.get();
|
|
38
|
+
|
|
39
|
+
// Setup (must be last middleware)
|
|
40
|
+
app.use(error.handleErrors());
|
|
41
|
+
|
|
42
|
+
// Create semantic errors
|
|
43
|
+
app.post(
|
|
44
|
+
'/users',
|
|
45
|
+
error.asyncRoute(async (req, res) => {
|
|
46
|
+
if (!req.body.email) throw error.badRequest('Email required');
|
|
47
|
+
if (!req.body.password) throw error.badRequest('Password required');
|
|
48
|
+
|
|
49
|
+
const existingUser = await findUser(req.body.email);
|
|
50
|
+
if (existingUser) throw error.conflict('Email already exists');
|
|
51
|
+
|
|
52
|
+
const user = await createUser(req.body);
|
|
53
|
+
res.json({ user });
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**That's it!** Semantic errors with automatic middleware handling.
|
|
59
|
+
|
|
60
|
+
## ๐ค LLM Quick Reference - Copy These Patterns
|
|
61
|
+
|
|
62
|
+
### **Error Creation (Copy Exactly)**
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// โ
CORRECT - Use semantic methods
|
|
66
|
+
throw error.badRequest('Email is required');
|
|
67
|
+
throw error.unauthorized('Login required');
|
|
68
|
+
throw error.forbidden('Admin access required');
|
|
69
|
+
throw error.notFound('User not found');
|
|
70
|
+
throw error.conflict('Email already exists');
|
|
71
|
+
throw error.serverError('Database unavailable');
|
|
72
|
+
|
|
73
|
+
// โ WRONG - Manual status codes
|
|
74
|
+
res.status(400).json({ error: 'Bad request' }); // Don't do this
|
|
75
|
+
throw new Error('Something went wrong'); // No status code
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### **Framework Setup (Copy Exactly)**
|
|
79
|
+
|
|
80
|
+
#### **Express Setup**
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// โ
CORRECT - Express middleware setup
|
|
84
|
+
const error = errorClass.get();
|
|
85
|
+
app.use(error.handleErrors()); // Must be LAST middleware
|
|
86
|
+
|
|
87
|
+
// โ
CORRECT - Express async route pattern
|
|
88
|
+
app.post(
|
|
89
|
+
'/api',
|
|
90
|
+
error.asyncRoute(async (req, res) => {
|
|
91
|
+
if (!data) throw error.badRequest('Data required');
|
|
92
|
+
})
|
|
93
|
+
);
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### **Fastify Setup**
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
// โ
CORRECT - Fastify error handler setup
|
|
100
|
+
import Fastify from 'fastify';
|
|
101
|
+
const fastify = Fastify();
|
|
102
|
+
const error = errorClass.get();
|
|
103
|
+
|
|
104
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
105
|
+
const appError = error.statusCode ? error : error.serverError(error.message);
|
|
106
|
+
reply.status(appError.statusCode).send({
|
|
107
|
+
error: appError.type,
|
|
108
|
+
message: appError.message,
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// โ
CORRECT - Fastify route pattern
|
|
113
|
+
fastify.post('/api', async (request, reply) => {
|
|
114
|
+
if (!request.body.data) throw error.badRequest('Data required');
|
|
115
|
+
// Fastify automatically catches errors
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
#### **Koa Setup**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// โ
CORRECT - Koa error handler setup
|
|
123
|
+
import Koa from 'koa';
|
|
124
|
+
const app = new Koa();
|
|
125
|
+
const error = errorClass.get();
|
|
126
|
+
|
|
127
|
+
app.use(async (ctx, next) => {
|
|
128
|
+
try {
|
|
129
|
+
await next();
|
|
130
|
+
} catch (error) {
|
|
131
|
+
const appError = error.statusCode
|
|
132
|
+
? error
|
|
133
|
+
: error.serverError(error.message);
|
|
134
|
+
ctx.status = appError.statusCode;
|
|
135
|
+
ctx.body = {
|
|
136
|
+
error: appError.type,
|
|
137
|
+
message: appError.message,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// โ
CORRECT - Koa route pattern
|
|
143
|
+
app.use(async (ctx, next) => {
|
|
144
|
+
if (!ctx.request.body.data) throw error.badRequest('Data required');
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### **Error Type Selection (Copy These Rules)**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// โ
Input validation (client's fault)
|
|
152
|
+
if (!email) throw error.badRequest('Email required');
|
|
153
|
+
if (password.length < 8) throw error.badRequest('Password too short');
|
|
154
|
+
|
|
155
|
+
// โ
Authentication (missing/invalid auth)
|
|
156
|
+
if (!token) throw error.unauthorized('Token required');
|
|
157
|
+
if (tokenExpired) throw error.unauthorized('Session expired');
|
|
158
|
+
|
|
159
|
+
// โ
Authorization (user authenticated but no permission)
|
|
160
|
+
if (!user.isAdmin) throw error.forbidden('Admin access required');
|
|
161
|
+
if (user.blocked) throw error.forbidden('Account suspended');
|
|
162
|
+
|
|
163
|
+
// โ
Resource not found
|
|
164
|
+
if (!user) throw error.notFound('User not found');
|
|
165
|
+
if (!post) throw error.notFound('Post not found');
|
|
166
|
+
|
|
167
|
+
// โ
Business logic conflicts
|
|
168
|
+
if (emailExists) throw error.conflict('Email already registered');
|
|
169
|
+
if (usernameExists) throw error.conflict('Username taken');
|
|
170
|
+
|
|
171
|
+
// โ
Server/external failures
|
|
172
|
+
catch (dbError) { throw error.serverError('Database unavailable'); }
|
|
173
|
+
catch (apiError) { throw error.serverError('External service down'); }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## โ ๏ธ Common LLM Mistakes - Avoid These
|
|
177
|
+
|
|
178
|
+
### **Wrong Error Types**
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// โ Using wrong error for situation
|
|
182
|
+
throw error.serverError('Email required'); // Should be badRequest
|
|
183
|
+
throw error.badRequest('Database connection failed'); // Should be serverError
|
|
184
|
+
throw error.unauthorized('Admin access required'); // Should be forbidden
|
|
185
|
+
|
|
186
|
+
// โ
Use correct error for situation
|
|
187
|
+
throw error.badRequest('Email required'); // Client input issue
|
|
188
|
+
throw error.serverError('Database connection failed'); // Server issue
|
|
189
|
+
throw error.forbidden('Admin access required'); // Permission issue
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### **Missing Middleware Setup**
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// โ Forgetting error middleware
|
|
196
|
+
const app = express();
|
|
197
|
+
app.post('/api', (req, res) => {
|
|
198
|
+
throw error.badRequest('Error'); // Nothing will catch this!
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// โ
Proper middleware setup
|
|
202
|
+
const app = express();
|
|
203
|
+
const error = errorClass.get();
|
|
204
|
+
app.use(error.handleErrors()); // Catches all errors
|
|
205
|
+
app.post('/api', (req, res) => {
|
|
206
|
+
throw error.badRequest('Error'); // Automatically handled
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### **Mixing Error Approaches**
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
// โ Mixing manual and automatic error handling
|
|
214
|
+
app.post(
|
|
215
|
+
'/api',
|
|
216
|
+
error.asyncRoute(async (req, res) => {
|
|
217
|
+
try {
|
|
218
|
+
if (!data) throw error.badRequest('Data required');
|
|
219
|
+
// ... logic
|
|
220
|
+
} catch (err) {
|
|
221
|
+
res.status(500).json({ error: 'Failed' }); // Don't catch manually!
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// โ
Let the system handle errors
|
|
227
|
+
app.post(
|
|
228
|
+
'/api',
|
|
229
|
+
error.asyncRoute(async (req, res) => {
|
|
230
|
+
if (!data) throw error.badRequest('Data required'); // Just throw - system handles
|
|
231
|
+
// ... logic
|
|
232
|
+
})
|
|
233
|
+
);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## ๐จ Error Handling Patterns
|
|
237
|
+
|
|
238
|
+
### **Input Validation Pattern**
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
app.post(
|
|
242
|
+
'/users',
|
|
243
|
+
error.asyncRoute(async (req, res) => {
|
|
244
|
+
const { email, password, name } = req.body;
|
|
245
|
+
|
|
246
|
+
// Validate required fields
|
|
247
|
+
if (!email) throw error.badRequest('Email is required');
|
|
248
|
+
if (!password) throw error.badRequest('Password is required');
|
|
249
|
+
if (!name) throw error.badRequest('Name is required');
|
|
250
|
+
|
|
251
|
+
// Validate format/rules
|
|
252
|
+
if (!email.includes('@')) throw error.badRequest('Invalid email format');
|
|
253
|
+
if (password.length < 8)
|
|
254
|
+
throw error.badRequest('Password must be 8+ characters');
|
|
255
|
+
|
|
256
|
+
// Success path
|
|
257
|
+
const user = await createUser({ email, password, name });
|
|
258
|
+
res.json({ user });
|
|
259
|
+
})
|
|
260
|
+
);
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### **Authentication Middleware Pattern**
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
const requireAuth = error.asyncRoute(async (req, res, next) => {
|
|
267
|
+
const token = req.headers.authorization?.replace('Bearer ', '');
|
|
268
|
+
|
|
269
|
+
if (!token) {
|
|
270
|
+
throw error.unauthorized('Authentication token required');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const decoded = jwt.verify(token, JWT_SECRET);
|
|
275
|
+
const user = await findUser(decoded.userId);
|
|
276
|
+
|
|
277
|
+
if (!user) {
|
|
278
|
+
throw error.unauthorized('Invalid token - user not found');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
req.user = user;
|
|
282
|
+
next();
|
|
283
|
+
} catch (jwtError) {
|
|
284
|
+
throw error.unauthorized('Invalid or expired token');
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Usage
|
|
289
|
+
app.get(
|
|
290
|
+
'/profile',
|
|
291
|
+
requireAuth,
|
|
292
|
+
error.asyncRoute(async (req, res) => {
|
|
293
|
+
res.json({ user: req.user });
|
|
294
|
+
})
|
|
295
|
+
);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### **Database Error Handling Pattern**
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
app.post(
|
|
302
|
+
'/posts',
|
|
303
|
+
error.asyncRoute(async (req, res) => {
|
|
304
|
+
const { title, content } = req.body;
|
|
305
|
+
|
|
306
|
+
if (!title) throw error.badRequest('Title required');
|
|
307
|
+
if (!content) throw error.badRequest('Content required');
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
// Check for duplicates (business logic)
|
|
311
|
+
const existing = await findPostByTitle(title);
|
|
312
|
+
if (existing) {
|
|
313
|
+
throw error.conflict('Post with this title already exists');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Create post
|
|
317
|
+
const post = await createPost({ title, content });
|
|
318
|
+
res.json({ post });
|
|
319
|
+
} catch (dbError) {
|
|
320
|
+
// Database connection/query failures
|
|
321
|
+
if (dbError.code === 'ECONNREFUSED') {
|
|
322
|
+
throw error.serverError('Database connection failed');
|
|
323
|
+
}
|
|
324
|
+
if (dbError.code === 'ETIMEDOUT') {
|
|
325
|
+
throw error.serverError('Database query timeout');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Re-throw if it's already our error
|
|
329
|
+
if (dbError.statusCode) {
|
|
330
|
+
throw dbError;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Unknown database error
|
|
334
|
+
throw error.serverError('Database operation failed');
|
|
335
|
+
}
|
|
336
|
+
})
|
|
337
|
+
);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## ๐ Environment Variables
|
|
341
|
+
|
|
342
|
+
### **Framework Configuration (Bloomneo Internal)**
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
# Error handling behavior (optional)
|
|
346
|
+
BLOOM_ERROR_STACK=false # Show stack traces (default: true in dev, false in prod)
|
|
347
|
+
BLOOM_ERROR_LOG=true # Enable error logging (default: true)
|
|
348
|
+
|
|
349
|
+
# Framework detection
|
|
350
|
+
NODE_ENV=production # Environment mode (development, production, test, staging)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### **Your Application Configuration**
|
|
354
|
+
|
|
355
|
+
```bash
|
|
356
|
+
# Your app-specific environment variables
|
|
357
|
+
DATABASE_URL=postgresql://...
|
|
358
|
+
API_KEY=your-api-key
|
|
359
|
+
SESSION_SECRET=your-session-secret
|
|
360
|
+
|
|
361
|
+
# Note: Use any naming convention for your app config
|
|
362
|
+
# Bloomneo only reads BLOOM_* prefixed variables
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### **Configuration Separation**
|
|
366
|
+
|
|
367
|
+
The error module follows **clear separation**:
|
|
368
|
+
|
|
369
|
+
- **VOILA*ERROR*\*** - Framework behavior (stack traces, logging)
|
|
370
|
+
- **Everything else** - Your application configuration
|
|
371
|
+
- **No interference** - Your app config remains untouched
|
|
372
|
+
|
|
373
|
+
## ๐ Production Deployment
|
|
374
|
+
|
|
375
|
+
### **Environment Configuration**
|
|
376
|
+
|
|
377
|
+
```bash
|
|
378
|
+
# โ
Production settings
|
|
379
|
+
NODE_ENV=production # Enables production mode
|
|
380
|
+
BLOOM_ERROR_STACK=false # Hide stack traces for security
|
|
381
|
+
BLOOM_ERROR_LOG=true # Enable error logging for monitoring
|
|
382
|
+
|
|
383
|
+
# โ
Development settings
|
|
384
|
+
NODE_ENV=development # Enables development mode
|
|
385
|
+
BLOOM_ERROR_STACK=true # Show stack traces for debugging
|
|
386
|
+
BLOOM_ERROR_LOG=true # Enable error logging
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### **Framework-Specific Setup**
|
|
390
|
+
|
|
391
|
+
#### **Express Production Setup**
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
import express from 'express';
|
|
395
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
396
|
+
|
|
397
|
+
const app = express();
|
|
398
|
+
|
|
399
|
+
// Standard middleware
|
|
400
|
+
app.use(express.json());
|
|
401
|
+
app.use(express.urlencoded({ extended: true }));
|
|
402
|
+
|
|
403
|
+
// Your routes here
|
|
404
|
+
app.get('/health', (req, res) => res.json({ status: 'ok' }));
|
|
405
|
+
app.use('/api', apiRoutes);
|
|
406
|
+
|
|
407
|
+
// ERROR MIDDLEWARE MUST BE LAST!
|
|
408
|
+
const error = errorClass.get();
|
|
409
|
+
app.use(error.handleErrors());
|
|
410
|
+
|
|
411
|
+
app.listen(3000, () => {
|
|
412
|
+
console.log('Server running on port 3000');
|
|
413
|
+
});
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
#### **Fastify Production Setup**
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
import Fastify from 'fastify';
|
|
420
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
421
|
+
|
|
422
|
+
const fastify = Fastify({ logger: true });
|
|
423
|
+
const error = errorClass.get();
|
|
424
|
+
|
|
425
|
+
// Global error handler
|
|
426
|
+
fastify.setErrorHandler((error, request, reply) => {
|
|
427
|
+
const appError = error.statusCode ? error : error.serverError(error.message);
|
|
428
|
+
|
|
429
|
+
// Log in production
|
|
430
|
+
if (error.getEnvironmentInfo().isProduction) {
|
|
431
|
+
fastify.log.error(error);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
reply.status(appError.statusCode).send({
|
|
435
|
+
error: appError.type,
|
|
436
|
+
message: appError.message,
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Your routes
|
|
441
|
+
fastify.register(apiRoutes, { prefix: '/api' });
|
|
442
|
+
|
|
443
|
+
const start = async () => {
|
|
444
|
+
try {
|
|
445
|
+
await fastify.listen({ port: 3000 });
|
|
446
|
+
} catch (err) {
|
|
447
|
+
fastify.log.error(err);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
start();
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### **Security Validation**
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// App startup validation
|
|
458
|
+
try {
|
|
459
|
+
const error = errorClass.get();
|
|
460
|
+
const env = error.getEnvironmentInfo();
|
|
461
|
+
|
|
462
|
+
console.log(`โ
Error handling initialized`);
|
|
463
|
+
console.log(`Environment: ${env.nodeEnv}`);
|
|
464
|
+
console.log(`Development mode: ${env.isDevelopment}`);
|
|
465
|
+
|
|
466
|
+
// Production security check
|
|
467
|
+
if (env.isProduction && process.env.BLOOM_ERROR_STACK === 'true') {
|
|
468
|
+
console.warn('โ ๏ธ Stack traces enabled in production - security risk!');
|
|
469
|
+
}
|
|
470
|
+
} catch (setupError) {
|
|
471
|
+
console.error('โ Error setup failed:', setupError.message);
|
|
472
|
+
process.exit(1);
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
### **Common Issues & Solutions**
|
|
477
|
+
|
|
478
|
+
- **"Error not caught"** โ Ensure error middleware is LAST in Express
|
|
479
|
+
- **"Stack traces in production"** โ Check `NODE_ENV=production` and
|
|
480
|
+
`BLOOM_ERROR_STACK=false`
|
|
481
|
+
- **"Async errors not handled"** โ Use `error.asyncRoute()` wrapper for async
|
|
482
|
+
routes
|
|
483
|
+
- **"Wrong error types"** โ Review error type selection guide above
|
|
484
|
+
- **"Fastify errors not caught"** โ Use `fastify.setErrorHandler()` with error
|
|
485
|
+
module
|
|
486
|
+
- **"Koa errors not handled"** โ Wrap routes in try/catch with error module
|
|
487
|
+
|
|
488
|
+
## ๐ Complete API Reference
|
|
489
|
+
|
|
490
|
+
### **Core Function**
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
const error = errorClass.get(); // One function, all methods
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
### **Error Creation Methods**
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
// Semantic HTTP errors
|
|
500
|
+
error.badRequest(message?); // 400 - Invalid input
|
|
501
|
+
error.unauthorized(message?); // 401 - Auth required
|
|
502
|
+
error.forbidden(message?); // 403 - Access denied
|
|
503
|
+
error.notFound(message?); // 404 - Resource missing
|
|
504
|
+
error.conflict(message?); // 409 - Business conflicts
|
|
505
|
+
error.serverError(message?); // 500 - Internal failures
|
|
506
|
+
|
|
507
|
+
// Custom errors
|
|
508
|
+
error.createError(statusCode, message, type?); // Any status code
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### **Express Middleware**
|
|
512
|
+
|
|
513
|
+
```typescript
|
|
514
|
+
// Error handling (must be last middleware)
|
|
515
|
+
error.handleErrors(options?);
|
|
516
|
+
|
|
517
|
+
// Async route wrapper
|
|
518
|
+
error.asyncRoute(handler);
|
|
519
|
+
|
|
520
|
+
// Error categorization
|
|
521
|
+
error.isClientError(error); // 4xx status codes
|
|
522
|
+
error.isServerError(error); // 5xx status codes
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
### **Utility Methods**
|
|
526
|
+
|
|
527
|
+
```typescript
|
|
528
|
+
// Environment info
|
|
529
|
+
error.getEnvironmentInfo(); // Current environment details
|
|
530
|
+
|
|
531
|
+
// Configuration access
|
|
532
|
+
error.getConfig(); // Current error configuration
|
|
533
|
+
|
|
534
|
+
// Shortcut methods (direct usage without get())
|
|
535
|
+
error.badRequest('Message');
|
|
536
|
+
error.unauthorized('Message');
|
|
537
|
+
error.forbidden('Message');
|
|
538
|
+
error.notFound('Message');
|
|
539
|
+
error.conflict('Message');
|
|
540
|
+
error.serverError('Message');
|
|
541
|
+
|
|
542
|
+
// Direct middleware usage
|
|
543
|
+
error.handleErrors();
|
|
544
|
+
error.asyncRoute(handler);
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## ๐ก Simple Usage Patterns
|
|
548
|
+
|
|
549
|
+
### **Basic Validation**
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
// Input validation
|
|
553
|
+
if (!email) throw error.badRequest('Email required');
|
|
554
|
+
if (!password) throw error.badRequest('Password required');
|
|
555
|
+
|
|
556
|
+
// Business validation
|
|
557
|
+
if (userExists) throw error.conflict('User already exists');
|
|
558
|
+
if (!userFound) throw error.notFound('User not found');
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### **Authentication Flow**
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
// Check for token
|
|
565
|
+
if (!token) throw error.unauthorized('Token required');
|
|
566
|
+
|
|
567
|
+
// Verify token
|
|
568
|
+
try {
|
|
569
|
+
const user = await verifyToken(token);
|
|
570
|
+
req.user = user;
|
|
571
|
+
} catch {
|
|
572
|
+
throw error.unauthorized('Invalid token');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Check permissions
|
|
576
|
+
if (!user.isAdmin) throw error.forbidden('Admin required');
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### **Database Operations**
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
try {
|
|
583
|
+
const result = await db.query(sql);
|
|
584
|
+
return result;
|
|
585
|
+
} catch (dbError) {
|
|
586
|
+
throw error.serverError('Database operation failed');
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
## ๐งช Testing
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
import { errorClass } from '@bloomneo/appkit/error';
|
|
594
|
+
|
|
595
|
+
// Reset for clean testing
|
|
596
|
+
const error = errorClass.reset({
|
|
597
|
+
middleware: {
|
|
598
|
+
showStack: false,
|
|
599
|
+
logErrors: false,
|
|
600
|
+
},
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
// Test error creation
|
|
604
|
+
const badRequestError = error.badRequest('Test message');
|
|
605
|
+
expect(badRequestError.statusCode).toBe(400);
|
|
606
|
+
expect(badRequestError.type).toBe('BAD_REQUEST');
|
|
607
|
+
|
|
608
|
+
// Test error categorization
|
|
609
|
+
const clientError = error.badRequest('Client error');
|
|
610
|
+
const serverError = error.serverError('Server error');
|
|
611
|
+
|
|
612
|
+
expect(error.isClientError(clientError)).toBe(true);
|
|
613
|
+
expect(error.isServerError(serverError)).toBe(true);
|
|
614
|
+
|
|
615
|
+
// Test environment detection
|
|
616
|
+
const env = error.getEnvironmentInfo();
|
|
617
|
+
expect(env.isDevelopment).toBeDefined();
|
|
618
|
+
expect(env.isProduction).toBeDefined();
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## ๐ Performance
|
|
622
|
+
|
|
623
|
+
- **Error Creation**: ~0.01ms per error
|
|
624
|
+
- **Middleware Processing**: ~0.1ms per request
|
|
625
|
+
- **Memory Usage**: <100KB overhead
|
|
626
|
+
- **Environment Parsing**: Once per app startup
|
|
627
|
+
- **Framework Agnostic**: Works with any Node.js framework
|
|
628
|
+
|
|
629
|
+
## ๐ TypeScript Support
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
import type {
|
|
633
|
+
AppError,
|
|
634
|
+
ErrorConfig,
|
|
635
|
+
ErrorHandlerOptions,
|
|
636
|
+
ExpressErrorHandler,
|
|
637
|
+
AsyncRouteHandler,
|
|
638
|
+
} from '@bloomneo/appkit/error';
|
|
639
|
+
|
|
640
|
+
// All methods are fully typed
|
|
641
|
+
const error = errorClass.get();
|
|
642
|
+
const middleware: ExpressErrorHandler = error.handleErrors();
|
|
643
|
+
const wrapper: AsyncRouteHandler = error.asyncRoute(handler);
|
|
644
|
+
|
|
645
|
+
// Environment info is typed
|
|
646
|
+
const env = error.getEnvironmentInfo();
|
|
647
|
+
// env.isDevelopment: boolean
|
|
648
|
+
// env.isProduction: boolean
|
|
649
|
+
// env.nodeEnv: string
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
## ๐ License
|
|
653
|
+
|
|
654
|
+
MIT ยฉ [Bloomneo](https://github.com/bloomneo)
|
|
655
|
+
|
|
656
|
+
---
|
|
657
|
+
|
|
658
|
+
<p align="center">
|
|
659
|
+
Built with โค๏ธ in India by the <a href="https://github.com/orgs/bloomneo/people">Bloomneo Team</a>
|
|
660
|
+
</p>
|