apienvelope 1.0.0 → 1.1.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/dist/index.d.ts +2 -455
- package/dist/index.js +2 -1553
- package/dist/index.mjs +2 -1254
- package/package.json +3 -2
- package/dist/chunk-2TAKYP6Q.mjs +0 -213
- package/dist/index.d.mts +0 -842
- package/dist/predefined-FHOIIQHS.mjs +0 -26
package/dist/index.js
CHANGED
|
@@ -1,1553 +1,2 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __esm = (fn, res) => function __init() {
|
|
7
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
8
|
-
};
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
22
|
-
|
|
23
|
-
// src/errors/ApiError.ts
|
|
24
|
-
var ApiError;
|
|
25
|
-
var init_ApiError = __esm({
|
|
26
|
-
"src/errors/ApiError.ts"() {
|
|
27
|
-
"use strict";
|
|
28
|
-
ApiError = class _ApiError extends Error {
|
|
29
|
-
code;
|
|
30
|
-
statusCode;
|
|
31
|
-
details;
|
|
32
|
-
fields;
|
|
33
|
-
context;
|
|
34
|
-
isOperational;
|
|
35
|
-
timestamp;
|
|
36
|
-
constructor(message, options = {}) {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = this.constructor.name;
|
|
39
|
-
this.code = options.code || "INTERNAL_ERROR";
|
|
40
|
-
this.statusCode = options.statusCode || 500;
|
|
41
|
-
this.details = options.details;
|
|
42
|
-
this.fields = options.fields;
|
|
43
|
-
this.context = options.context;
|
|
44
|
-
this.isOperational = options.isOperational ?? true;
|
|
45
|
-
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
46
|
-
Error.captureStackTrace(this, this.constructor);
|
|
47
|
-
if (options.cause) {
|
|
48
|
-
this.cause = options.cause;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Serialize error for response
|
|
53
|
-
*/
|
|
54
|
-
serialize(includeStack = false) {
|
|
55
|
-
const serialized = {
|
|
56
|
-
code: this.code,
|
|
57
|
-
message: this.message,
|
|
58
|
-
statusCode: this.statusCode
|
|
59
|
-
};
|
|
60
|
-
if (this.details) {
|
|
61
|
-
serialized.details = this.details;
|
|
62
|
-
}
|
|
63
|
-
if (this.fields) {
|
|
64
|
-
serialized.fields = this.fields;
|
|
65
|
-
}
|
|
66
|
-
if (this.context) {
|
|
67
|
-
serialized.context = this.context;
|
|
68
|
-
}
|
|
69
|
-
if (includeStack && this.stack) {
|
|
70
|
-
serialized.stack = this.stack;
|
|
71
|
-
}
|
|
72
|
-
return serialized;
|
|
73
|
-
}
|
|
74
|
-
/**
|
|
75
|
-
* Get error chain for nested errors
|
|
76
|
-
*/
|
|
77
|
-
getErrorChain() {
|
|
78
|
-
const chain = [];
|
|
79
|
-
let current = this;
|
|
80
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
81
|
-
while (current && !seen.has(current)) {
|
|
82
|
-
seen.add(current);
|
|
83
|
-
chain.push({
|
|
84
|
-
name: current.name,
|
|
85
|
-
message: current.message,
|
|
86
|
-
code: current instanceof _ApiError ? current.code : void 0
|
|
87
|
-
});
|
|
88
|
-
current = current.cause;
|
|
89
|
-
}
|
|
90
|
-
return chain;
|
|
91
|
-
}
|
|
92
|
-
/**
|
|
93
|
-
* Create a new error with additional context
|
|
94
|
-
*/
|
|
95
|
-
withContext(context) {
|
|
96
|
-
return new _ApiError(this.message, {
|
|
97
|
-
code: this.code,
|
|
98
|
-
statusCode: this.statusCode,
|
|
99
|
-
details: this.details,
|
|
100
|
-
fields: this.fields,
|
|
101
|
-
context: { ...this.context, ...context },
|
|
102
|
-
cause: this.cause,
|
|
103
|
-
isOperational: this.isOperational
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Convert to JSON for logging
|
|
108
|
-
*/
|
|
109
|
-
toJSON() {
|
|
110
|
-
return {
|
|
111
|
-
name: this.name,
|
|
112
|
-
message: this.message,
|
|
113
|
-
code: this.code,
|
|
114
|
-
statusCode: this.statusCode,
|
|
115
|
-
details: this.details,
|
|
116
|
-
fields: this.fields,
|
|
117
|
-
context: this.context,
|
|
118
|
-
isOperational: this.isOperational,
|
|
119
|
-
timestamp: this.timestamp,
|
|
120
|
-
stack: this.stack
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
// src/errors/predefined.ts
|
|
128
|
-
var predefined_exports = {};
|
|
129
|
-
__export(predefined_exports, {
|
|
130
|
-
BadRequestError: () => BadRequestError,
|
|
131
|
-
ConflictError: () => ConflictError,
|
|
132
|
-
ForbiddenError: () => ForbiddenError,
|
|
133
|
-
InternalServerError: () => InternalServerError,
|
|
134
|
-
NotFoundError: () => NotFoundError,
|
|
135
|
-
RateLimitError: () => RateLimitError,
|
|
136
|
-
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
137
|
-
UnauthorizedError: () => UnauthorizedError,
|
|
138
|
-
UnprocessableEntityError: () => UnprocessableEntityError,
|
|
139
|
-
ValidationError: () => ValidationError,
|
|
140
|
-
createError: () => createError
|
|
141
|
-
});
|
|
142
|
-
function createError(ErrorClass, message, context) {
|
|
143
|
-
const error = new ErrorClass(message);
|
|
144
|
-
return context ? error.withContext(context) : error;
|
|
145
|
-
}
|
|
146
|
-
var ValidationError, NotFoundError, UnauthorizedError, ForbiddenError, ConflictError, InternalServerError, BadRequestError, RateLimitError, ServiceUnavailableError, UnprocessableEntityError;
|
|
147
|
-
var init_predefined = __esm({
|
|
148
|
-
"src/errors/predefined.ts"() {
|
|
149
|
-
"use strict";
|
|
150
|
-
init_ApiError();
|
|
151
|
-
ValidationError = class extends ApiError {
|
|
152
|
-
constructor(message = "Validation failed", fields, options = {}) {
|
|
153
|
-
super(message, {
|
|
154
|
-
...options,
|
|
155
|
-
code: "VALIDATION_ERROR",
|
|
156
|
-
statusCode: 400,
|
|
157
|
-
fields
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
NotFoundError = class extends ApiError {
|
|
162
|
-
constructor(message = "Resource not found", options = {}) {
|
|
163
|
-
super(message, {
|
|
164
|
-
...options,
|
|
165
|
-
code: "NOT_FOUND",
|
|
166
|
-
statusCode: 404
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
UnauthorizedError = class extends ApiError {
|
|
171
|
-
constructor(message = "Authentication required", options = {}) {
|
|
172
|
-
super(message, {
|
|
173
|
-
...options,
|
|
174
|
-
code: "UNAUTHORIZED",
|
|
175
|
-
statusCode: 401
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
ForbiddenError = class extends ApiError {
|
|
180
|
-
constructor(message = "Access forbidden", options = {}) {
|
|
181
|
-
super(message, {
|
|
182
|
-
...options,
|
|
183
|
-
code: "FORBIDDEN",
|
|
184
|
-
statusCode: 403
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
};
|
|
188
|
-
ConflictError = class extends ApiError {
|
|
189
|
-
constructor(message = "Resource conflict", options = {}) {
|
|
190
|
-
super(message, {
|
|
191
|
-
...options,
|
|
192
|
-
code: "CONFLICT",
|
|
193
|
-
statusCode: 409
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
InternalServerError = class extends ApiError {
|
|
198
|
-
constructor(message = "Internal server error", options = {}) {
|
|
199
|
-
super(message, {
|
|
200
|
-
...options,
|
|
201
|
-
code: "INTERNAL_ERROR",
|
|
202
|
-
statusCode: 500,
|
|
203
|
-
isOperational: false
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
BadRequestError = class extends ApiError {
|
|
208
|
-
constructor(message = "Bad request", options = {}) {
|
|
209
|
-
super(message, {
|
|
210
|
-
...options,
|
|
211
|
-
code: "BAD_REQUEST",
|
|
212
|
-
statusCode: 400
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
RateLimitError = class extends ApiError {
|
|
217
|
-
retryAfter;
|
|
218
|
-
constructor(message = "Rate limit exceeded", retryAfter, options = {}) {
|
|
219
|
-
super(message, {
|
|
220
|
-
...options,
|
|
221
|
-
code: "RATE_LIMIT_EXCEEDED",
|
|
222
|
-
statusCode: 429,
|
|
223
|
-
details: retryAfter ? { retryAfter } : void 0
|
|
224
|
-
});
|
|
225
|
-
this.retryAfter = retryAfter;
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
ServiceUnavailableError = class extends ApiError {
|
|
229
|
-
constructor(message = "Service temporarily unavailable", options = {}) {
|
|
230
|
-
super(message, {
|
|
231
|
-
...options,
|
|
232
|
-
code: "SERVICE_UNAVAILABLE",
|
|
233
|
-
statusCode: 503
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
};
|
|
237
|
-
UnprocessableEntityError = class extends ApiError {
|
|
238
|
-
constructor(message = "Unprocessable entity", options = {}) {
|
|
239
|
-
super(message, {
|
|
240
|
-
...options,
|
|
241
|
-
code: "UNPROCESSABLE_ENTITY",
|
|
242
|
-
statusCode: 422
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
// src/index.ts
|
|
250
|
-
var index_exports = {};
|
|
251
|
-
__export(index_exports, {
|
|
252
|
-
ApiError: () => ApiError,
|
|
253
|
-
ApiRoute: () => ApiRoute,
|
|
254
|
-
BadRequestError: () => BadRequestError,
|
|
255
|
-
ConflictError: () => ConflictError,
|
|
256
|
-
CursorPaginate: () => CursorPaginate,
|
|
257
|
-
ErrorCodes: () => ErrorCodes,
|
|
258
|
-
ErrorHandler: () => ErrorHandler,
|
|
259
|
-
ForbiddenError: () => ForbiddenError,
|
|
260
|
-
HandleErrors: () => HandleErrors,
|
|
261
|
-
InternalServerError: () => InternalServerError,
|
|
262
|
-
NotFoundError: () => NotFoundError,
|
|
263
|
-
Paginate: () => Paginate,
|
|
264
|
-
PaginationHelper: () => PaginationHelper,
|
|
265
|
-
RateLimitError: () => RateLimitError,
|
|
266
|
-
ResponseFormatter: () => ResponseFormatter,
|
|
267
|
-
ServiceUnavailableError: () => ServiceUnavailableError,
|
|
268
|
-
StatusCodeMapper: () => StatusCodeMapper,
|
|
269
|
-
UnauthorizedError: () => UnauthorizedError,
|
|
270
|
-
UnprocessableEntityError: () => UnprocessableEntityError,
|
|
271
|
-
Validate: () => Validate,
|
|
272
|
-
ValidationError: () => ValidationError,
|
|
273
|
-
asyncHandler: () => asyncHandler,
|
|
274
|
-
calculateCursorPaginationMeta: () => calculateCursorPaginationMeta,
|
|
275
|
-
calculatePaginationMeta: () => calculatePaginationMeta,
|
|
276
|
-
catchErrors: () => catchErrors,
|
|
277
|
-
createError: () => createError,
|
|
278
|
-
createErrorCatcher: () => createErrorCatcher,
|
|
279
|
-
createErrorHandler: () => createErrorHandler,
|
|
280
|
-
createErrorSerializer: () => createErrorSerializer,
|
|
281
|
-
createPaginationHelper: () => createPaginationHelper,
|
|
282
|
-
createResponseDecorator: () => createResponseDecorator,
|
|
283
|
-
createResponseFormatter: () => createResponseFormatter,
|
|
284
|
-
createResponseWrapper: () => createResponseWrapper,
|
|
285
|
-
createStatusCodeMapper: () => createStatusCodeMapper,
|
|
286
|
-
default: () => index_default,
|
|
287
|
-
defaultConfig: () => defaultConfig,
|
|
288
|
-
errorCatcher: () => errorCatcher,
|
|
289
|
-
errorHandlerMiddleware: () => errorHandlerMiddleware,
|
|
290
|
-
extractErrorContext: () => extractErrorContext,
|
|
291
|
-
extractFieldErrors: () => extractFieldErrors,
|
|
292
|
-
generateCursorPaginationLinks: () => generateCursorPaginationLinks,
|
|
293
|
-
generatePaginationLinks: () => generatePaginationLinks,
|
|
294
|
-
generateRequestId: () => generateRequestId,
|
|
295
|
-
isErrorResponse: () => isErrorResponse,
|
|
296
|
-
isPaginatedResponse: () => isPaginatedResponse,
|
|
297
|
-
isPlainObject: () => isPlainObject,
|
|
298
|
-
isSuccessResponse: () => isSuccessResponse,
|
|
299
|
-
responseFormatter: () => responseFormatter,
|
|
300
|
-
responseWrapper: () => responseWrapper,
|
|
301
|
-
serializeError: () => serializeError,
|
|
302
|
-
validateConfig: () => validateConfig,
|
|
303
|
-
validatePaginationInput: () => validatePaginationInput
|
|
304
|
-
});
|
|
305
|
-
module.exports = __toCommonJS(index_exports);
|
|
306
|
-
|
|
307
|
-
// src/types/config.ts
|
|
308
|
-
var defaultConfig = {
|
|
309
|
-
includeStackTraces: false,
|
|
310
|
-
environment: "production",
|
|
311
|
-
includeTimestamp: true,
|
|
312
|
-
maskSensitiveData: true,
|
|
313
|
-
requestIdHeader: "x-request-id",
|
|
314
|
-
correlationIdHeader: "x-correlation-id",
|
|
315
|
-
pagination: {
|
|
316
|
-
defaultLimit: 10,
|
|
317
|
-
maxLimit: 100,
|
|
318
|
-
includeLinks: false
|
|
319
|
-
},
|
|
320
|
-
preResponseHooks: [],
|
|
321
|
-
postResponseHooks: [],
|
|
322
|
-
passthroughMode: false,
|
|
323
|
-
sensitiveFields: ["password", "token", "secret", "apiKey", "authorization"],
|
|
324
|
-
generateRequestId: true
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
// src/utils/errorSerializer.ts
|
|
328
|
-
init_ApiError();
|
|
329
|
-
var defaultOptions = {
|
|
330
|
-
includeStack: false,
|
|
331
|
-
maskSensitiveData: true,
|
|
332
|
-
sensitiveFields: ["password", "token", "secret", "apiKey", "authorization"]
|
|
333
|
-
};
|
|
334
|
-
function maskSensitiveFields(obj, sensitiveFields) {
|
|
335
|
-
const masked = {};
|
|
336
|
-
const lowerSensitiveFields = sensitiveFields.map((f) => f.toLowerCase());
|
|
337
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
338
|
-
if (lowerSensitiveFields.includes(key.toLowerCase())) {
|
|
339
|
-
masked[key] = "[REDACTED]";
|
|
340
|
-
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
341
|
-
masked[key] = maskSensitiveFields(value, sensitiveFields);
|
|
342
|
-
} else {
|
|
343
|
-
masked[key] = value;
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
return masked;
|
|
347
|
-
}
|
|
348
|
-
function safeSerializeDetails(details, seen = /* @__PURE__ */ new WeakSet()) {
|
|
349
|
-
if (seen.has(details)) {
|
|
350
|
-
return { _circular: true };
|
|
351
|
-
}
|
|
352
|
-
seen.add(details);
|
|
353
|
-
const result = {};
|
|
354
|
-
for (const [key, value] of Object.entries(details)) {
|
|
355
|
-
if (value === null || value === void 0) {
|
|
356
|
-
result[key] = value;
|
|
357
|
-
} else if (typeof value === "object") {
|
|
358
|
-
if (value instanceof Error) {
|
|
359
|
-
result[key] = {
|
|
360
|
-
name: value.name,
|
|
361
|
-
message: value.message
|
|
362
|
-
};
|
|
363
|
-
} else if (Array.isArray(value)) {
|
|
364
|
-
result[key] = value.map(
|
|
365
|
-
(item) => typeof item === "object" && item !== null ? safeSerializeDetails(item, seen) : item
|
|
366
|
-
);
|
|
367
|
-
} else {
|
|
368
|
-
result[key] = safeSerializeDetails(value, seen);
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
result[key] = value;
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
return result;
|
|
375
|
-
}
|
|
376
|
-
function serializeError(error, options = {}) {
|
|
377
|
-
const opts = { ...defaultOptions, ...options };
|
|
378
|
-
if (opts.customSerializers) {
|
|
379
|
-
for (const [ErrorClass, serializer] of opts.customSerializers) {
|
|
380
|
-
if (error instanceof ErrorClass) {
|
|
381
|
-
const customResult = serializer(error);
|
|
382
|
-
return {
|
|
383
|
-
code: customResult.code || "CUSTOM_ERROR",
|
|
384
|
-
message: customResult.message || error.message,
|
|
385
|
-
details: opts.maskSensitiveData ? maskSensitiveFields(customResult, opts.sensitiveFields) : customResult
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
if (error instanceof ApiError) {
|
|
391
|
-
const serialized = error.serialize(opts.includeStack);
|
|
392
|
-
let details = serialized.details;
|
|
393
|
-
if (details && opts.maskSensitiveData) {
|
|
394
|
-
details = maskSensitiveFields(
|
|
395
|
-
safeSerializeDetails(details),
|
|
396
|
-
opts.sensitiveFields
|
|
397
|
-
);
|
|
398
|
-
}
|
|
399
|
-
const result2 = {
|
|
400
|
-
code: serialized.code,
|
|
401
|
-
message: serialized.message
|
|
402
|
-
};
|
|
403
|
-
if (details && Object.keys(details).length > 0) {
|
|
404
|
-
result2.details = details;
|
|
405
|
-
}
|
|
406
|
-
if (serialized.fields) {
|
|
407
|
-
result2.fields = serialized.fields;
|
|
408
|
-
}
|
|
409
|
-
if (opts.includeStack && serialized.stack) {
|
|
410
|
-
result2.stack = serialized.stack;
|
|
411
|
-
}
|
|
412
|
-
return result2;
|
|
413
|
-
}
|
|
414
|
-
const result = {
|
|
415
|
-
code: "INTERNAL_ERROR",
|
|
416
|
-
message: error.message || "An unexpected error occurred"
|
|
417
|
-
};
|
|
418
|
-
if (opts.includeStack && error.stack) {
|
|
419
|
-
result.stack = error.stack;
|
|
420
|
-
}
|
|
421
|
-
return result;
|
|
422
|
-
}
|
|
423
|
-
function extractFieldErrors(error) {
|
|
424
|
-
if (error instanceof ApiError && error.fields) {
|
|
425
|
-
return error.fields;
|
|
426
|
-
}
|
|
427
|
-
return void 0;
|
|
428
|
-
}
|
|
429
|
-
function extractErrorContext(error) {
|
|
430
|
-
if (error instanceof ApiError && error.context) {
|
|
431
|
-
return error.context;
|
|
432
|
-
}
|
|
433
|
-
return void 0;
|
|
434
|
-
}
|
|
435
|
-
function createErrorSerializer(options = {}) {
|
|
436
|
-
const opts = { ...defaultOptions, ...options };
|
|
437
|
-
return (error) => serializeError(error, opts);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// src/utils/statusCodeMapper.ts
|
|
441
|
-
init_ApiError();
|
|
442
|
-
init_predefined();
|
|
443
|
-
var defaultErrorMappings = /* @__PURE__ */ new Map([
|
|
444
|
-
[ValidationError, 400],
|
|
445
|
-
[BadRequestError, 400],
|
|
446
|
-
[UnauthorizedError, 401],
|
|
447
|
-
[ForbiddenError, 403],
|
|
448
|
-
[NotFoundError, 404],
|
|
449
|
-
[ConflictError, 409],
|
|
450
|
-
[UnprocessableEntityError, 422],
|
|
451
|
-
[RateLimitError, 429],
|
|
452
|
-
[InternalServerError, 500],
|
|
453
|
-
[ServiceUnavailableError, 503]
|
|
454
|
-
]);
|
|
455
|
-
var methodStatusCodes = {
|
|
456
|
-
GET: 200,
|
|
457
|
-
POST: 201,
|
|
458
|
-
PUT: 200,
|
|
459
|
-
PATCH: 200,
|
|
460
|
-
DELETE: 204,
|
|
461
|
-
HEAD: 200,
|
|
462
|
-
OPTIONS: 200
|
|
463
|
-
};
|
|
464
|
-
var StatusCodeMapper = class {
|
|
465
|
-
customMappings;
|
|
466
|
-
constructor(customMappings) {
|
|
467
|
-
this.customMappings = customMappings || /* @__PURE__ */ new Map();
|
|
468
|
-
}
|
|
469
|
-
/**
|
|
470
|
-
* Get status code for an error
|
|
471
|
-
*/
|
|
472
|
-
getStatusCode(error) {
|
|
473
|
-
if (error instanceof ApiError) {
|
|
474
|
-
return error.statusCode;
|
|
475
|
-
}
|
|
476
|
-
for (const [ErrorClass, statusCode] of this.customMappings) {
|
|
477
|
-
if (error instanceof ErrorClass) {
|
|
478
|
-
return statusCode;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
for (const [ErrorClass, statusCode] of defaultErrorMappings) {
|
|
482
|
-
if (error instanceof ErrorClass) {
|
|
483
|
-
return statusCode;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
return 500;
|
|
487
|
-
}
|
|
488
|
-
/**
|
|
489
|
-
* Get success status code based on HTTP method
|
|
490
|
-
*/
|
|
491
|
-
getSuccessStatusCode(method, hasData) {
|
|
492
|
-
const upperMethod = method.toUpperCase();
|
|
493
|
-
if (upperMethod === "DELETE" && !hasData) {
|
|
494
|
-
return 204;
|
|
495
|
-
}
|
|
496
|
-
return methodStatusCodes[upperMethod] || 200;
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Add custom error mapping
|
|
500
|
-
*/
|
|
501
|
-
addMapping(ErrorClass, statusCode) {
|
|
502
|
-
this.customMappings.set(ErrorClass, statusCode);
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Remove custom error mapping
|
|
506
|
-
*/
|
|
507
|
-
removeMapping(ErrorClass) {
|
|
508
|
-
return this.customMappings.delete(ErrorClass);
|
|
509
|
-
}
|
|
510
|
-
/**
|
|
511
|
-
* Check if status code indicates success
|
|
512
|
-
*/
|
|
513
|
-
static isSuccessCode(statusCode) {
|
|
514
|
-
return statusCode >= 200 && statusCode < 300;
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Check if status code indicates client error
|
|
518
|
-
*/
|
|
519
|
-
static isClientError(statusCode) {
|
|
520
|
-
return statusCode >= 400 && statusCode < 500;
|
|
521
|
-
}
|
|
522
|
-
/**
|
|
523
|
-
* Check if status code indicates server error
|
|
524
|
-
*/
|
|
525
|
-
static isServerError(statusCode) {
|
|
526
|
-
return statusCode >= 500 && statusCode < 600;
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
function createStatusCodeMapper(customMappings) {
|
|
530
|
-
return new StatusCodeMapper(customMappings);
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// src/utils/validators.ts
|
|
534
|
-
function validatePaginationInput(input, config) {
|
|
535
|
-
const page = Math.max(1, Math.floor(Number(input.page) || 1));
|
|
536
|
-
const limit = Math.min(
|
|
537
|
-
config.maxLimit,
|
|
538
|
-
Math.max(1, Math.floor(Number(input.limit) || config.defaultLimit))
|
|
539
|
-
);
|
|
540
|
-
const total = Math.max(0, Math.floor(Number(input.total) || 0));
|
|
541
|
-
return { page, limit, total };
|
|
542
|
-
}
|
|
543
|
-
function calculatePaginationMeta(input, baseUrl) {
|
|
544
|
-
const { page, limit, total } = input;
|
|
545
|
-
const totalPages = Math.ceil(total / limit) || 1;
|
|
546
|
-
const hasNextPage = page < totalPages;
|
|
547
|
-
const hasPreviousPage = page > 1;
|
|
548
|
-
const meta = {
|
|
549
|
-
page,
|
|
550
|
-
limit,
|
|
551
|
-
total,
|
|
552
|
-
totalPages,
|
|
553
|
-
hasNextPage,
|
|
554
|
-
hasPreviousPage
|
|
555
|
-
};
|
|
556
|
-
if (baseUrl) {
|
|
557
|
-
meta.links = generatePaginationLinks(page, totalPages, limit, baseUrl);
|
|
558
|
-
}
|
|
559
|
-
return meta;
|
|
560
|
-
}
|
|
561
|
-
function generatePaginationLinks(page, totalPages, limit, baseUrl) {
|
|
562
|
-
const url = new URL(baseUrl);
|
|
563
|
-
const createLink = (p) => {
|
|
564
|
-
url.searchParams.set("page", String(p));
|
|
565
|
-
url.searchParams.set("limit", String(limit));
|
|
566
|
-
return url.toString();
|
|
567
|
-
};
|
|
568
|
-
const links = {
|
|
569
|
-
self: createLink(page),
|
|
570
|
-
first: createLink(1),
|
|
571
|
-
last: createLink(totalPages)
|
|
572
|
-
};
|
|
573
|
-
if (page < totalPages) {
|
|
574
|
-
links.next = createLink(page + 1);
|
|
575
|
-
}
|
|
576
|
-
if (page > 1) {
|
|
577
|
-
links.previous = createLink(page - 1);
|
|
578
|
-
}
|
|
579
|
-
return links;
|
|
580
|
-
}
|
|
581
|
-
function calculateCursorPaginationMeta(input, baseUrl) {
|
|
582
|
-
const meta = {
|
|
583
|
-
limit: input.limit,
|
|
584
|
-
hasMore: input.hasMore
|
|
585
|
-
};
|
|
586
|
-
if (input.cursor) {
|
|
587
|
-
meta.cursor = input.cursor;
|
|
588
|
-
}
|
|
589
|
-
if (input.nextCursor) {
|
|
590
|
-
meta.nextCursor = input.nextCursor;
|
|
591
|
-
}
|
|
592
|
-
if (input.previousCursor) {
|
|
593
|
-
meta.previousCursor = input.previousCursor;
|
|
594
|
-
}
|
|
595
|
-
if (baseUrl) {
|
|
596
|
-
meta.links = generateCursorPaginationLinks(input, baseUrl);
|
|
597
|
-
}
|
|
598
|
-
return meta;
|
|
599
|
-
}
|
|
600
|
-
function generateCursorPaginationLinks(input, baseUrl) {
|
|
601
|
-
const url = new URL(baseUrl);
|
|
602
|
-
const createLink = (cursor) => {
|
|
603
|
-
if (cursor) {
|
|
604
|
-
url.searchParams.set("cursor", cursor);
|
|
605
|
-
} else {
|
|
606
|
-
url.searchParams.delete("cursor");
|
|
607
|
-
}
|
|
608
|
-
url.searchParams.set("limit", String(input.limit));
|
|
609
|
-
return url.toString();
|
|
610
|
-
};
|
|
611
|
-
const links = {
|
|
612
|
-
self: createLink(input.cursor)
|
|
613
|
-
};
|
|
614
|
-
if (input.nextCursor && input.hasMore) {
|
|
615
|
-
links.next = createLink(input.nextCursor);
|
|
616
|
-
}
|
|
617
|
-
if (input.previousCursor) {
|
|
618
|
-
links.previous = createLink(input.previousCursor);
|
|
619
|
-
}
|
|
620
|
-
return links;
|
|
621
|
-
}
|
|
622
|
-
function validateConfig(input) {
|
|
623
|
-
const errors = [];
|
|
624
|
-
if (input.environment && !["development", "staging", "production", "test"].includes(input.environment)) {
|
|
625
|
-
errors.push(`Invalid environment: ${input.environment}`);
|
|
626
|
-
}
|
|
627
|
-
if (input.pagination) {
|
|
628
|
-
if (input.pagination.defaultLimit !== void 0 && input.pagination.defaultLimit < 1) {
|
|
629
|
-
errors.push("pagination.defaultLimit must be at least 1");
|
|
630
|
-
}
|
|
631
|
-
if (input.pagination.maxLimit !== void 0 && input.pagination.maxLimit < 1) {
|
|
632
|
-
errors.push("pagination.maxLimit must be at least 1");
|
|
633
|
-
}
|
|
634
|
-
if (input.pagination.defaultLimit !== void 0 && input.pagination.maxLimit !== void 0 && input.pagination.defaultLimit > input.pagination.maxLimit) {
|
|
635
|
-
errors.push("pagination.defaultLimit cannot exceed pagination.maxLimit");
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return errors;
|
|
639
|
-
}
|
|
640
|
-
function isPlainObject(value) {
|
|
641
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
642
|
-
}
|
|
643
|
-
function generateRequestId() {
|
|
644
|
-
const timestamp = Date.now().toString(36);
|
|
645
|
-
const random = Math.random().toString(36).substring(2, 10);
|
|
646
|
-
return `${timestamp}-${random}`;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// src/core/ResponseFormatter.ts
|
|
650
|
-
var ResponseFormatter = class {
|
|
651
|
-
config;
|
|
652
|
-
statusCodeMapper;
|
|
653
|
-
constructor(config = {}) {
|
|
654
|
-
this.config = this.mergeConfig(config);
|
|
655
|
-
this.statusCodeMapper = new StatusCodeMapper(this.config.customErrorMappers);
|
|
656
|
-
}
|
|
657
|
-
/**
|
|
658
|
-
* Merge user config with defaults
|
|
659
|
-
*/
|
|
660
|
-
mergeConfig(input) {
|
|
661
|
-
return {
|
|
662
|
-
...defaultConfig,
|
|
663
|
-
...input,
|
|
664
|
-
pagination: {
|
|
665
|
-
...defaultConfig.pagination,
|
|
666
|
-
...input.pagination
|
|
667
|
-
},
|
|
668
|
-
preResponseHooks: input.preResponseHooks || defaultConfig.preResponseHooks,
|
|
669
|
-
postResponseHooks: input.postResponseHooks || defaultConfig.postResponseHooks,
|
|
670
|
-
sensitiveFields: input.sensitiveFields || defaultConfig.sensitiveFields
|
|
671
|
-
};
|
|
672
|
-
}
|
|
673
|
-
/**
|
|
674
|
-
* Get current configuration
|
|
675
|
-
*/
|
|
676
|
-
getConfig() {
|
|
677
|
-
return { ...this.config };
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* Update configuration
|
|
681
|
-
*/
|
|
682
|
-
updateConfig(config) {
|
|
683
|
-
this.config = this.mergeConfig({ ...this.config, ...config });
|
|
684
|
-
if (config.customErrorMappers) {
|
|
685
|
-
this.statusCodeMapper = new StatusCodeMapper(config.customErrorMappers);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
/**
|
|
689
|
-
* Generate timestamp
|
|
690
|
-
*/
|
|
691
|
-
getTimestamp() {
|
|
692
|
-
return this.config.includeTimestamp ? (/* @__PURE__ */ new Date()).toISOString() : "";
|
|
693
|
-
}
|
|
694
|
-
/**
|
|
695
|
-
* Apply pre-response hooks
|
|
696
|
-
*/
|
|
697
|
-
applyPreHooks(data, meta) {
|
|
698
|
-
let result = { data, meta };
|
|
699
|
-
for (const hook of this.config.preResponseHooks) {
|
|
700
|
-
const hookResult = hook(result.data, result.meta);
|
|
701
|
-
result = { data: hookResult.data, meta: hookResult.meta };
|
|
702
|
-
}
|
|
703
|
-
return result;
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* Apply post-response hooks
|
|
707
|
-
*/
|
|
708
|
-
applyPostHooks(response) {
|
|
709
|
-
let result = response;
|
|
710
|
-
for (const hook of this.config.postResponseHooks) {
|
|
711
|
-
result = hook(result);
|
|
712
|
-
}
|
|
713
|
-
return result;
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* Format a success response
|
|
717
|
-
*/
|
|
718
|
-
formatSuccess(data, meta) {
|
|
719
|
-
const processed = this.applyPreHooks(data, meta);
|
|
720
|
-
const response = {
|
|
721
|
-
success: true,
|
|
722
|
-
data: processed.data,
|
|
723
|
-
timestamp: this.getTimestamp()
|
|
724
|
-
};
|
|
725
|
-
if (processed.meta && Object.keys(processed.meta).length > 0) {
|
|
726
|
-
response.meta = processed.meta;
|
|
727
|
-
}
|
|
728
|
-
if (this.config.passthroughMode && this.config.customFormatter) {
|
|
729
|
-
return this.config.customFormatter(response);
|
|
730
|
-
}
|
|
731
|
-
return this.applyPostHooks(response);
|
|
732
|
-
}
|
|
733
|
-
/**
|
|
734
|
-
* Format an error response
|
|
735
|
-
*/
|
|
736
|
-
formatError(error, meta) {
|
|
737
|
-
const includeStack = this.config.includeStackTraces || this.config.environment === "development";
|
|
738
|
-
const errorDetails = serializeError(error, {
|
|
739
|
-
includeStack,
|
|
740
|
-
maskSensitiveData: this.config.maskSensitiveData,
|
|
741
|
-
sensitiveFields: this.config.sensitiveFields,
|
|
742
|
-
customSerializers: this.config.customSerializers
|
|
743
|
-
});
|
|
744
|
-
const response = {
|
|
745
|
-
success: false,
|
|
746
|
-
error: errorDetails,
|
|
747
|
-
timestamp: this.getTimestamp()
|
|
748
|
-
};
|
|
749
|
-
if (meta && Object.keys(meta).length > 0) {
|
|
750
|
-
response.meta = meta;
|
|
751
|
-
}
|
|
752
|
-
if (this.config.passthroughMode && this.config.customFormatter) {
|
|
753
|
-
return this.config.customFormatter(response);
|
|
754
|
-
}
|
|
755
|
-
return this.applyPostHooks(response);
|
|
756
|
-
}
|
|
757
|
-
/**
|
|
758
|
-
* Format a paginated response (offset-based)
|
|
759
|
-
*/
|
|
760
|
-
formatPaginated(data, paginationInput, meta, baseUrl) {
|
|
761
|
-
const processed = this.applyPreHooks(data, meta);
|
|
762
|
-
const validatedInput = validatePaginationInput(paginationInput, this.config.pagination);
|
|
763
|
-
const paginationMeta = calculatePaginationMeta(
|
|
764
|
-
validatedInput,
|
|
765
|
-
this.config.pagination.includeLinks ? baseUrl : void 0
|
|
766
|
-
);
|
|
767
|
-
const response = {
|
|
768
|
-
success: true,
|
|
769
|
-
data: processed.data,
|
|
770
|
-
pagination: paginationMeta,
|
|
771
|
-
timestamp: this.getTimestamp()
|
|
772
|
-
};
|
|
773
|
-
if (processed.meta && Object.keys(processed.meta).length > 0) {
|
|
774
|
-
response.meta = processed.meta;
|
|
775
|
-
}
|
|
776
|
-
return this.applyPostHooks(response);
|
|
777
|
-
}
|
|
778
|
-
/**
|
|
779
|
-
* Format a cursor-paginated response
|
|
780
|
-
*/
|
|
781
|
-
formatCursorPaginated(data, cursorInput, meta, baseUrl) {
|
|
782
|
-
const processed = this.applyPreHooks(data, meta);
|
|
783
|
-
const paginationMeta = calculateCursorPaginationMeta(
|
|
784
|
-
cursorInput,
|
|
785
|
-
this.config.pagination.includeLinks ? baseUrl : void 0
|
|
786
|
-
);
|
|
787
|
-
const response = {
|
|
788
|
-
success: true,
|
|
789
|
-
data: processed.data,
|
|
790
|
-
pagination: paginationMeta,
|
|
791
|
-
timestamp: this.getTimestamp()
|
|
792
|
-
};
|
|
793
|
-
if (processed.meta && Object.keys(processed.meta).length > 0) {
|
|
794
|
-
response.meta = processed.meta;
|
|
795
|
-
}
|
|
796
|
-
return this.applyPostHooks(response);
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* Get status code for an error
|
|
800
|
-
*/
|
|
801
|
-
getErrorStatusCode(error) {
|
|
802
|
-
return this.statusCodeMapper.getStatusCode(error);
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Get success status code based on HTTP method
|
|
806
|
-
*/
|
|
807
|
-
getSuccessStatusCode(method, hasData) {
|
|
808
|
-
return this.statusCodeMapper.getSuccessStatusCode(method, hasData);
|
|
809
|
-
}
|
|
810
|
-
/**
|
|
811
|
-
* Generate or extract request ID
|
|
812
|
-
*/
|
|
813
|
-
getRequestId(existingId) {
|
|
814
|
-
if (existingId) {
|
|
815
|
-
return existingId;
|
|
816
|
-
}
|
|
817
|
-
return this.config.generateRequestId ? generateRequestId() : "";
|
|
818
|
-
}
|
|
819
|
-
};
|
|
820
|
-
function createResponseFormatter(config) {
|
|
821
|
-
return new ResponseFormatter(config);
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// src/core/ErrorHandler.ts
|
|
825
|
-
init_ApiError();
|
|
826
|
-
var defaultErrorHandlerOptions = {
|
|
827
|
-
logErrors: true
|
|
828
|
-
};
|
|
829
|
-
var ErrorHandler = class {
|
|
830
|
-
formatter;
|
|
831
|
-
options;
|
|
832
|
-
config;
|
|
833
|
-
constructor(formatter, options = {}) {
|
|
834
|
-
this.formatter = formatter;
|
|
835
|
-
this.options = { ...defaultErrorHandlerOptions, ...options };
|
|
836
|
-
this.config = formatter.getConfig();
|
|
837
|
-
}
|
|
838
|
-
/**
|
|
839
|
-
* Extract request metadata for error context
|
|
840
|
-
*/
|
|
841
|
-
extractMeta(req) {
|
|
842
|
-
const meta = {};
|
|
843
|
-
const requestId = req.get(this.config.requestIdHeader);
|
|
844
|
-
if (requestId) {
|
|
845
|
-
meta.requestId = requestId;
|
|
846
|
-
}
|
|
847
|
-
const correlationId = req.get(this.config.correlationIdHeader);
|
|
848
|
-
if (correlationId) {
|
|
849
|
-
meta.correlationId = correlationId;
|
|
850
|
-
}
|
|
851
|
-
return meta;
|
|
852
|
-
}
|
|
853
|
-
/**
|
|
854
|
-
* Log error if enabled
|
|
855
|
-
*/
|
|
856
|
-
logError(error, req) {
|
|
857
|
-
if (!this.options.logErrors) return;
|
|
858
|
-
if (this.options.errorLogger) {
|
|
859
|
-
this.options.errorLogger(error, req);
|
|
860
|
-
return;
|
|
861
|
-
}
|
|
862
|
-
const isOperational = error instanceof ApiError ? error.isOperational : false;
|
|
863
|
-
const logLevel = isOperational ? "warn" : "error";
|
|
864
|
-
console[logLevel]({
|
|
865
|
-
message: error.message,
|
|
866
|
-
name: error.name,
|
|
867
|
-
stack: error.stack,
|
|
868
|
-
path: req.path,
|
|
869
|
-
method: req.method,
|
|
870
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
871
|
-
});
|
|
872
|
-
}
|
|
873
|
-
/**
|
|
874
|
-
* Check if error is operational (expected)
|
|
875
|
-
*/
|
|
876
|
-
isOperationalError(error) {
|
|
877
|
-
if (error instanceof ApiError) {
|
|
878
|
-
return error.isOperational;
|
|
879
|
-
}
|
|
880
|
-
return false;
|
|
881
|
-
}
|
|
882
|
-
/**
|
|
883
|
-
* Handle the error and send response
|
|
884
|
-
*/
|
|
885
|
-
handle(error, req, res, _next) {
|
|
886
|
-
this.logError(error, req);
|
|
887
|
-
const transformedError = this.options.transformError ? this.options.transformError(error) : error;
|
|
888
|
-
if (!this.isOperationalError(transformedError) && this.options.handleNonOperational) {
|
|
889
|
-
this.options.handleNonOperational(transformedError, req, res);
|
|
890
|
-
return;
|
|
891
|
-
}
|
|
892
|
-
const statusCode = this.formatter.getErrorStatusCode(transformedError);
|
|
893
|
-
const meta = this.extractMeta(req);
|
|
894
|
-
const response = this.formatter.formatError(transformedError, meta);
|
|
895
|
-
res.status(statusCode).json(response);
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Create Express error handling middleware
|
|
899
|
-
*/
|
|
900
|
-
middleware() {
|
|
901
|
-
return (error, req, res, next) => {
|
|
902
|
-
this.handle(error, req, res, next);
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
};
|
|
906
|
-
function createErrorHandler(formatter, options) {
|
|
907
|
-
return new ErrorHandler(formatter, options);
|
|
908
|
-
}
|
|
909
|
-
function errorHandlerMiddleware(formatter, options) {
|
|
910
|
-
const handler = new ErrorHandler(formatter, options);
|
|
911
|
-
return handler.middleware();
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// src/core/PaginationHelper.ts
|
|
915
|
-
var PaginationHelper = class {
|
|
916
|
-
config;
|
|
917
|
-
constructor(config = {}) {
|
|
918
|
-
this.config = {
|
|
919
|
-
defaultLimit: config.defaultLimit ?? 10,
|
|
920
|
-
maxLimit: config.maxLimit ?? 100,
|
|
921
|
-
includeLinks: config.includeLinks ?? false,
|
|
922
|
-
baseUrl: config.baseUrl
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Extract pagination parameters from request
|
|
927
|
-
*/
|
|
928
|
-
extractFromRequest(req) {
|
|
929
|
-
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
|
|
930
|
-
const limit = Math.min(
|
|
931
|
-
this.config.maxLimit,
|
|
932
|
-
Math.max(1, parseInt(req.query.limit, 10) || this.config.defaultLimit)
|
|
933
|
-
);
|
|
934
|
-
return { page, limit };
|
|
935
|
-
}
|
|
936
|
-
/**
|
|
937
|
-
* Extract cursor pagination parameters from request
|
|
938
|
-
*/
|
|
939
|
-
extractCursorFromRequest(req) {
|
|
940
|
-
const cursor = req.query.cursor;
|
|
941
|
-
const limit = Math.min(
|
|
942
|
-
this.config.maxLimit,
|
|
943
|
-
Math.max(1, parseInt(req.query.limit, 10) || this.config.defaultLimit)
|
|
944
|
-
);
|
|
945
|
-
return { cursor, limit };
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Calculate offset for database queries
|
|
949
|
-
*/
|
|
950
|
-
calculateOffset(page, limit) {
|
|
951
|
-
return (page - 1) * limit;
|
|
952
|
-
}
|
|
953
|
-
/**
|
|
954
|
-
* Build pagination metadata
|
|
955
|
-
*/
|
|
956
|
-
buildMeta(input, baseUrl) {
|
|
957
|
-
const validated = validatePaginationInput(input, this.config);
|
|
958
|
-
return calculatePaginationMeta(
|
|
959
|
-
validated,
|
|
960
|
-
this.config.includeLinks ? baseUrl || this.config.baseUrl : void 0
|
|
961
|
-
);
|
|
962
|
-
}
|
|
963
|
-
/**
|
|
964
|
-
* Build cursor pagination metadata
|
|
965
|
-
*/
|
|
966
|
-
buildCursorMeta(input, baseUrl) {
|
|
967
|
-
return calculateCursorPaginationMeta(
|
|
968
|
-
input,
|
|
969
|
-
this.config.includeLinks ? baseUrl || this.config.baseUrl : void 0
|
|
970
|
-
);
|
|
971
|
-
}
|
|
972
|
-
/**
|
|
973
|
-
* Create pagination info from data array and total count
|
|
974
|
-
*/
|
|
975
|
-
fromArray(data, page, limit, total) {
|
|
976
|
-
const pagination = this.buildMeta({ page, limit, total });
|
|
977
|
-
return { data, pagination };
|
|
978
|
-
}
|
|
979
|
-
/**
|
|
980
|
-
* Paginate an in-memory array
|
|
981
|
-
*/
|
|
982
|
-
paginateArray(items, page, limit) {
|
|
983
|
-
const total = items.length;
|
|
984
|
-
const offset = this.calculateOffset(page, limit);
|
|
985
|
-
const data = items.slice(offset, offset + limit);
|
|
986
|
-
return this.fromArray(data, page, limit, total);
|
|
987
|
-
}
|
|
988
|
-
/**
|
|
989
|
-
* Check if there are more pages
|
|
990
|
-
*/
|
|
991
|
-
hasNextPage(page, limit, total) {
|
|
992
|
-
const totalPages = Math.ceil(total / limit);
|
|
993
|
-
return page < totalPages;
|
|
994
|
-
}
|
|
995
|
-
/**
|
|
996
|
-
* Check if there are previous pages
|
|
997
|
-
*/
|
|
998
|
-
hasPreviousPage(page) {
|
|
999
|
-
return page > 1;
|
|
1000
|
-
}
|
|
1001
|
-
/**
|
|
1002
|
-
* Get total pages count
|
|
1003
|
-
*/
|
|
1004
|
-
getTotalPages(total, limit) {
|
|
1005
|
-
return Math.ceil(total / limit) || 1;
|
|
1006
|
-
}
|
|
1007
|
-
/**
|
|
1008
|
-
* Validate page number is within bounds
|
|
1009
|
-
*/
|
|
1010
|
-
isValidPage(page, total, limit) {
|
|
1011
|
-
if (page < 1) return false;
|
|
1012
|
-
if (total === 0) return page === 1;
|
|
1013
|
-
const totalPages = this.getTotalPages(total, limit);
|
|
1014
|
-
return page <= totalPages;
|
|
1015
|
-
}
|
|
1016
|
-
/**
|
|
1017
|
-
* Get configuration
|
|
1018
|
-
*/
|
|
1019
|
-
getConfig() {
|
|
1020
|
-
return { ...this.config };
|
|
1021
|
-
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Update configuration
|
|
1024
|
-
*/
|
|
1025
|
-
updateConfig(config) {
|
|
1026
|
-
this.config = { ...this.config, ...config };
|
|
1027
|
-
}
|
|
1028
|
-
};
|
|
1029
|
-
function createPaginationHelper(config) {
|
|
1030
|
-
return new PaginationHelper(config);
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// src/errors/index.ts
|
|
1034
|
-
init_ApiError();
|
|
1035
|
-
init_predefined();
|
|
1036
|
-
|
|
1037
|
-
// src/middleware/responseWrapper.ts
|
|
1038
|
-
function responseWrapper(options = {}) {
|
|
1039
|
-
const { skipPaths = [], skipCondition, ...formatterConfig } = options;
|
|
1040
|
-
const formatter = new ResponseFormatter(formatterConfig);
|
|
1041
|
-
const config = formatter.getConfig();
|
|
1042
|
-
return (req, res, next) => {
|
|
1043
|
-
const formattedReq = req;
|
|
1044
|
-
const formattedRes = res;
|
|
1045
|
-
if (skipPaths.includes(req.path) || skipCondition && skipCondition(req)) {
|
|
1046
|
-
next();
|
|
1047
|
-
return;
|
|
1048
|
-
}
|
|
1049
|
-
let requestId = req.get(config.requestIdHeader);
|
|
1050
|
-
if (!requestId && config.generateRequestId) {
|
|
1051
|
-
requestId = generateRequestId();
|
|
1052
|
-
}
|
|
1053
|
-
if (requestId) {
|
|
1054
|
-
formattedReq.requestId = requestId;
|
|
1055
|
-
res.setHeader(config.requestIdHeader, requestId);
|
|
1056
|
-
}
|
|
1057
|
-
const correlationId = req.get(config.correlationIdHeader);
|
|
1058
|
-
if (correlationId) {
|
|
1059
|
-
formattedReq.correlationId = correlationId;
|
|
1060
|
-
res.setHeader(config.correlationIdHeader, correlationId);
|
|
1061
|
-
}
|
|
1062
|
-
formattedRes.formatter = formatter;
|
|
1063
|
-
const buildMeta = (additionalMeta) => {
|
|
1064
|
-
const meta = {};
|
|
1065
|
-
if (requestId) meta.requestId = requestId;
|
|
1066
|
-
if (correlationId) meta.correlationId = correlationId;
|
|
1067
|
-
return { ...meta, ...additionalMeta };
|
|
1068
|
-
};
|
|
1069
|
-
const getBaseUrl = () => {
|
|
1070
|
-
const protocol = req.protocol;
|
|
1071
|
-
const host = req.get("host") || "localhost";
|
|
1072
|
-
return `${protocol}://${host}${req.originalUrl.split("?")[0]}`;
|
|
1073
|
-
};
|
|
1074
|
-
formattedRes.respond = (data, meta, statusCode) => {
|
|
1075
|
-
const response = formatter.formatSuccess(data, buildMeta(meta));
|
|
1076
|
-
const status = statusCode ?? formatter.getSuccessStatusCode(req.method, data !== void 0);
|
|
1077
|
-
res.status(status).json(response);
|
|
1078
|
-
};
|
|
1079
|
-
formattedRes.respondPaginated = (data, pagination, meta) => {
|
|
1080
|
-
const response = formatter.formatPaginated(
|
|
1081
|
-
data,
|
|
1082
|
-
pagination,
|
|
1083
|
-
buildMeta(meta),
|
|
1084
|
-
getBaseUrl()
|
|
1085
|
-
);
|
|
1086
|
-
res.status(200).json(response);
|
|
1087
|
-
};
|
|
1088
|
-
formattedRes.respondCursorPaginated = (data, pagination, meta) => {
|
|
1089
|
-
const response = formatter.formatCursorPaginated(
|
|
1090
|
-
data,
|
|
1091
|
-
pagination,
|
|
1092
|
-
buildMeta(meta),
|
|
1093
|
-
getBaseUrl()
|
|
1094
|
-
);
|
|
1095
|
-
res.status(200).json(response);
|
|
1096
|
-
};
|
|
1097
|
-
formattedRes.respondError = (error, meta) => {
|
|
1098
|
-
const response = formatter.formatError(error, buildMeta(meta));
|
|
1099
|
-
const statusCode = formatter.getErrorStatusCode(error);
|
|
1100
|
-
res.status(statusCode).json(response);
|
|
1101
|
-
};
|
|
1102
|
-
next();
|
|
1103
|
-
};
|
|
1104
|
-
}
|
|
1105
|
-
function createResponseWrapper(formatter, options = {}) {
|
|
1106
|
-
const { skipPaths = [], skipCondition } = options;
|
|
1107
|
-
const config = formatter.getConfig();
|
|
1108
|
-
return (req, res, next) => {
|
|
1109
|
-
const formattedReq = req;
|
|
1110
|
-
const formattedRes = res;
|
|
1111
|
-
if (skipPaths.includes(req.path) || skipCondition && skipCondition(req)) {
|
|
1112
|
-
next();
|
|
1113
|
-
return;
|
|
1114
|
-
}
|
|
1115
|
-
let requestId = req.get(config.requestIdHeader);
|
|
1116
|
-
if (!requestId && config.generateRequestId) {
|
|
1117
|
-
requestId = generateRequestId();
|
|
1118
|
-
}
|
|
1119
|
-
if (requestId) {
|
|
1120
|
-
formattedReq.requestId = requestId;
|
|
1121
|
-
res.setHeader(config.requestIdHeader, requestId);
|
|
1122
|
-
}
|
|
1123
|
-
const correlationId = req.get(config.correlationIdHeader);
|
|
1124
|
-
if (correlationId) {
|
|
1125
|
-
formattedReq.correlationId = correlationId;
|
|
1126
|
-
res.setHeader(config.correlationIdHeader, correlationId);
|
|
1127
|
-
}
|
|
1128
|
-
formattedRes.formatter = formatter;
|
|
1129
|
-
const buildMeta = (additionalMeta) => {
|
|
1130
|
-
const meta = {};
|
|
1131
|
-
if (requestId) meta.requestId = requestId;
|
|
1132
|
-
if (correlationId) meta.correlationId = correlationId;
|
|
1133
|
-
return { ...meta, ...additionalMeta };
|
|
1134
|
-
};
|
|
1135
|
-
const getBaseUrl = () => {
|
|
1136
|
-
const protocol = req.protocol;
|
|
1137
|
-
const host = req.get("host") || "localhost";
|
|
1138
|
-
return `${protocol}://${host}${req.originalUrl.split("?")[0]}`;
|
|
1139
|
-
};
|
|
1140
|
-
formattedRes.respond = (data, meta, statusCode) => {
|
|
1141
|
-
const response = formatter.formatSuccess(data, buildMeta(meta));
|
|
1142
|
-
const status = statusCode ?? formatter.getSuccessStatusCode(req.method, data !== void 0);
|
|
1143
|
-
res.status(status).json(response);
|
|
1144
|
-
};
|
|
1145
|
-
formattedRes.respondPaginated = (data, pagination, meta) => {
|
|
1146
|
-
const response = formatter.formatPaginated(data, pagination, buildMeta(meta), getBaseUrl());
|
|
1147
|
-
res.status(200).json(response);
|
|
1148
|
-
};
|
|
1149
|
-
formattedRes.respondCursorPaginated = (data, pagination, meta) => {
|
|
1150
|
-
const response = formatter.formatCursorPaginated(data, pagination, buildMeta(meta), getBaseUrl());
|
|
1151
|
-
res.status(200).json(response);
|
|
1152
|
-
};
|
|
1153
|
-
formattedRes.respondError = (error, meta) => {
|
|
1154
|
-
const response = formatter.formatError(error, buildMeta(meta));
|
|
1155
|
-
const statusCode = formatter.getErrorStatusCode(error);
|
|
1156
|
-
res.status(statusCode).json(response);
|
|
1157
|
-
};
|
|
1158
|
-
next();
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// src/middleware/errorCatcher.ts
|
|
1163
|
-
function errorCatcher(options = {}) {
|
|
1164
|
-
const {
|
|
1165
|
-
logErrors = true,
|
|
1166
|
-
errorLogger,
|
|
1167
|
-
transformError,
|
|
1168
|
-
customHandler,
|
|
1169
|
-
...formatterConfig
|
|
1170
|
-
} = options;
|
|
1171
|
-
const formatter = new ResponseFormatter(formatterConfig);
|
|
1172
|
-
const config = formatter.getConfig();
|
|
1173
|
-
return (error, req, res, _next) => {
|
|
1174
|
-
if (customHandler && customHandler(error, req, res)) {
|
|
1175
|
-
return;
|
|
1176
|
-
}
|
|
1177
|
-
if (logErrors) {
|
|
1178
|
-
if (errorLogger) {
|
|
1179
|
-
errorLogger(error, req);
|
|
1180
|
-
} else {
|
|
1181
|
-
console.error({
|
|
1182
|
-
message: error.message,
|
|
1183
|
-
name: error.name,
|
|
1184
|
-
stack: error.stack,
|
|
1185
|
-
path: req.path,
|
|
1186
|
-
method: req.method,
|
|
1187
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1188
|
-
});
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
1191
|
-
const finalError = transformError ? transformError(error) : error;
|
|
1192
|
-
const formattedReq = req;
|
|
1193
|
-
const meta = {};
|
|
1194
|
-
if (formattedReq.requestId) {
|
|
1195
|
-
meta.requestId = formattedReq.requestId;
|
|
1196
|
-
} else {
|
|
1197
|
-
const requestId = req.get(config.requestIdHeader);
|
|
1198
|
-
if (requestId) {
|
|
1199
|
-
meta.requestId = requestId;
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
if (formattedReq.correlationId) {
|
|
1203
|
-
meta.correlationId = formattedReq.correlationId;
|
|
1204
|
-
} else {
|
|
1205
|
-
const correlationId = req.get(config.correlationIdHeader);
|
|
1206
|
-
if (correlationId) {
|
|
1207
|
-
meta.correlationId = correlationId;
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
const response = formatter.formatError(finalError, meta);
|
|
1211
|
-
const statusCode = formatter.getErrorStatusCode(finalError);
|
|
1212
|
-
res.status(statusCode).json(response);
|
|
1213
|
-
};
|
|
1214
|
-
}
|
|
1215
|
-
function createErrorCatcher(formatter, options = {}) {
|
|
1216
|
-
const { logErrors = true, errorLogger, transformError, customHandler } = options;
|
|
1217
|
-
const config = formatter.getConfig();
|
|
1218
|
-
return (error, req, res, _next) => {
|
|
1219
|
-
if (customHandler && customHandler(error, req, res)) {
|
|
1220
|
-
return;
|
|
1221
|
-
}
|
|
1222
|
-
if (logErrors) {
|
|
1223
|
-
if (errorLogger) {
|
|
1224
|
-
errorLogger(error, req);
|
|
1225
|
-
} else {
|
|
1226
|
-
console.error({
|
|
1227
|
-
message: error.message,
|
|
1228
|
-
name: error.name,
|
|
1229
|
-
stack: error.stack,
|
|
1230
|
-
path: req.path,
|
|
1231
|
-
method: req.method,
|
|
1232
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1233
|
-
});
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
const finalError = transformError ? transformError(error) : error;
|
|
1237
|
-
const formattedReq = req;
|
|
1238
|
-
const meta = {};
|
|
1239
|
-
if (formattedReq.requestId) {
|
|
1240
|
-
meta.requestId = formattedReq.requestId;
|
|
1241
|
-
} else {
|
|
1242
|
-
const requestId = req.get(config.requestIdHeader);
|
|
1243
|
-
if (requestId) {
|
|
1244
|
-
meta.requestId = requestId;
|
|
1245
|
-
}
|
|
1246
|
-
}
|
|
1247
|
-
if (formattedReq.correlationId) {
|
|
1248
|
-
meta.correlationId = formattedReq.correlationId;
|
|
1249
|
-
} else {
|
|
1250
|
-
const correlationId = req.get(config.correlationIdHeader);
|
|
1251
|
-
if (correlationId) {
|
|
1252
|
-
meta.correlationId = correlationId;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
const response = formatter.formatError(finalError, meta);
|
|
1256
|
-
const statusCode = formatter.getErrorStatusCode(finalError);
|
|
1257
|
-
res.status(statusCode).json(response);
|
|
1258
|
-
};
|
|
1259
|
-
}
|
|
1260
|
-
function asyncHandler(fn) {
|
|
1261
|
-
return (req, res, next) => {
|
|
1262
|
-
Promise.resolve(fn(req, res, next)).catch(next);
|
|
1263
|
-
};
|
|
1264
|
-
}
|
|
1265
|
-
function catchErrors(fn) {
|
|
1266
|
-
return async (req, res, next) => {
|
|
1267
|
-
try {
|
|
1268
|
-
await fn(req, res);
|
|
1269
|
-
} catch (error) {
|
|
1270
|
-
next(error);
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
}
|
|
1274
|
-
|
|
1275
|
-
// src/decorators/response.ts
|
|
1276
|
-
function createResponseDecorator(formatter) {
|
|
1277
|
-
return (options = {}) => {
|
|
1278
|
-
return (_target, _propertyKey, descriptor) => {
|
|
1279
|
-
const originalMethod = descriptor.value;
|
|
1280
|
-
descriptor.value = async function(req, res, next) {
|
|
1281
|
-
try {
|
|
1282
|
-
const result = await originalMethod.call(this, req, res, next);
|
|
1283
|
-
if (res.headersSent) {
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
if (options.raw) {
|
|
1287
|
-
res.json(result);
|
|
1288
|
-
return;
|
|
1289
|
-
}
|
|
1290
|
-
const formattedReq = req;
|
|
1291
|
-
const meta = { ...options.meta };
|
|
1292
|
-
if (formattedReq.requestId) {
|
|
1293
|
-
meta.requestId = formattedReq.requestId;
|
|
1294
|
-
}
|
|
1295
|
-
if (formattedReq.correlationId) {
|
|
1296
|
-
meta.correlationId = formattedReq.correlationId;
|
|
1297
|
-
}
|
|
1298
|
-
const response = formatter.formatSuccess(result, meta);
|
|
1299
|
-
const statusCode = options.statusCode ?? formatter.getSuccessStatusCode(req.method, result !== void 0);
|
|
1300
|
-
res.status(statusCode).json(response);
|
|
1301
|
-
} catch (error) {
|
|
1302
|
-
if (next) {
|
|
1303
|
-
next(error);
|
|
1304
|
-
} else {
|
|
1305
|
-
throw error;
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
|
-
};
|
|
1309
|
-
return descriptor;
|
|
1310
|
-
};
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
function HandleErrors() {
|
|
1314
|
-
return (_target, _propertyKey, descriptor) => {
|
|
1315
|
-
const originalMethod = descriptor.value;
|
|
1316
|
-
descriptor.value = async function(req, res, next) {
|
|
1317
|
-
try {
|
|
1318
|
-
await originalMethod.call(this, req, res, next);
|
|
1319
|
-
} catch (error) {
|
|
1320
|
-
next(error);
|
|
1321
|
-
}
|
|
1322
|
-
};
|
|
1323
|
-
return descriptor;
|
|
1324
|
-
};
|
|
1325
|
-
}
|
|
1326
|
-
function ApiRoute(formatter, options = {}) {
|
|
1327
|
-
return (_target, _propertyKey, descriptor) => {
|
|
1328
|
-
const originalMethod = descriptor.value;
|
|
1329
|
-
descriptor.value = async function(req, res, next) {
|
|
1330
|
-
try {
|
|
1331
|
-
const result = await originalMethod.call(this, req, res, next);
|
|
1332
|
-
if (res.headersSent) {
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
if (options.raw) {
|
|
1336
|
-
res.json(result);
|
|
1337
|
-
return;
|
|
1338
|
-
}
|
|
1339
|
-
const formattedReq = req;
|
|
1340
|
-
const meta = { ...options.meta };
|
|
1341
|
-
if (formattedReq.requestId) {
|
|
1342
|
-
meta.requestId = formattedReq.requestId;
|
|
1343
|
-
}
|
|
1344
|
-
if (formattedReq.correlationId) {
|
|
1345
|
-
meta.correlationId = formattedReq.correlationId;
|
|
1346
|
-
}
|
|
1347
|
-
const response = formatter.formatSuccess(result, meta);
|
|
1348
|
-
const statusCode = options.statusCode ?? formatter.getSuccessStatusCode(req.method, result !== void 0);
|
|
1349
|
-
res.status(statusCode).json(response);
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
next(error);
|
|
1352
|
-
}
|
|
1353
|
-
};
|
|
1354
|
-
return descriptor;
|
|
1355
|
-
};
|
|
1356
|
-
}
|
|
1357
|
-
function Validate(validator, source = "body") {
|
|
1358
|
-
return (_target, _propertyKey, descriptor) => {
|
|
1359
|
-
const originalMethod = descriptor.value;
|
|
1360
|
-
descriptor.value = async function(req, res, next) {
|
|
1361
|
-
const data = req[source];
|
|
1362
|
-
const result = validator(data);
|
|
1363
|
-
if (!result.valid) {
|
|
1364
|
-
const { ValidationError: ValidationError2 } = await Promise.resolve().then(() => (init_predefined(), predefined_exports));
|
|
1365
|
-
next(new ValidationError2("Validation failed", result.errors));
|
|
1366
|
-
return;
|
|
1367
|
-
}
|
|
1368
|
-
await originalMethod.call(this, req, res, next);
|
|
1369
|
-
};
|
|
1370
|
-
return descriptor;
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
// src/decorators/paginate.ts
|
|
1375
|
-
function Paginate(formatter, options = {}) {
|
|
1376
|
-
const paginationHelper = new PaginationHelper({
|
|
1377
|
-
defaultLimit: options.defaultLimit ?? 10,
|
|
1378
|
-
maxLimit: options.maxLimit ?? 100,
|
|
1379
|
-
includeLinks: options.includeLinks ?? false
|
|
1380
|
-
});
|
|
1381
|
-
return (_target, _propertyKey, descriptor) => {
|
|
1382
|
-
const originalMethod = descriptor.value;
|
|
1383
|
-
descriptor.value = async function(req, res, next) {
|
|
1384
|
-
try {
|
|
1385
|
-
const { page, limit } = paginationHelper.extractFromRequest(req);
|
|
1386
|
-
req.pagination = {
|
|
1387
|
-
page,
|
|
1388
|
-
limit,
|
|
1389
|
-
offset: paginationHelper.calculateOffset(page, limit)
|
|
1390
|
-
};
|
|
1391
|
-
const result = await originalMethod.call(this, req, res, next);
|
|
1392
|
-
if (res.headersSent) {
|
|
1393
|
-
return;
|
|
1394
|
-
}
|
|
1395
|
-
const formattedReq = req;
|
|
1396
|
-
const meta = { ...options.meta };
|
|
1397
|
-
if (formattedReq.requestId) {
|
|
1398
|
-
meta.requestId = formattedReq.requestId;
|
|
1399
|
-
}
|
|
1400
|
-
if (formattedReq.correlationId) {
|
|
1401
|
-
meta.correlationId = formattedReq.correlationId;
|
|
1402
|
-
}
|
|
1403
|
-
const baseUrl = options.includeLinks ? `${req.protocol}://${req.get("host")}${req.originalUrl.split("?")[0]}` : void 0;
|
|
1404
|
-
const response = formatter.formatPaginated(
|
|
1405
|
-
result.data,
|
|
1406
|
-
{ page, limit, total: result.total },
|
|
1407
|
-
Object.keys(meta).length > 0 ? meta : void 0,
|
|
1408
|
-
baseUrl
|
|
1409
|
-
);
|
|
1410
|
-
res.status(200).json(response);
|
|
1411
|
-
} catch (error) {
|
|
1412
|
-
next(error);
|
|
1413
|
-
}
|
|
1414
|
-
};
|
|
1415
|
-
return descriptor;
|
|
1416
|
-
};
|
|
1417
|
-
}
|
|
1418
|
-
function CursorPaginate(formatter, options = {}) {
|
|
1419
|
-
const paginationHelper = new PaginationHelper({
|
|
1420
|
-
defaultLimit: options.defaultLimit ?? 10,
|
|
1421
|
-
maxLimit: options.maxLimit ?? 100,
|
|
1422
|
-
includeLinks: options.includeLinks ?? false
|
|
1423
|
-
});
|
|
1424
|
-
return (_target, _propertyKey, descriptor) => {
|
|
1425
|
-
const originalMethod = descriptor.value;
|
|
1426
|
-
descriptor.value = async function(req, res, next) {
|
|
1427
|
-
try {
|
|
1428
|
-
const { cursor, limit } = paginationHelper.extractCursorFromRequest(req);
|
|
1429
|
-
req.pagination = {
|
|
1430
|
-
cursor,
|
|
1431
|
-
limit
|
|
1432
|
-
};
|
|
1433
|
-
const result = await originalMethod.call(this, req, res, next);
|
|
1434
|
-
if (res.headersSent) {
|
|
1435
|
-
return;
|
|
1436
|
-
}
|
|
1437
|
-
const formattedReq = req;
|
|
1438
|
-
const meta = { ...options.meta };
|
|
1439
|
-
if (formattedReq.requestId) {
|
|
1440
|
-
meta.requestId = formattedReq.requestId;
|
|
1441
|
-
}
|
|
1442
|
-
if (formattedReq.correlationId) {
|
|
1443
|
-
meta.correlationId = formattedReq.correlationId;
|
|
1444
|
-
}
|
|
1445
|
-
const baseUrl = options.includeLinks ? `${req.protocol}://${req.get("host")}${req.originalUrl.split("?")[0]}` : void 0;
|
|
1446
|
-
const response = formatter.formatCursorPaginated(
|
|
1447
|
-
result.data,
|
|
1448
|
-
{
|
|
1449
|
-
limit,
|
|
1450
|
-
cursor,
|
|
1451
|
-
nextCursor: result.nextCursor,
|
|
1452
|
-
previousCursor: result.previousCursor,
|
|
1453
|
-
hasMore: result.hasMore
|
|
1454
|
-
},
|
|
1455
|
-
Object.keys(meta).length > 0 ? meta : void 0,
|
|
1456
|
-
baseUrl
|
|
1457
|
-
);
|
|
1458
|
-
res.status(200).json(response);
|
|
1459
|
-
} catch (error) {
|
|
1460
|
-
next(error);
|
|
1461
|
-
}
|
|
1462
|
-
};
|
|
1463
|
-
return descriptor;
|
|
1464
|
-
};
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// src/types/responses.ts
|
|
1468
|
-
function isSuccessResponse(response) {
|
|
1469
|
-
return response.success === true;
|
|
1470
|
-
}
|
|
1471
|
-
function isErrorResponse(response) {
|
|
1472
|
-
return response.success === false;
|
|
1473
|
-
}
|
|
1474
|
-
function isPaginatedResponse(response) {
|
|
1475
|
-
return response.success === true && "pagination" in response;
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
// src/types/errors.ts
|
|
1479
|
-
var ErrorCodes = {
|
|
1480
|
-
VALIDATION_ERROR: "VALIDATION_ERROR",
|
|
1481
|
-
NOT_FOUND: "NOT_FOUND",
|
|
1482
|
-
UNAUTHORIZED: "UNAUTHORIZED",
|
|
1483
|
-
FORBIDDEN: "FORBIDDEN",
|
|
1484
|
-
CONFLICT: "CONFLICT",
|
|
1485
|
-
INTERNAL_ERROR: "INTERNAL_ERROR",
|
|
1486
|
-
BAD_REQUEST: "BAD_REQUEST",
|
|
1487
|
-
RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
|
|
1488
|
-
SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
|
|
1489
|
-
UNPROCESSABLE_ENTITY: "UNPROCESSABLE_ENTITY"
|
|
1490
|
-
};
|
|
1491
|
-
|
|
1492
|
-
// src/index.ts
|
|
1493
|
-
function responseFormatter(options = {}) {
|
|
1494
|
-
return {
|
|
1495
|
-
wrapper: responseWrapper(options),
|
|
1496
|
-
errorHandler: errorCatcher(options)
|
|
1497
|
-
};
|
|
1498
|
-
}
|
|
1499
|
-
var index_default = responseFormatter;
|
|
1500
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
1501
|
-
0 && (module.exports = {
|
|
1502
|
-
ApiError,
|
|
1503
|
-
ApiRoute,
|
|
1504
|
-
BadRequestError,
|
|
1505
|
-
ConflictError,
|
|
1506
|
-
CursorPaginate,
|
|
1507
|
-
ErrorCodes,
|
|
1508
|
-
ErrorHandler,
|
|
1509
|
-
ForbiddenError,
|
|
1510
|
-
HandleErrors,
|
|
1511
|
-
InternalServerError,
|
|
1512
|
-
NotFoundError,
|
|
1513
|
-
Paginate,
|
|
1514
|
-
PaginationHelper,
|
|
1515
|
-
RateLimitError,
|
|
1516
|
-
ResponseFormatter,
|
|
1517
|
-
ServiceUnavailableError,
|
|
1518
|
-
StatusCodeMapper,
|
|
1519
|
-
UnauthorizedError,
|
|
1520
|
-
UnprocessableEntityError,
|
|
1521
|
-
Validate,
|
|
1522
|
-
ValidationError,
|
|
1523
|
-
asyncHandler,
|
|
1524
|
-
calculateCursorPaginationMeta,
|
|
1525
|
-
calculatePaginationMeta,
|
|
1526
|
-
catchErrors,
|
|
1527
|
-
createError,
|
|
1528
|
-
createErrorCatcher,
|
|
1529
|
-
createErrorHandler,
|
|
1530
|
-
createErrorSerializer,
|
|
1531
|
-
createPaginationHelper,
|
|
1532
|
-
createResponseDecorator,
|
|
1533
|
-
createResponseFormatter,
|
|
1534
|
-
createResponseWrapper,
|
|
1535
|
-
createStatusCodeMapper,
|
|
1536
|
-
defaultConfig,
|
|
1537
|
-
errorCatcher,
|
|
1538
|
-
errorHandlerMiddleware,
|
|
1539
|
-
extractErrorContext,
|
|
1540
|
-
extractFieldErrors,
|
|
1541
|
-
generateCursorPaginationLinks,
|
|
1542
|
-
generatePaginationLinks,
|
|
1543
|
-
generateRequestId,
|
|
1544
|
-
isErrorResponse,
|
|
1545
|
-
isPaginatedResponse,
|
|
1546
|
-
isPlainObject,
|
|
1547
|
-
isSuccessResponse,
|
|
1548
|
-
responseFormatter,
|
|
1549
|
-
responseWrapper,
|
|
1550
|
-
serializeError,
|
|
1551
|
-
validateConfig,
|
|
1552
|
-
validatePaginationInput
|
|
1553
|
-
});
|
|
1
|
+
'use strict';Object.defineProperty(exports,'__esModule',{value:true});var xe=Object.defineProperty;var G=(r,e)=>()=>(r&&(e=r(r=0)),e);var Pe=(r,e)=>{for(var t in e)xe(r,t,{get:e[t],enumerable:true});};exports.ApiError=void 0;var q=G(()=>{exports.ApiError=class r extends Error{code;statusCode;details;fields;context;isOperational;timestamp;constructor(e,t={}){super(e),this.name=this.constructor.name,this.code=t.code||"INTERNAL_ERROR",this.statusCode=t.statusCode||500,this.details=t.details,this.fields=t.fields,this.context=t.context,this.isOperational=t.isOperational??true,this.timestamp=new Date().toISOString(),Error.captureStackTrace(this,this.constructor),t.cause&&(this.cause=t.cause);}serialize(e=false){let t={code:this.code,message:this.message,statusCode:this.statusCode};return this.details&&(t.details=this.details),this.fields&&(t.fields=this.fields),this.context&&(t.context=this.context),e&&this.stack&&(t.stack=this.stack),t}getErrorChain(){let e=[],t=this,o=new WeakSet;for(;t&&!o.has(t);)o.add(t),e.push({name:t.name,message:t.message,code:t instanceof r?t.code:void 0}),t=t.cause;return e}withContext(e){return new r(this.message,{code:this.code,statusCode:this.statusCode,details:this.details,fields:this.fields,context:{...this.context,...e},cause:this.cause,isOperational:this.isOperational})}toJSON(){return {name:this.name,message:this.message,code:this.code,statusCode:this.statusCode,details:this.details,fields:this.fields,context:this.context,isOperational:this.isOperational,timestamp:this.timestamp,stack:this.stack}}};});var te={};Pe(te,{BadRequestError:()=>exports.BadRequestError,ConflictError:()=>exports.ConflictError,ForbiddenError:()=>exports.ForbiddenError,InternalServerError:()=>exports.InternalServerError,NotFoundError:()=>exports.NotFoundError,RateLimitError:()=>exports.RateLimitError,ServiceUnavailableError:()=>exports.ServiceUnavailableError,UnauthorizedError:()=>exports.UnauthorizedError,UnprocessableEntityError:()=>exports.UnprocessableEntityError,ValidationError:()=>exports.ValidationError,createError:()=>$});function $(r,e,t){let o=new r(e);return t?o.withContext(t):o}exports.ValidationError=void 0;exports.NotFoundError=void 0;exports.UnauthorizedError=void 0;exports.ForbiddenError=void 0;exports.ConflictError=void 0;exports.InternalServerError=void 0;exports.BadRequestError=void 0;exports.RateLimitError=void 0;exports.ServiceUnavailableError=void 0;exports.UnprocessableEntityError=void 0;var U=G(()=>{q();exports.ValidationError=class extends exports.ApiError{constructor(e="Validation failed",t,o={}){super(e,{...o,code:"VALIDATION_ERROR",statusCode:400,fields:t});}},exports.NotFoundError=class extends exports.ApiError{constructor(e="Resource not found",t={}){super(e,{...t,code:"NOT_FOUND",statusCode:404});}},exports.UnauthorizedError=class extends exports.ApiError{constructor(e="Authentication required",t={}){super(e,{...t,code:"UNAUTHORIZED",statusCode:401});}},exports.ForbiddenError=class extends exports.ApiError{constructor(e="Access forbidden",t={}){super(e,{...t,code:"FORBIDDEN",statusCode:403});}},exports.ConflictError=class extends exports.ApiError{constructor(e="Resource conflict",t={}){super(e,{...t,code:"CONFLICT",statusCode:409});}},exports.InternalServerError=class extends exports.ApiError{constructor(e="Internal server error",t={}){super(e,{...t,code:"INTERNAL_ERROR",statusCode:500,isOperational:false});}},exports.BadRequestError=class extends exports.ApiError{constructor(e="Bad request",t={}){super(e,{...t,code:"BAD_REQUEST",statusCode:400});}},exports.RateLimitError=class extends exports.ApiError{retryAfter;constructor(e="Rate limit exceeded",t,o={}){super(e,{...o,code:"RATE_LIMIT_EXCEEDED",statusCode:429,details:t?{retryAfter:t}:void 0}),this.retryAfter=t;}},exports.ServiceUnavailableError=class extends exports.ApiError{constructor(e="Service temporarily unavailable",t={}){super(e,{...t,code:"SERVICE_UNAVAILABLE",statusCode:503});}},exports.UnprocessableEntityError=class extends exports.ApiError{constructor(e="Unprocessable entity",t={}){super(e,{...t,code:"UNPROCESSABLE_ENTITY",statusCode:422});}};});var I={includeStackTraces:false,environment:"production",includeTimestamp:true,maskSensitiveData:true,requestIdHeader:"x-request-id",correlationIdHeader:"x-correlation-id",pagination:{defaultLimit:10,maxLimit:100,includeLinks:false},preResponseHooks:[],postResponseHooks:[],passthroughMode:false,sensitiveFields:["password","token","secret","apiKey","authorization"],generateRequestId:true};q();var Q={includeStack:false,maskSensitiveData:true,sensitiveFields:["password","token","secret","apiKey","authorization"]};function V(r,e,t=0){if(t>10)return {_truncated:true};let o=Object.create(null),n=e.map(s=>s.toLowerCase());for(let[s,a]of Object.entries(r))s==="__proto__"||s==="constructor"||s==="prototype"||(n.includes(s.toLowerCase())?o[s]="[REDACTED]":a&&typeof a=="object"&&!Array.isArray(a)?o[s]=V(a,e,t+1):o[s]=a);return o}function W(r,e=new WeakSet,t=0){if(t>10)return {_truncated:true};if(e.has(r))return {_circular:true};e.add(r);let o=Object.create(null);for(let[n,s]of Object.entries(r))n==="__proto__"||n==="constructor"||n==="prototype"||(s==null?o[n]=s:typeof s=="object"?s instanceof Error?o[n]={name:s.name,message:s.message}:Array.isArray(s)?o[n]=s.slice(0,100).map(a=>typeof a=="object"&&a!==null?W(a,e,t+1):a):o[n]=W(s,e,t+1):o[n]=s);return o}function j(r,e={}){let t={...Q,...e};if(t.customSerializers){for(let[n,s]of t.customSerializers)if(r instanceof n){let a=s(r);return {code:a.code||"CUSTOM_ERROR",message:a.message||r.message,details:t.maskSensitiveData?V(a,t.sensitiveFields):a}}}if(r instanceof exports.ApiError){let n=r.serialize(t.includeStack),s=n.details;s&&t.maskSensitiveData&&(s=V(W(s),t.sensitiveFields,0));let a={code:n.code,message:n.message};return s&&Object.keys(s).length>0&&(a.details=s),n.fields&&(a.fields=n.fields),t.includeStack&&n.stack&&(a.stack=n.stack),a}let o={code:"INTERNAL_ERROR",message:r.message||"An unexpected error occurred"};return t.includeStack&&r.stack&&(o.stack=r.stack),o}function Y(r){if(r instanceof exports.ApiError&&r.fields)return r.fields}function J(r){if(r instanceof exports.ApiError&&r.context)return r.context}function ee(r={}){let e={...Q,...r};return t=>j(t,e)}q();U();var ye=new Map([[exports.ValidationError,400],[exports.BadRequestError,400],[exports.UnauthorizedError,401],[exports.ForbiddenError,403],[exports.NotFoundError,404],[exports.ConflictError,409],[exports.UnprocessableEntityError,422],[exports.RateLimitError,429],[exports.InternalServerError,500],[exports.ServiceUnavailableError,503]]),Ie={GET:200,POST:201,PUT:200,PATCH:200,DELETE:204,HEAD:200,OPTIONS:200},P=class{customMappings;constructor(e){this.customMappings=e||new Map;}getStatusCode(e){if(e instanceof exports.ApiError)return e.statusCode;for(let[t,o]of this.customMappings)if(e instanceof t)return o;for(let[t,o]of ye)if(e instanceof t)return o;return 500}getSuccessStatusCode(e,t){let o=e.toUpperCase();return o==="DELETE"&&!t?204:Ie[o]||200}addMapping(e,t){this.customMappings.set(e,t);}removeMapping(e){return this.customMappings.delete(e)}static isSuccessCode(e){return e>=200&&e<300}static isClientError(e){return e>=400&&e<500}static isServerError(e){return e>=500&&e<600}};function re(r){return new P(r)}function K(r,e,t=Number.MAX_SAFE_INTEGER){let o=Number(r);return !Number.isFinite(o)||Number.isNaN(o)?e:Math.min(t,Math.max(0,Math.floor(o)))}function A(r,e){let t=Math.max(1,K(r.page,1,1e6)),o=Math.min(e.maxLimit,Math.max(1,K(r.limit,e.defaultLimit,e.maxLimit))),n=K(r.total,0,Number.MAX_SAFE_INTEGER);return {page:t,limit:o,total:n}}function D(r,e){let{page:t,limit:o,total:n}=r,s=Math.ceil(n/o)||1,a=t<s,i=t>1,p={page:t,limit:o,total:n,totalPages:s,hasNextPage:a,hasPreviousPage:i};return e&&(p.links=X(t,s,o,e)),p}function X(r,e,t,o){let n=new URL(o),s=i=>(n.searchParams.set("page",String(i)),n.searchParams.set("limit",String(t)),n.toString()),a={self:s(r),first:s(1),last:s(e)};return r<e&&(a.next=s(r+1)),r>1&&(a.previous=s(r-1)),a}function N(r,e){let t={limit:r.limit,hasMore:r.hasMore};return r.cursor&&(t.cursor=r.cursor),r.nextCursor&&(t.nextCursor=r.nextCursor),r.previousCursor&&(t.previousCursor=r.previousCursor),e&&(t.links=Z(r,e)),t}function Z(r,e){let t=new URL(e),o=s=>(s?t.searchParams.set("cursor",s):t.searchParams.delete("cursor"),t.searchParams.set("limit",String(r.limit)),t.toString()),n={self:o(r.cursor)};return r.nextCursor&&r.hasMore&&(n.next=o(r.nextCursor)),r.previousCursor&&(n.previous=o(r.previousCursor)),n}function oe(r){let e=[];return r.environment&&!["development","staging","production","test"].includes(r.environment)&&e.push(`Invalid environment: ${r.environment}`),r.pagination&&(r.pagination.defaultLimit!==void 0&&r.pagination.defaultLimit<1&&e.push("pagination.defaultLimit must be at least 1"),r.pagination.maxLimit!==void 0&&r.pagination.maxLimit<1&&e.push("pagination.maxLimit must be at least 1"),r.pagination.defaultLimit!==void 0&&r.pagination.maxLimit!==void 0&&r.pagination.defaultLimit>r.pagination.maxLimit&&e.push("pagination.defaultLimit cannot exceed pagination.maxLimit")),e}function se(r){return typeof r=="object"&&r!==null&&!Array.isArray(r)}function H(){let r=Date.now().toString(36),e;if(typeof globalThis.crypto<"u"&&globalThis.crypto.getRandomValues){let t=new Uint8Array(6);globalThis.crypto.getRandomValues(t),e=Array.from(t,o=>o.toString(16).padStart(2,"0")).join("");}else e=Math.random().toString(36).substring(2,10);return `${r}-${e}`}var x=class{config;statusCodeMapper;constructor(e={}){this.config=this.mergeConfig(e),this.statusCodeMapper=new P(this.config.customErrorMappers);}mergeConfig(e){return {...I,...e,pagination:{...I.pagination,...e.pagination},preResponseHooks:e.preResponseHooks||I.preResponseHooks,postResponseHooks:e.postResponseHooks||I.postResponseHooks,sensitiveFields:e.sensitiveFields||I.sensitiveFields}}getConfig(){return {...this.config}}updateConfig(e){this.config=this.mergeConfig({...this.config,...e}),e.customErrorMappers&&(this.statusCodeMapper=new P(e.customErrorMappers));}getTimestamp(){return this.config.includeTimestamp?new Date().toISOString():""}applyPreHooks(e,t){let o={data:e,meta:t};for(let n of this.config.preResponseHooks){let s=n(o.data,o.meta);o={data:s.data,meta:s.meta};}return o}applyPostHooks(e){let t=e;for(let o of this.config.postResponseHooks)t=o(t);return t}formatSuccess(e,t){let o=this.applyPreHooks(e,t),n={success:true,data:o.data,timestamp:this.getTimestamp()};return o.meta&&Object.keys(o.meta).length>0&&(n.meta=o.meta),this.config.passthroughMode&&this.config.customFormatter?this.config.customFormatter(n):this.applyPostHooks(n)}formatError(e,t){let o=this.config.includeStackTraces||this.config.environment==="development",s={success:false,error:j(e,{includeStack:o,maskSensitiveData:this.config.maskSensitiveData,sensitiveFields:this.config.sensitiveFields,customSerializers:this.config.customSerializers}),timestamp:this.getTimestamp()};return t&&Object.keys(t).length>0&&(s.meta=t),this.config.passthroughMode&&this.config.customFormatter?this.config.customFormatter(s):this.applyPostHooks(s)}formatPaginated(e,t,o,n){let s=this.applyPreHooks(e,o),a=A(t,this.config.pagination),i=D(a,this.config.pagination.includeLinks?n:void 0),p={success:true,data:s.data,pagination:i,timestamp:this.getTimestamp()};return s.meta&&Object.keys(s.meta).length>0&&(p.meta=s.meta),this.applyPostHooks(p)}formatCursorPaginated(e,t,o,n){let s=this.applyPreHooks(e,o),a=N(t,this.config.pagination.includeLinks?n:void 0),i={success:true,data:s.data,pagination:a,timestamp:this.getTimestamp()};return s.meta&&Object.keys(s.meta).length>0&&(i.meta=s.meta),this.applyPostHooks(i)}getErrorStatusCode(e){return this.statusCodeMapper.getStatusCode(e)}getSuccessStatusCode(e,t){return this.statusCodeMapper.getSuccessStatusCode(e,t)}getRequestId(e){return e||(this.config.generateRequestId?H():"")}};function ne(r){return new x(r)}q();var be={logErrors:true},_=class{formatter;options;config;constructor(e,t={}){this.formatter=e,this.options={...be,...t},this.config=e.getConfig();}extractMeta(e){let t={},o=e.get(this.config.requestIdHeader);o&&(t.requestId=o);let n=e.get(this.config.correlationIdHeader);return n&&(t.correlationId=n),t}logError(e,t){if(!this.options.logErrors)return;if(this.options.errorLogger){this.options.errorLogger(e,t);return}let n=(e instanceof exports.ApiError?e.isOperational:false)?"warn":"error";console[n]({message:e.message,name:e.name,stack:e.stack,path:t.path,method:t.method,timestamp:new Date().toISOString()});}isOperationalError(e){return e instanceof exports.ApiError?e.isOperational:false}handle(e,t,o,n){this.logError(e,t);let s=this.options.transformError?this.options.transformError(e):e;if(!this.isOperationalError(s)&&this.options.handleNonOperational){this.options.handleNonOperational(s,t,o);return}let a=this.formatter.getErrorStatusCode(s),i=this.extractMeta(t),p=this.formatter.formatError(s,i);o.status(a).json(p);}middleware(){return (e,t,o,n)=>{this.handle(e,t,o,n);}}};function ae(r,e){return new _(r,e)}function ie(r,e){return new _(r,e).middleware()}var y=class{config;constructor(e={}){this.config={defaultLimit:e.defaultLimit??10,maxLimit:e.maxLimit??100,includeLinks:e.includeLinks??false,baseUrl:e.baseUrl};}safeParseInt(e,t,o){if(typeof e!="string")return t;let n=parseInt(e,10);return !Number.isFinite(n)||Number.isNaN(n)?t:Math.min(o,Math.max(1,n))}extractFromRequest(e){let t=this.safeParseInt(e.query.page,1,1e6),o=Math.min(this.config.maxLimit,this.safeParseInt(e.query.limit,this.config.defaultLimit,this.config.maxLimit));return {page:t,limit:o}}validateCursor(e){if(!(typeof e!="string"||e.length===0)&&!(e.length>512)&&/^[a-zA-Z0-9+/=_-]+$/.test(e))return e}extractCursorFromRequest(e){let t=this.validateCursor(e.query.cursor),o=Math.min(this.config.maxLimit,this.safeParseInt(e.query.limit,this.config.defaultLimit,this.config.maxLimit));return {cursor:t,limit:o}}calculateOffset(e,t){return (e-1)*t}buildMeta(e,t){let o=A(e,this.config);return D(o,this.config.includeLinks?t||this.config.baseUrl:void 0)}buildCursorMeta(e,t){return N(e,this.config.includeLinks?t||this.config.baseUrl:void 0)}fromArray(e,t,o,n){let s=this.buildMeta({page:t,limit:o,total:n});return {data:e,pagination:s}}paginateArray(e,t,o){let n=e.length,s=this.calculateOffset(t,o),a=e.slice(s,s+o);return this.fromArray(a,t,o,n)}hasNextPage(e,t,o){let n=Math.ceil(o/t);return e<n}hasPreviousPage(e){return e>1}getTotalPages(e,t){return Math.ceil(e/t)||1}isValidPage(e,t,o){if(e<1)return false;if(t===0)return e===1;let n=this.getTotalPages(t,o);return e<=n}getConfig(){return {...this.config}}updateConfig(e){this.config={...this.config,...e};}};function pe(r){return new y(r)}q();U();function z(r={}){let{skipPaths:e=[],skipCondition:t,...o}=r,n=new x(o),s=n.getConfig();return (a,i,p)=>{let c=a,u=i;if(e.includes(a.path)||t&&t(a)){p();return}let l=a.get(s.requestIdHeader);!l&&s.generateRequestId&&(l=H()),l&&(c.requestId=l,i.setHeader(s.requestIdHeader,l));let R=a.get(s.correlationIdHeader);R&&(c.correlationId=R,i.setHeader(s.correlationIdHeader,R)),u.formatter=n;let f=d=>{let g={};return l&&(g.requestId=l),R&&(g.correlationId=R),{...g,...d}},m=()=>{let d=a.protocol,g=a.get("host")||"localhost";return `${d}://${g}${a.originalUrl.split("?")[0]}`};u.respond=(d,g,C)=>{let h=n.formatSuccess(d,f(g)),he=C??n.getSuccessStatusCode(a.method,d!==void 0);i.status(he).json(h);},u.respondPaginated=(d,g,C)=>{let h=n.formatPaginated(d,g,f(C),m());i.status(200).json(h);},u.respondCursorPaginated=(d,g,C)=>{let h=n.formatCursorPaginated(d,g,f(C),m());i.status(200).json(h);},u.respondError=(d,g)=>{let C=n.formatError(d,f(g)),h=n.getErrorStatusCode(d);i.status(h).json(C);},p();}}function ue(r,e={}){let{skipPaths:t=[],skipCondition:o}=e,n=r.getConfig();return (s,a,i)=>{let p=s,c=a;if(t.includes(s.path)||o&&o(s)){i();return}let u=s.get(n.requestIdHeader);!u&&n.generateRequestId&&(u=H()),u&&(p.requestId=u,a.setHeader(n.requestIdHeader,u));let l=s.get(n.correlationIdHeader);l&&(p.correlationId=l,a.setHeader(n.correlationIdHeader,l)),c.formatter=r;let R=m=>{let d={};return u&&(d.requestId=u),l&&(d.correlationId=l),{...d,...m}},f=()=>{let m=s.protocol,d=s.get("host")||"localhost";return `${m}://${d}${s.originalUrl.split("?")[0]}`};c.respond=(m,d,g)=>{let C=r.formatSuccess(m,R(d)),h=g??r.getSuccessStatusCode(s.method,m!==void 0);a.status(h).json(C);},c.respondPaginated=(m,d,g)=>{let C=r.formatPaginated(m,d,R(g),f());a.status(200).json(C);},c.respondCursorPaginated=(m,d,g)=>{let C=r.formatCursorPaginated(m,d,R(g),f());a.status(200).json(C);},c.respondError=(m,d)=>{let g=r.formatError(m,R(d)),C=r.getErrorStatusCode(m);a.status(C).json(g);},i();}}function B(r={}){let{logErrors:e=true,errorLogger:t,transformError:o,customHandler:n,...s}=r,a=new x(s),i=a.getConfig();return (p,c,u,l)=>{if(n&&n(p,c,u))return;e&&(t?t(p,c):console.error({message:p.message,name:p.name,stack:p.stack,path:c.path,method:c.method,timestamp:new Date().toISOString()}));let R=o?o(p):p,f=c,m={};if(f.requestId)m.requestId=f.requestId;else {let C=c.get(i.requestIdHeader);C&&(m.requestId=C);}if(f.correlationId)m.correlationId=f.correlationId;else {let C=c.get(i.correlationIdHeader);C&&(m.correlationId=C);}let d=a.formatError(R,m),g=a.getErrorStatusCode(R);u.status(g).json(d);}}function ce(r,e={}){let{logErrors:t=true,errorLogger:o,transformError:n,customHandler:s}=e,a=r.getConfig();return (i,p,c,u)=>{if(s&&s(i,p,c))return;t&&(o?o(i,p):console.error({message:i.message,name:i.name,stack:i.stack,path:p.path,method:p.method,timestamp:new Date().toISOString()}));let l=n?n(i):i,R=p,f={};if(R.requestId)f.requestId=R.requestId;else {let g=p.get(a.requestIdHeader);g&&(f.requestId=g);}if(R.correlationId)f.correlationId=R.correlationId;else {let g=p.get(a.correlationIdHeader);g&&(f.correlationId=g);}let m=r.formatError(l,f),d=r.getErrorStatusCode(l);c.status(d).json(m);}}function de(r){return (e,t,o)=>{Promise.resolve(r(e,t,o)).catch(o);}}function le(r){return async(e,t,o)=>{try{await r(e,t);}catch(n){o(n);}}}function me(r){return (e={})=>(t,o,n)=>{let s=n.value;return n.value=async function(a,i,p){try{let c=await s.call(this,a,i,p);if(i.headersSent)return;if(e.raw){i.json(c);return}let u=a,l={...e.meta};u.requestId&&(l.requestId=u.requestId),u.correlationId&&(l.correlationId=u.correlationId);let R=r.formatSuccess(c,l),f=e.statusCode??r.getSuccessStatusCode(a.method,c!==void 0);i.status(f).json(R);}catch(c){if(p)p(c);else throw c}},n}}function ge(){return (r,e,t)=>{let o=t.value;return t.value=async function(n,s,a){try{await o.call(this,n,s,a);}catch(i){a(i);}},t}}function fe(r,e={}){return (t,o,n)=>{let s=n.value;return n.value=async function(a,i,p){try{let c=await s.call(this,a,i,p);if(i.headersSent)return;if(e.raw){i.json(c);return}let u=a,l={...e.meta};u.requestId&&(l.requestId=u.requestId),u.correlationId&&(l.correlationId=u.correlationId);let R=r.formatSuccess(c,l),f=e.statusCode??r.getSuccessStatusCode(a.method,c!==void 0);i.status(f).json(R);}catch(c){p(c);}},n}}function Re(r,e="body"){return (t,o,n)=>{let s=n.value;return n.value=async function(a,i,p){let c=a[e],u=r(c);if(!u.valid){let{ValidationError:l}=await Promise.resolve().then(()=>(U(),te));p(new l("Validation failed",u.errors));return}await s.call(this,a,i,p);},n}}function Ee(r,e={}){let t=new y({defaultLimit:e.defaultLimit??10,maxLimit:e.maxLimit??100,includeLinks:e.includeLinks??false});return (o,n,s)=>{let a=s.value;return s.value=async function(i,p,c){try{let{page:u,limit:l}=t.extractFromRequest(i);i.pagination={page:u,limit:l,offset:t.calculateOffset(u,l)};let R=await a.call(this,i,p,c);if(p.headersSent)return;let f=i,m={...e.meta};f.requestId&&(m.requestId=f.requestId),f.correlationId&&(m.correlationId=f.correlationId);let d=e.includeLinks?`${i.protocol}://${i.get("host")}${i.originalUrl.split("?")[0]}`:void 0,g=r.formatPaginated(R.data,{page:u,limit:l,total:R.total},Object.keys(m).length>0?m:void 0,d);p.status(200).json(g);}catch(u){c(u);}},s}}function Ce(r,e={}){let t=new y({defaultLimit:e.defaultLimit??10,maxLimit:e.maxLimit??100,includeLinks:e.includeLinks??false});return (o,n,s)=>{let a=s.value;return s.value=async function(i,p,c){try{let{cursor:u,limit:l}=t.extractCursorFromRequest(i);i.pagination={cursor:u,limit:l};let R=await a.call(this,i,p,c);if(p.headersSent)return;let f=i,m={...e.meta};f.requestId&&(m.requestId=f.requestId),f.correlationId&&(m.correlationId=f.correlationId);let d=e.includeLinks?`${i.protocol}://${i.get("host")}${i.originalUrl.split("?")[0]}`:void 0,g=r.formatCursorPaginated(R.data,{limit:l,cursor:u,nextCursor:R.nextCursor,previousCursor:R.previousCursor,hasMore:R.hasMore},Object.keys(m).length>0?m:void 0,d);p.status(200).json(g);}catch(u){c(u);}},s}}function ke(r){return r.success===true}function Me(r){return r.success===false}function Fe(r){return r.success===true&&"pagination"in r}var Se={VALIDATION_ERROR:"VALIDATION_ERROR",NOT_FOUND:"NOT_FOUND",UNAUTHORIZED:"UNAUTHORIZED",FORBIDDEN:"FORBIDDEN",CONFLICT:"CONFLICT",INTERNAL_ERROR:"INTERNAL_ERROR",BAD_REQUEST:"BAD_REQUEST",RATE_LIMIT_EXCEEDED:"RATE_LIMIT_EXCEEDED",SERVICE_UNAVAILABLE:"SERVICE_UNAVAILABLE",UNPROCESSABLE_ENTITY:"UNPROCESSABLE_ENTITY"};function ve(r={}){return {wrapper:z(r),errorHandler:B(r)}}var bt=ve;
|
|
2
|
+
exports.ApiRoute=fe;exports.CursorPaginate=Ce;exports.ErrorCodes=Se;exports.ErrorHandler=_;exports.HandleErrors=ge;exports.Paginate=Ee;exports.PaginationHelper=y;exports.ResponseFormatter=x;exports.StatusCodeMapper=P;exports.Validate=Re;exports.asyncHandler=de;exports.calculateCursorPaginationMeta=N;exports.calculatePaginationMeta=D;exports.catchErrors=le;exports.createError=$;exports.createErrorCatcher=ce;exports.createErrorHandler=ae;exports.createErrorSerializer=ee;exports.createPaginationHelper=pe;exports.createResponseDecorator=me;exports.createResponseFormatter=ne;exports.createResponseWrapper=ue;exports.createStatusCodeMapper=re;exports.default=bt;exports.defaultConfig=I;exports.errorCatcher=B;exports.errorHandlerMiddleware=ie;exports.extractErrorContext=J;exports.extractFieldErrors=Y;exports.generateCursorPaginationLinks=Z;exports.generatePaginationLinks=X;exports.generateRequestId=H;exports.isErrorResponse=Me;exports.isPaginatedResponse=Fe;exports.isPlainObject=se;exports.isSuccessResponse=ke;exports.responseFormatter=ve;exports.responseWrapper=z;exports.serializeError=j;exports.validateConfig=oe;exports.validatePaginationInput=A;
|