apienvelope 1.0.0
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/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/chunk-2TAKYP6Q.mjs +213 -0
- package/dist/index.d.mts +842 -0
- package/dist/index.d.ts +842 -0
- package/dist/index.js +1553 -0
- package/dist/index.mjs +1254 -0
- package/dist/predefined-FHOIIQHS.mjs +26 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sepehr Mohseni
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# apienvelope
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/apienvelope)
|
|
4
|
+
[](https://github.com/sepehr-mohseni/apienvelope)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Standardized API response formatting for Express.js applications. Enforces consistent response structures, handles errors gracefully, and provides built-in pagination support with full TypeScript coverage.
|
|
9
|
+
|
|
10
|
+
## Why apienvelope?
|
|
11
|
+
|
|
12
|
+
Building REST APIs often leads to inconsistent response formats across endpoints. This package solves that by providing:
|
|
13
|
+
|
|
14
|
+
- **Uniform response structure** across your entire API
|
|
15
|
+
- **Zero-config error handling** with proper HTTP status codes
|
|
16
|
+
- **Built-in request tracing** via request/correlation IDs
|
|
17
|
+
- **Type-safe responses** that work seamlessly with frontend clients
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install apienvelope
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import express from 'express';
|
|
29
|
+
import { responseWrapper, errorCatcher, NotFoundError } from 'apienvelope';
|
|
30
|
+
|
|
31
|
+
const app = express();
|
|
32
|
+
|
|
33
|
+
app.use(responseWrapper({ environment: 'production' }));
|
|
34
|
+
|
|
35
|
+
app.get('/users/:id', async (req, res) => {
|
|
36
|
+
const user = await db.users.findById(req.params.id);
|
|
37
|
+
if (!user) {
|
|
38
|
+
throw new NotFoundError('User not found');
|
|
39
|
+
}
|
|
40
|
+
res.respond(user);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.get('/posts', async (req, res) => {
|
|
44
|
+
const { page = 1, limit = 10 } = req.query;
|
|
45
|
+
const { data, total } = await db.posts.paginate(page, limit);
|
|
46
|
+
res.respondPaginated(data, { page: Number(page), limit: Number(limit), total });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
app.use(errorCatcher({ environment: 'production' }));
|
|
50
|
+
|
|
51
|
+
app.listen(3000);
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Response Formats
|
|
55
|
+
|
|
56
|
+
### Success Response
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"success": true,
|
|
60
|
+
"data": { "id": 1, "name": "John Doe", "email": "john@example.com" },
|
|
61
|
+
"meta": { "requestId": "req_abc123" },
|
|
62
|
+
"timestamp": "2024-12-23T10:30:00.000Z"
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Error Response
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"success": false,
|
|
70
|
+
"error": {
|
|
71
|
+
"code": "VALIDATION_ERROR",
|
|
72
|
+
"message": "Validation failed",
|
|
73
|
+
"fields": {
|
|
74
|
+
"email": ["Invalid email format"],
|
|
75
|
+
"password": ["Must be at least 8 characters"]
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"meta": { "requestId": "req_abc123" },
|
|
79
|
+
"timestamp": "2024-12-23T10:30:00.000Z"
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Paginated Response
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"success": true,
|
|
87
|
+
"data": [{ "id": 1, "title": "Post 1" }, { "id": 2, "title": "Post 2" }],
|
|
88
|
+
"pagination": {
|
|
89
|
+
"page": 1,
|
|
90
|
+
"limit": 10,
|
|
91
|
+
"total": 100,
|
|
92
|
+
"totalPages": 10,
|
|
93
|
+
"hasNextPage": true,
|
|
94
|
+
"hasPreviousPage": false
|
|
95
|
+
},
|
|
96
|
+
"timestamp": "2024-12-23T10:30:00.000Z"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Error Classes
|
|
101
|
+
|
|
102
|
+
| Class | Status | Code |
|
|
103
|
+
|-------|--------|------|
|
|
104
|
+
| `ValidationError` | 400 | VALIDATION_ERROR |
|
|
105
|
+
| `BadRequestError` | 400 | BAD_REQUEST |
|
|
106
|
+
| `UnauthorizedError` | 401 | UNAUTHORIZED |
|
|
107
|
+
| `ForbiddenError` | 403 | FORBIDDEN |
|
|
108
|
+
| `NotFoundError` | 404 | NOT_FOUND |
|
|
109
|
+
| `ConflictError` | 409 | CONFLICT |
|
|
110
|
+
| `UnprocessableEntityError` | 422 | UNPROCESSABLE_ENTITY |
|
|
111
|
+
| `RateLimitError` | 429 | RATE_LIMIT_EXCEEDED |
|
|
112
|
+
| `InternalServerError` | 500 | INTERNAL_ERROR |
|
|
113
|
+
| `ServiceUnavailableError` | 503 | SERVICE_UNAVAILABLE |
|
|
114
|
+
|
|
115
|
+
## Configuration
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
app.use(responseWrapper({
|
|
119
|
+
environment: 'production',
|
|
120
|
+
includeStackTraces: false,
|
|
121
|
+
|
|
122
|
+
requestIdHeader: 'x-request-id',
|
|
123
|
+
correlationIdHeader: 'x-correlation-id',
|
|
124
|
+
generateRequestId: true,
|
|
125
|
+
|
|
126
|
+
maskSensitiveData: true,
|
|
127
|
+
sensitiveFields: ['password', 'token', 'secret', 'apiKey'],
|
|
128
|
+
|
|
129
|
+
pagination: {
|
|
130
|
+
defaultLimit: 20,
|
|
131
|
+
maxLimit: 100,
|
|
132
|
+
includeLinks: true,
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
customErrorMappers: new Map([
|
|
136
|
+
[PaymentError, 402],
|
|
137
|
+
[RateLimitError, 429],
|
|
138
|
+
]),
|
|
139
|
+
}));
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## TypeScript Support
|
|
143
|
+
|
|
144
|
+
Full generic support for type-safe API responses:
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { ApiResponse, isSuccessResponse } from 'apienvelope';
|
|
148
|
+
|
|
149
|
+
interface User {
|
|
150
|
+
id: number;
|
|
151
|
+
name: string;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function getUser(id: number): Promise<User | null> {
|
|
155
|
+
const response: ApiResponse<User> = await fetch(`/api/users/${id}`).then(r => r.json());
|
|
156
|
+
|
|
157
|
+
if (isSuccessResponse(response)) {
|
|
158
|
+
return response.data; // TypeScript infers User type
|
|
159
|
+
}
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Custom Errors
|
|
165
|
+
|
|
166
|
+
Extend `ApiError` for domain-specific errors:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
import { ApiError } from 'apienvelope';
|
|
170
|
+
|
|
171
|
+
class InsufficientFundsError extends ApiError {
|
|
172
|
+
constructor(balance: number, required: number) {
|
|
173
|
+
super('Insufficient funds for this transaction', {
|
|
174
|
+
code: 'INSUFFICIENT_FUNDS',
|
|
175
|
+
statusCode: 402,
|
|
176
|
+
details: { currentBalance: balance, requiredAmount: required },
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Async Handler
|
|
183
|
+
|
|
184
|
+
Wrap async routes to automatically catch and forward errors:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { asyncHandler } from 'apienvelope';
|
|
188
|
+
|
|
189
|
+
app.get('/users/:id', asyncHandler(async (req, res) => {
|
|
190
|
+
const user = await db.users.findById(req.params.id);
|
|
191
|
+
res.respond(user);
|
|
192
|
+
}));
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Pagination Helper
|
|
196
|
+
|
|
197
|
+
Utility class for handling pagination logic:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { PaginationHelper } from 'apienvelope';
|
|
201
|
+
|
|
202
|
+
const paginator = new PaginationHelper({ defaultLimit: 20, maxLimit: 100 });
|
|
203
|
+
|
|
204
|
+
app.get('/items', async (req, res) => {
|
|
205
|
+
const { page, limit } = paginator.extractFromRequest(req);
|
|
206
|
+
const offset = paginator.calculateOffset(page, limit);
|
|
207
|
+
|
|
208
|
+
const items = await db.items.find().skip(offset).limit(limit);
|
|
209
|
+
const total = await db.items.count();
|
|
210
|
+
|
|
211
|
+
res.respondPaginated(items, { page, limit, total });
|
|
212
|
+
});
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Cursor Pagination
|
|
216
|
+
|
|
217
|
+
Support for cursor-based pagination:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
app.get('/feed', async (req, res) => {
|
|
221
|
+
const { cursor, limit = 20 } = req.query;
|
|
222
|
+
const { items, nextCursor } = await db.feed.getCursor(cursor, limit);
|
|
223
|
+
|
|
224
|
+
res.respondCursorPaginated(items, {
|
|
225
|
+
limit: Number(limit),
|
|
226
|
+
cursor: cursor as string,
|
|
227
|
+
nextCursor,
|
|
228
|
+
hasMore: !!nextCursor,
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Response Hooks
|
|
234
|
+
|
|
235
|
+
Transform responses before they're sent:
|
|
236
|
+
|
|
237
|
+
```typescript
|
|
238
|
+
app.use(responseWrapper({
|
|
239
|
+
preResponseHooks: [
|
|
240
|
+
(data, meta) => ({
|
|
241
|
+
data,
|
|
242
|
+
meta: { ...meta, apiVersion: '2.0' },
|
|
243
|
+
}),
|
|
244
|
+
],
|
|
245
|
+
postResponseHooks: [
|
|
246
|
+
(response) => ({
|
|
247
|
+
...response,
|
|
248
|
+
meta: { ...response.meta, serverTime: Date.now() },
|
|
249
|
+
}),
|
|
250
|
+
],
|
|
251
|
+
}));
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## API Reference
|
|
255
|
+
|
|
256
|
+
### Middleware
|
|
257
|
+
- `responseWrapper(options)` - Adds response formatting methods to Express
|
|
258
|
+
- `errorCatcher(options)` - Global error handler middleware
|
|
259
|
+
- `asyncHandler(fn)` - Wraps async functions for error handling
|
|
260
|
+
|
|
261
|
+
### Response Methods
|
|
262
|
+
- `res.respond(data, meta?, statusCode?)` - Send formatted success response
|
|
263
|
+
- `res.respondPaginated(data, pagination, meta?)` - Send paginated response
|
|
264
|
+
- `res.respondCursorPaginated(data, pagination, meta?)` - Send cursor-paginated response
|
|
265
|
+
- `res.respondError(error, meta?)` - Send formatted error response
|
|
266
|
+
|
|
267
|
+
### Classes
|
|
268
|
+
- `ResponseFormatter` - Core formatting logic
|
|
269
|
+
- `PaginationHelper` - Pagination utilities
|
|
270
|
+
- `ApiError` - Base error class
|
|
271
|
+
- `StatusCodeMapper` - HTTP status code mapping
|
|
272
|
+
|
|
273
|
+
### Type Guards
|
|
274
|
+
- `isSuccessResponse(response)` - Check if response is successful
|
|
275
|
+
- `isErrorResponse(response)` - Check if response is an error
|
|
276
|
+
- `isPaginatedResponse(response)` - Check if response is paginated
|
|
277
|
+
|
|
278
|
+
## Requirements
|
|
279
|
+
|
|
280
|
+
- Node.js >= 16.0.0
|
|
281
|
+
- Express >= 4.0.0
|
|
282
|
+
|
|
283
|
+
## Author
|
|
284
|
+
|
|
285
|
+
**Sepehr Mohseni**
|
|
286
|
+
|
|
287
|
+
- [GitHub](https://github.com/sepehr-mohseni)
|
|
288
|
+
- [LinkedIn](https://www.linkedin.com/in/sepehr-mohseni/)
|
|
289
|
+
- [npm](https://www.npmjs.com/~sepehr-mohseni)
|
|
290
|
+
|
|
291
|
+
## License
|
|
292
|
+
|
|
293
|
+
MIT
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// src/errors/ApiError.ts
|
|
2
|
+
var ApiError = class _ApiError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
statusCode;
|
|
5
|
+
details;
|
|
6
|
+
fields;
|
|
7
|
+
context;
|
|
8
|
+
isOperational;
|
|
9
|
+
timestamp;
|
|
10
|
+
constructor(message, options = {}) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = this.constructor.name;
|
|
13
|
+
this.code = options.code || "INTERNAL_ERROR";
|
|
14
|
+
this.statusCode = options.statusCode || 500;
|
|
15
|
+
this.details = options.details;
|
|
16
|
+
this.fields = options.fields;
|
|
17
|
+
this.context = options.context;
|
|
18
|
+
this.isOperational = options.isOperational ?? true;
|
|
19
|
+
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
20
|
+
Error.captureStackTrace(this, this.constructor);
|
|
21
|
+
if (options.cause) {
|
|
22
|
+
this.cause = options.cause;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Serialize error for response
|
|
27
|
+
*/
|
|
28
|
+
serialize(includeStack = false) {
|
|
29
|
+
const serialized = {
|
|
30
|
+
code: this.code,
|
|
31
|
+
message: this.message,
|
|
32
|
+
statusCode: this.statusCode
|
|
33
|
+
};
|
|
34
|
+
if (this.details) {
|
|
35
|
+
serialized.details = this.details;
|
|
36
|
+
}
|
|
37
|
+
if (this.fields) {
|
|
38
|
+
serialized.fields = this.fields;
|
|
39
|
+
}
|
|
40
|
+
if (this.context) {
|
|
41
|
+
serialized.context = this.context;
|
|
42
|
+
}
|
|
43
|
+
if (includeStack && this.stack) {
|
|
44
|
+
serialized.stack = this.stack;
|
|
45
|
+
}
|
|
46
|
+
return serialized;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get error chain for nested errors
|
|
50
|
+
*/
|
|
51
|
+
getErrorChain() {
|
|
52
|
+
const chain = [];
|
|
53
|
+
let current = this;
|
|
54
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
55
|
+
while (current && !seen.has(current)) {
|
|
56
|
+
seen.add(current);
|
|
57
|
+
chain.push({
|
|
58
|
+
name: current.name,
|
|
59
|
+
message: current.message,
|
|
60
|
+
code: current instanceof _ApiError ? current.code : void 0
|
|
61
|
+
});
|
|
62
|
+
current = current.cause;
|
|
63
|
+
}
|
|
64
|
+
return chain;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create a new error with additional context
|
|
68
|
+
*/
|
|
69
|
+
withContext(context) {
|
|
70
|
+
return new _ApiError(this.message, {
|
|
71
|
+
code: this.code,
|
|
72
|
+
statusCode: this.statusCode,
|
|
73
|
+
details: this.details,
|
|
74
|
+
fields: this.fields,
|
|
75
|
+
context: { ...this.context, ...context },
|
|
76
|
+
cause: this.cause,
|
|
77
|
+
isOperational: this.isOperational
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Convert to JSON for logging
|
|
82
|
+
*/
|
|
83
|
+
toJSON() {
|
|
84
|
+
return {
|
|
85
|
+
name: this.name,
|
|
86
|
+
message: this.message,
|
|
87
|
+
code: this.code,
|
|
88
|
+
statusCode: this.statusCode,
|
|
89
|
+
details: this.details,
|
|
90
|
+
fields: this.fields,
|
|
91
|
+
context: this.context,
|
|
92
|
+
isOperational: this.isOperational,
|
|
93
|
+
timestamp: this.timestamp,
|
|
94
|
+
stack: this.stack
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// src/errors/predefined.ts
|
|
100
|
+
var ValidationError = class extends ApiError {
|
|
101
|
+
constructor(message = "Validation failed", fields, options = {}) {
|
|
102
|
+
super(message, {
|
|
103
|
+
...options,
|
|
104
|
+
code: "VALIDATION_ERROR",
|
|
105
|
+
statusCode: 400,
|
|
106
|
+
fields
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var NotFoundError = class extends ApiError {
|
|
111
|
+
constructor(message = "Resource not found", options = {}) {
|
|
112
|
+
super(message, {
|
|
113
|
+
...options,
|
|
114
|
+
code: "NOT_FOUND",
|
|
115
|
+
statusCode: 404
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
var UnauthorizedError = class extends ApiError {
|
|
120
|
+
constructor(message = "Authentication required", options = {}) {
|
|
121
|
+
super(message, {
|
|
122
|
+
...options,
|
|
123
|
+
code: "UNAUTHORIZED",
|
|
124
|
+
statusCode: 401
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
var ForbiddenError = class extends ApiError {
|
|
129
|
+
constructor(message = "Access forbidden", options = {}) {
|
|
130
|
+
super(message, {
|
|
131
|
+
...options,
|
|
132
|
+
code: "FORBIDDEN",
|
|
133
|
+
statusCode: 403
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
var ConflictError = class extends ApiError {
|
|
138
|
+
constructor(message = "Resource conflict", options = {}) {
|
|
139
|
+
super(message, {
|
|
140
|
+
...options,
|
|
141
|
+
code: "CONFLICT",
|
|
142
|
+
statusCode: 409
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
var InternalServerError = class extends ApiError {
|
|
147
|
+
constructor(message = "Internal server error", options = {}) {
|
|
148
|
+
super(message, {
|
|
149
|
+
...options,
|
|
150
|
+
code: "INTERNAL_ERROR",
|
|
151
|
+
statusCode: 500,
|
|
152
|
+
isOperational: false
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
var BadRequestError = class extends ApiError {
|
|
157
|
+
constructor(message = "Bad request", options = {}) {
|
|
158
|
+
super(message, {
|
|
159
|
+
...options,
|
|
160
|
+
code: "BAD_REQUEST",
|
|
161
|
+
statusCode: 400
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
var RateLimitError = class extends ApiError {
|
|
166
|
+
retryAfter;
|
|
167
|
+
constructor(message = "Rate limit exceeded", retryAfter, options = {}) {
|
|
168
|
+
super(message, {
|
|
169
|
+
...options,
|
|
170
|
+
code: "RATE_LIMIT_EXCEEDED",
|
|
171
|
+
statusCode: 429,
|
|
172
|
+
details: retryAfter ? { retryAfter } : void 0
|
|
173
|
+
});
|
|
174
|
+
this.retryAfter = retryAfter;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
var ServiceUnavailableError = class extends ApiError {
|
|
178
|
+
constructor(message = "Service temporarily unavailable", options = {}) {
|
|
179
|
+
super(message, {
|
|
180
|
+
...options,
|
|
181
|
+
code: "SERVICE_UNAVAILABLE",
|
|
182
|
+
statusCode: 503
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var UnprocessableEntityError = class extends ApiError {
|
|
187
|
+
constructor(message = "Unprocessable entity", options = {}) {
|
|
188
|
+
super(message, {
|
|
189
|
+
...options,
|
|
190
|
+
code: "UNPROCESSABLE_ENTITY",
|
|
191
|
+
statusCode: 422
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
function createError(ErrorClass, message, context) {
|
|
196
|
+
const error = new ErrorClass(message);
|
|
197
|
+
return context ? error.withContext(context) : error;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export {
|
|
201
|
+
ApiError,
|
|
202
|
+
ValidationError,
|
|
203
|
+
NotFoundError,
|
|
204
|
+
UnauthorizedError,
|
|
205
|
+
ForbiddenError,
|
|
206
|
+
ConflictError,
|
|
207
|
+
InternalServerError,
|
|
208
|
+
BadRequestError,
|
|
209
|
+
RateLimitError,
|
|
210
|
+
ServiceUnavailableError,
|
|
211
|
+
UnprocessableEntityError,
|
|
212
|
+
createError
|
|
213
|
+
};
|