@carecard/common-util 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/dist/cjs/appErrorHandlers.cjs +179 -0
- package/dist/cjs/appErrorHandlers.d.ts +128 -0
- package/dist/cjs/index.cjs +19 -0
- package/dist/cjs/index.d.ts +3 -0
- package/dist/cjs/responseStatus.cjs +71 -0
- package/dist/cjs/responseStatus.d.ts +65 -0
- package/dist/cjs/utilityFunctions.cjs +30 -0
- package/dist/cjs/utilityFunctions.d.ts +9 -0
- package/dist/esm/appErrorHandlers.d.ts +128 -0
- package/dist/esm/appErrorHandlers.js +156 -0
- package/dist/esm/index.d.ts +3 -0
- package/dist/esm/index.js +3 -0
- package/dist/esm/responseStatus.d.ts +65 -0
- package/dist/esm/responseStatus.js +66 -0
- package/dist/esm/utilityFunctions.d.ts +9 -0
- package/dist/esm/utilityFunctions.js +27 -0
- package/package.json +55 -0
- package/readme.md +6 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* appErrors.ts */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.throwInvalidTimeValueError = exports.throwFileTooLargeError = exports.throwInputNotUuidError = exports.throwBadInputError = exports.throwNotAuthorizedError = exports.throwFileFormatNotSupportedError = exports.throwBadVisitorTokenError = exports.throwUsedTokenError = exports.throwTransactionFailedError = exports.throwUpdateFailedError = exports.throwLoginRequiredError = exports.throwRecordNotSavedError = exports.throwRecordNotFoundError = exports.throwRecordExistError = exports.throwWrongCredentialsError = exports.throwValidationFailureError = exports.ERROR_TYPES = exports.AppError = void 0;
|
|
5
|
+
exports.throwAppError = throwAppError;
|
|
6
|
+
exports.notFound404 = notFound404;
|
|
7
|
+
exports.appErrorHandler = appErrorHandler;
|
|
8
|
+
/* --------------------------------------------------
|
|
9
|
+
* Base Error Class
|
|
10
|
+
* -------------------------------------------------- */
|
|
11
|
+
class AppError extends Error {
|
|
12
|
+
constructor(message, code, params = {}) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.name = 'AppError';
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.userMessage = params.userMessage ?? null;
|
|
17
|
+
this.details = params.details ?? null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.AppError = AppError;
|
|
21
|
+
/* --------------------------------------------------
|
|
22
|
+
* Error Registry (single source of truth)
|
|
23
|
+
* -------------------------------------------------- */
|
|
24
|
+
exports.ERROR_TYPES = {
|
|
25
|
+
VALIDATION_FAILURE: {
|
|
26
|
+
message: 'Validation_Failure',
|
|
27
|
+
code: 'VALIDATION_FAILURE',
|
|
28
|
+
status: 401,
|
|
29
|
+
},
|
|
30
|
+
WRONG_CREDENTIALS: {
|
|
31
|
+
message: 'Wrong_Credentials',
|
|
32
|
+
code: 'WRONG_CREDENTIALS',
|
|
33
|
+
status: 401,
|
|
34
|
+
},
|
|
35
|
+
USED_TOKEN: {
|
|
36
|
+
message: 'Used_Token',
|
|
37
|
+
code: 'USED_TOKEN',
|
|
38
|
+
status: 401,
|
|
39
|
+
},
|
|
40
|
+
BAD_VISITOR_TOKEN: {
|
|
41
|
+
message: 'Bad_Visitor_Token',
|
|
42
|
+
code: 'BAD_VISITOR_TOKEN',
|
|
43
|
+
status: 401,
|
|
44
|
+
},
|
|
45
|
+
LOGIN_REQUIRED: {
|
|
46
|
+
message: 'Login_Required',
|
|
47
|
+
code: 'LOGIN_REQUIRED',
|
|
48
|
+
status: 401,
|
|
49
|
+
},
|
|
50
|
+
NOT_AUTHORIZED: {
|
|
51
|
+
message: 'Not_Authorized',
|
|
52
|
+
code: 'NOT_AUTHORIZED',
|
|
53
|
+
status: 401,
|
|
54
|
+
},
|
|
55
|
+
BAD_INPUT: {
|
|
56
|
+
message: 'Bad_Input',
|
|
57
|
+
code: 'BAD_INPUT',
|
|
58
|
+
status: 400,
|
|
59
|
+
},
|
|
60
|
+
INPUT_NOT_UUID: {
|
|
61
|
+
message: 'Input_Not_Uuid',
|
|
62
|
+
code: 'INPUT_NOT_UUID',
|
|
63
|
+
status: 400,
|
|
64
|
+
},
|
|
65
|
+
RECORD_NOT_SAVED: {
|
|
66
|
+
message: 'Record_NotSaved',
|
|
67
|
+
code: 'RECORD_NOT_SAVED',
|
|
68
|
+
status: 400,
|
|
69
|
+
},
|
|
70
|
+
UPDATE_FAILED: {
|
|
71
|
+
message: 'Update_Failed',
|
|
72
|
+
code: 'UPDATE_FAILED',
|
|
73
|
+
status: 400,
|
|
74
|
+
},
|
|
75
|
+
TRANSACTION_FAILED: {
|
|
76
|
+
message: 'Transaction_Failed',
|
|
77
|
+
code: 'TRANSACTION_FAILED',
|
|
78
|
+
status: 400,
|
|
79
|
+
},
|
|
80
|
+
RECORD_EXIST: {
|
|
81
|
+
message: 'Record_Exist',
|
|
82
|
+
code: 'RECORD_EXIST',
|
|
83
|
+
status: 409,
|
|
84
|
+
},
|
|
85
|
+
RECORD_NOT_FOUND: {
|
|
86
|
+
message: 'Record_NotFound',
|
|
87
|
+
code: 'RECORD_NOT_FOUND',
|
|
88
|
+
status: 404,
|
|
89
|
+
},
|
|
90
|
+
FILE_FORMAT_NOT_SUPPORTED: {
|
|
91
|
+
message: 'File_Format_Not_Supported',
|
|
92
|
+
code: 'FILE_FORMAT_NOT_SUPPORTED',
|
|
93
|
+
status: 415,
|
|
94
|
+
},
|
|
95
|
+
FILE_TOO_LARGE: {
|
|
96
|
+
message: 'File too large',
|
|
97
|
+
code: 'FILE_TOO_LARGE',
|
|
98
|
+
status: 413,
|
|
99
|
+
},
|
|
100
|
+
INVALID_TIME_VALUE: {
|
|
101
|
+
message: 'Invalid time value',
|
|
102
|
+
code: 'INVALID_TIME_VALUE',
|
|
103
|
+
status: 403,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
/* --------------------------------------------------
|
|
107
|
+
* Generic throw helper
|
|
108
|
+
* -------------------------------------------------- */
|
|
109
|
+
function throwAppError(type, params = {}) {
|
|
110
|
+
const { message, code } = exports.ERROR_TYPES[type];
|
|
111
|
+
throw new AppError(message, code, params);
|
|
112
|
+
}
|
|
113
|
+
/* --------------------------------------------------
|
|
114
|
+
* Backward-compatible helpers (optional)
|
|
115
|
+
* -------------------------------------------------- */
|
|
116
|
+
const throwValidationFailureError = (p = {}) => throwAppError('VALIDATION_FAILURE', p);
|
|
117
|
+
exports.throwValidationFailureError = throwValidationFailureError;
|
|
118
|
+
const throwWrongCredentialsError = (p = {}) => throwAppError('WRONG_CREDENTIALS', p);
|
|
119
|
+
exports.throwWrongCredentialsError = throwWrongCredentialsError;
|
|
120
|
+
const throwRecordExistError = (p = {}) => throwAppError('RECORD_EXIST', p);
|
|
121
|
+
exports.throwRecordExistError = throwRecordExistError;
|
|
122
|
+
const throwRecordNotFoundError = (p = {}) => throwAppError('RECORD_NOT_FOUND', p);
|
|
123
|
+
exports.throwRecordNotFoundError = throwRecordNotFoundError;
|
|
124
|
+
const throwRecordNotSavedError = (p = {}) => throwAppError('RECORD_NOT_SAVED', p);
|
|
125
|
+
exports.throwRecordNotSavedError = throwRecordNotSavedError;
|
|
126
|
+
const throwLoginRequiredError = (p = {}) => throwAppError('LOGIN_REQUIRED', p);
|
|
127
|
+
exports.throwLoginRequiredError = throwLoginRequiredError;
|
|
128
|
+
const throwUpdateFailedError = (p = {}) => throwAppError('UPDATE_FAILED', p);
|
|
129
|
+
exports.throwUpdateFailedError = throwUpdateFailedError;
|
|
130
|
+
const throwTransactionFailedError = (p = {}) => throwAppError('TRANSACTION_FAILED', p);
|
|
131
|
+
exports.throwTransactionFailedError = throwTransactionFailedError;
|
|
132
|
+
const throwUsedTokenError = (p = {}) => throwAppError('USED_TOKEN', p);
|
|
133
|
+
exports.throwUsedTokenError = throwUsedTokenError;
|
|
134
|
+
const throwBadVisitorTokenError = (p = {}) => throwAppError('BAD_VISITOR_TOKEN', p);
|
|
135
|
+
exports.throwBadVisitorTokenError = throwBadVisitorTokenError;
|
|
136
|
+
const throwFileFormatNotSupportedError = (p = {}) => throwAppError('FILE_FORMAT_NOT_SUPPORTED', p);
|
|
137
|
+
exports.throwFileFormatNotSupportedError = throwFileFormatNotSupportedError;
|
|
138
|
+
const throwNotAuthorizedError = (p = {}) => throwAppError('NOT_AUTHORIZED', p);
|
|
139
|
+
exports.throwNotAuthorizedError = throwNotAuthorizedError;
|
|
140
|
+
const throwBadInputError = (p = {}) => throwAppError('BAD_INPUT', p);
|
|
141
|
+
exports.throwBadInputError = throwBadInputError;
|
|
142
|
+
const throwInputNotUuidError = (p = {}) => throwAppError('INPUT_NOT_UUID', p);
|
|
143
|
+
exports.throwInputNotUuidError = throwInputNotUuidError;
|
|
144
|
+
const throwFileTooLargeError = (p = {}) => throwAppError('FILE_TOO_LARGE', p);
|
|
145
|
+
exports.throwFileTooLargeError = throwFileTooLargeError;
|
|
146
|
+
const throwInvalidTimeValueError = (p = {}) => throwAppError('INVALID_TIME_VALUE', p);
|
|
147
|
+
exports.throwInvalidTimeValueError = throwInvalidTimeValueError;
|
|
148
|
+
/* --------------------------------------------------
|
|
149
|
+
* Express middleware
|
|
150
|
+
* -------------------------------------------------- */
|
|
151
|
+
function notFound404(req, res, _next) {
|
|
152
|
+
res.status(404).send({
|
|
153
|
+
error: {
|
|
154
|
+
code: 'NOT_FOUND',
|
|
155
|
+
message: 'Not found',
|
|
156
|
+
details: null,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
function appErrorHandler(err, _req, res, _next) {
|
|
161
|
+
// Iteration without `.find` or `Object.values` for maximum compatibility
|
|
162
|
+
let errorType = undefined;
|
|
163
|
+
const keys = Object.keys(exports.ERROR_TYPES);
|
|
164
|
+
for (const key of keys) {
|
|
165
|
+
const candidate = exports.ERROR_TYPES[key];
|
|
166
|
+
if (candidate.message === err?.message) {
|
|
167
|
+
errorType = candidate;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const status = errorType?.status ?? 500;
|
|
172
|
+
res.status(status).send({
|
|
173
|
+
error: {
|
|
174
|
+
code: err?.code ?? errorType?.code ?? null,
|
|
175
|
+
message: err?.userMessage ?? errorType?.message ?? null,
|
|
176
|
+
details: err?.details ?? null,
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application-level error handling utilities.
|
|
3
|
+
* Use throwXxxError functions in services/controllers.
|
|
4
|
+
* Use appErrorHandler + notFound404 in app.js.
|
|
5
|
+
*/
|
|
6
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
7
|
+
export interface AppErrorParams {
|
|
8
|
+
userMessage?: string | null;
|
|
9
|
+
details?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export type ErrorInfo = {
|
|
12
|
+
message: string;
|
|
13
|
+
code: string;
|
|
14
|
+
status: number;
|
|
15
|
+
};
|
|
16
|
+
export declare class AppError extends Error {
|
|
17
|
+
readonly code: string;
|
|
18
|
+
readonly userMessage: string | null;
|
|
19
|
+
readonly details: unknown;
|
|
20
|
+
constructor(message: string, code: string, params?: AppErrorParams);
|
|
21
|
+
}
|
|
22
|
+
export declare const ERROR_TYPES: {
|
|
23
|
+
readonly VALIDATION_FAILURE: {
|
|
24
|
+
readonly message: "Validation_Failure";
|
|
25
|
+
readonly code: "VALIDATION_FAILURE";
|
|
26
|
+
readonly status: 401;
|
|
27
|
+
};
|
|
28
|
+
readonly WRONG_CREDENTIALS: {
|
|
29
|
+
readonly message: "Wrong_Credentials";
|
|
30
|
+
readonly code: "WRONG_CREDENTIALS";
|
|
31
|
+
readonly status: 401;
|
|
32
|
+
};
|
|
33
|
+
readonly USED_TOKEN: {
|
|
34
|
+
readonly message: "Used_Token";
|
|
35
|
+
readonly code: "USED_TOKEN";
|
|
36
|
+
readonly status: 401;
|
|
37
|
+
};
|
|
38
|
+
readonly BAD_VISITOR_TOKEN: {
|
|
39
|
+
readonly message: "Bad_Visitor_Token";
|
|
40
|
+
readonly code: "BAD_VISITOR_TOKEN";
|
|
41
|
+
readonly status: 401;
|
|
42
|
+
};
|
|
43
|
+
readonly LOGIN_REQUIRED: {
|
|
44
|
+
readonly message: "Login_Required";
|
|
45
|
+
readonly code: "LOGIN_REQUIRED";
|
|
46
|
+
readonly status: 401;
|
|
47
|
+
};
|
|
48
|
+
readonly NOT_AUTHORIZED: {
|
|
49
|
+
readonly message: "Not_Authorized";
|
|
50
|
+
readonly code: "NOT_AUTHORIZED";
|
|
51
|
+
readonly status: 401;
|
|
52
|
+
};
|
|
53
|
+
readonly BAD_INPUT: {
|
|
54
|
+
readonly message: "Bad_Input";
|
|
55
|
+
readonly code: "BAD_INPUT";
|
|
56
|
+
readonly status: 400;
|
|
57
|
+
};
|
|
58
|
+
readonly INPUT_NOT_UUID: {
|
|
59
|
+
readonly message: "Input_Not_Uuid";
|
|
60
|
+
readonly code: "INPUT_NOT_UUID";
|
|
61
|
+
readonly status: 400;
|
|
62
|
+
};
|
|
63
|
+
readonly RECORD_NOT_SAVED: {
|
|
64
|
+
readonly message: "Record_NotSaved";
|
|
65
|
+
readonly code: "RECORD_NOT_SAVED";
|
|
66
|
+
readonly status: 400;
|
|
67
|
+
};
|
|
68
|
+
readonly UPDATE_FAILED: {
|
|
69
|
+
readonly message: "Update_Failed";
|
|
70
|
+
readonly code: "UPDATE_FAILED";
|
|
71
|
+
readonly status: 400;
|
|
72
|
+
};
|
|
73
|
+
readonly TRANSACTION_FAILED: {
|
|
74
|
+
readonly message: "Transaction_Failed";
|
|
75
|
+
readonly code: "TRANSACTION_FAILED";
|
|
76
|
+
readonly status: 400;
|
|
77
|
+
};
|
|
78
|
+
readonly RECORD_EXIST: {
|
|
79
|
+
readonly message: "Record_Exist";
|
|
80
|
+
readonly code: "RECORD_EXIST";
|
|
81
|
+
readonly status: 409;
|
|
82
|
+
};
|
|
83
|
+
readonly RECORD_NOT_FOUND: {
|
|
84
|
+
readonly message: "Record_NotFound";
|
|
85
|
+
readonly code: "RECORD_NOT_FOUND";
|
|
86
|
+
readonly status: 404;
|
|
87
|
+
};
|
|
88
|
+
readonly FILE_FORMAT_NOT_SUPPORTED: {
|
|
89
|
+
readonly message: "File_Format_Not_Supported";
|
|
90
|
+
readonly code: "FILE_FORMAT_NOT_SUPPORTED";
|
|
91
|
+
readonly status: 415;
|
|
92
|
+
};
|
|
93
|
+
readonly FILE_TOO_LARGE: {
|
|
94
|
+
readonly message: "File too large";
|
|
95
|
+
readonly code: "FILE_TOO_LARGE";
|
|
96
|
+
readonly status: 413;
|
|
97
|
+
};
|
|
98
|
+
readonly INVALID_TIME_VALUE: {
|
|
99
|
+
readonly message: "Invalid time value";
|
|
100
|
+
readonly code: "INVALID_TIME_VALUE";
|
|
101
|
+
readonly status: 403;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
export type ErrorTypeKey = keyof typeof ERROR_TYPES;
|
|
105
|
+
export declare function throwAppError(type: ErrorTypeKey, params?: AppErrorParams): never;
|
|
106
|
+
export declare const throwValidationFailureError: (p?: AppErrorParams) => never;
|
|
107
|
+
export declare const throwWrongCredentialsError: (p?: AppErrorParams) => never;
|
|
108
|
+
export declare const throwRecordExistError: (p?: AppErrorParams) => never;
|
|
109
|
+
export declare const throwRecordNotFoundError: (p?: AppErrorParams) => never;
|
|
110
|
+
export declare const throwRecordNotSavedError: (p?: AppErrorParams) => never;
|
|
111
|
+
export declare const throwLoginRequiredError: (p?: AppErrorParams) => never;
|
|
112
|
+
export declare const throwUpdateFailedError: (p?: AppErrorParams) => never;
|
|
113
|
+
export declare const throwTransactionFailedError: (p?: AppErrorParams) => never;
|
|
114
|
+
export declare const throwUsedTokenError: (p?: AppErrorParams) => never;
|
|
115
|
+
export declare const throwBadVisitorTokenError: (p?: AppErrorParams) => never;
|
|
116
|
+
export declare const throwFileFormatNotSupportedError: (p?: AppErrorParams) => never;
|
|
117
|
+
export declare const throwNotAuthorizedError: (p?: AppErrorParams) => never;
|
|
118
|
+
export declare const throwBadInputError: (p?: AppErrorParams) => never;
|
|
119
|
+
export declare const throwInputNotUuidError: (p?: AppErrorParams) => never;
|
|
120
|
+
export declare const throwFileTooLargeError: (p?: AppErrorParams) => never;
|
|
121
|
+
export declare const throwInvalidTimeValueError: (p?: AppErrorParams) => never;
|
|
122
|
+
export declare function notFound404(req: Request, res: Response, _next: NextFunction): void;
|
|
123
|
+
export declare function appErrorHandler(err: {
|
|
124
|
+
message?: string;
|
|
125
|
+
code?: string | number | null;
|
|
126
|
+
userMessage?: string | null;
|
|
127
|
+
details?: unknown;
|
|
128
|
+
}, _req: Request, res: Response, _next: NextFunction): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./appErrorHandlers"), exports);
|
|
18
|
+
__exportStar(require("./responseStatus"), exports);
|
|
19
|
+
__exportStar(require("./utilityFunctions"), exports);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.setOk200 = setOk200;
|
|
4
|
+
exports.setCreated201 = setCreated201;
|
|
5
|
+
exports.setBadRequest400ClientError = setBadRequest400ClientError;
|
|
6
|
+
/**
|
|
7
|
+
* The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.
|
|
8
|
+
* It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full
|
|
9
|
+
* response if the content was not changed. Additionally, etags help to prevent simultaneous updates of
|
|
10
|
+
* a resource from overwriting each other ("mid-air collisions").
|
|
11
|
+
*
|
|
12
|
+
* If the resource at a given URL changes, a new Etag value must be generated. A comparison of them can
|
|
13
|
+
* determine whether two representations of a resource are the same. Etags are therefore similar to fingerprints, a
|
|
14
|
+
* nd might also be used for tracking purposes by some servers. They might also be set to persist indefinitely
|
|
15
|
+
* by a tracking server.
|
|
16
|
+
*
|
|
17
|
+
* For example, when editing a wiki, the current wiki content may be hashed and put into an Etag header
|
|
18
|
+
* in the response:
|
|
19
|
+
*
|
|
20
|
+
* ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
21
|
+
*
|
|
22
|
+
* When saving changes to a wiki page (posting data), the POST request will contain the If-Match header containing
|
|
23
|
+
* the ETag values to check freshness against.
|
|
24
|
+
*
|
|
25
|
+
* If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
26
|
+
*
|
|
27
|
+
* If the hashes don't match, it means that the document has been edited in-between and a 412 Precondition Failed
|
|
28
|
+
* error is thrown.
|
|
29
|
+
*
|
|
30
|
+
* @param res
|
|
31
|
+
* @param ETag
|
|
32
|
+
* @returns {*}
|
|
33
|
+
*/
|
|
34
|
+
function setOk200(res, ETag) {
|
|
35
|
+
res.set({ ETag: ETag });
|
|
36
|
+
res.status(200);
|
|
37
|
+
return res;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The 201 (Created) status code indicates that the request has been
|
|
41
|
+
* fulfilled and has resulted in one or more new resources being
|
|
42
|
+
* created. The primary resource created by the request is identified
|
|
43
|
+
* by either a Location header field in the response or, if no Location
|
|
44
|
+
* field is received, by the effective request URI.
|
|
45
|
+
*
|
|
46
|
+
* The 201 response payload typically describes and links to the
|
|
47
|
+
* resource(s) created. See
|
|
48
|
+
* https://datatracker.ietf.org/doc/html/rfc7231#section-7.2
|
|
49
|
+
* for a discussion of the meaning and purpose of validator header
|
|
50
|
+
* fields, such as ETag and Last-Modified, in a 201 response.
|
|
51
|
+
*
|
|
52
|
+
* @param res
|
|
53
|
+
* @returns {*}
|
|
54
|
+
*/
|
|
55
|
+
function setCreated201(res) {
|
|
56
|
+
res.status(201);
|
|
57
|
+
return res;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The 400 (Bad Request) status code indicates that the server cannot or
|
|
61
|
+
* will not process the request due to something that is perceived to be
|
|
62
|
+
* a client error (e.g., malformed request syntax, invalid request
|
|
63
|
+
* message framing, or deceptive request routing).
|
|
64
|
+
*
|
|
65
|
+
* @param res
|
|
66
|
+
* @returns {*}
|
|
67
|
+
*/
|
|
68
|
+
function setBadRequest400ClientError(res) {
|
|
69
|
+
res.status(400);
|
|
70
|
+
return res;
|
|
71
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.
|
|
3
|
+
* It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full
|
|
4
|
+
* response if the content was not changed. Additionally, etags help to prevent simultaneous updates of
|
|
5
|
+
* a resource from overwriting each other ("mid-air collisions").
|
|
6
|
+
*
|
|
7
|
+
* If the resource at a given URL changes, a new Etag value must be generated. A comparison of them can
|
|
8
|
+
* determine whether two representations of a resource are the same. Etags are therefore similar to fingerprints, a
|
|
9
|
+
* nd might also be used for tracking purposes by some servers. They might also be set to persist indefinitely
|
|
10
|
+
* by a tracking server.
|
|
11
|
+
*
|
|
12
|
+
* For example, when editing a wiki, the current wiki content may be hashed and put into an Etag header
|
|
13
|
+
* in the response:
|
|
14
|
+
*
|
|
15
|
+
* ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
16
|
+
*
|
|
17
|
+
* When saving changes to a wiki page (posting data), the POST request will contain the If-Match header containing
|
|
18
|
+
* the ETag values to check freshness against.
|
|
19
|
+
*
|
|
20
|
+
* If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
21
|
+
*
|
|
22
|
+
* If the hashes don't match, it means that the document has been edited in-between and a 412 Precondition Failed
|
|
23
|
+
* error is thrown.
|
|
24
|
+
*
|
|
25
|
+
* @param res
|
|
26
|
+
* @param ETag
|
|
27
|
+
* @returns {*}
|
|
28
|
+
*/
|
|
29
|
+
export declare function setOk200(res: {
|
|
30
|
+
set: (arg0: {
|
|
31
|
+
ETag: any;
|
|
32
|
+
}) => void;
|
|
33
|
+
status: (arg0: number) => void;
|
|
34
|
+
}, ETag: any): any;
|
|
35
|
+
/**
|
|
36
|
+
* The 201 (Created) status code indicates that the request has been
|
|
37
|
+
* fulfilled and has resulted in one or more new resources being
|
|
38
|
+
* created. The primary resource created by the request is identified
|
|
39
|
+
* by either a Location header field in the response or, if no Location
|
|
40
|
+
* field is received, by the effective request URI.
|
|
41
|
+
*
|
|
42
|
+
* The 201 response payload typically describes and links to the
|
|
43
|
+
* resource(s) created. See
|
|
44
|
+
* https://datatracker.ietf.org/doc/html/rfc7231#section-7.2
|
|
45
|
+
* for a discussion of the meaning and purpose of validator header
|
|
46
|
+
* fields, such as ETag and Last-Modified, in a 201 response.
|
|
47
|
+
*
|
|
48
|
+
* @param res
|
|
49
|
+
* @returns {*}
|
|
50
|
+
*/
|
|
51
|
+
export declare function setCreated201(res: {
|
|
52
|
+
status: (arg0: number) => void;
|
|
53
|
+
}): any;
|
|
54
|
+
/**
|
|
55
|
+
* The 400 (Bad Request) status code indicates that the server cannot or
|
|
56
|
+
* will not process the request due to something that is perceived to be
|
|
57
|
+
* a client error (e.g., malformed request syntax, invalid request
|
|
58
|
+
* message framing, or deceptive request routing).
|
|
59
|
+
*
|
|
60
|
+
* @param res
|
|
61
|
+
* @returns {*}
|
|
62
|
+
*/
|
|
63
|
+
export declare function setBadRequest400ClientError(res: {
|
|
64
|
+
status: (arg0: number) => void;
|
|
65
|
+
}): any;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/utilityFunctions.ts
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.extractObjectWithProperties = extractObjectWithProperties;
|
|
5
|
+
/**
|
|
6
|
+
* Create a new object containing only the properties listed in `arrayOfProperties`.
|
|
7
|
+
* Keeps the original behavior: only copies properties whose values are truthy.
|
|
8
|
+
*
|
|
9
|
+
* Example:
|
|
10
|
+
* extractObjectWithProperties({ a: 1, b: 0, c: 'x' }, ['a', 'b', 'c'])
|
|
11
|
+
* -> { a: 1, c: 'x' } // 'b' is omitted because 0 is falsy
|
|
12
|
+
*/
|
|
13
|
+
function extractObjectWithProperties(obj, arrayOfProperties) {
|
|
14
|
+
// Initialize with the correct type to avoid "element implicitly has an 'any' type"
|
|
15
|
+
const returnObj = {};
|
|
16
|
+
// If obj is null/undefined, return an empty object
|
|
17
|
+
if (!obj) {
|
|
18
|
+
return returnObj;
|
|
19
|
+
}
|
|
20
|
+
// Iterate using `for...of` to avoid lib/target issues with Array.prototype.find for older configs
|
|
21
|
+
for (const nameOfProperty of arrayOfProperties) {
|
|
22
|
+
const value = obj[nameOfProperty];
|
|
23
|
+
// Preserve original "truthy" filter: only copy if value is truthy
|
|
24
|
+
if (value) {
|
|
25
|
+
// Assign with proper typing
|
|
26
|
+
returnObj[nameOfProperty] = value;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return returnObj;
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new object containing only the properties listed in `arrayOfProperties`.
|
|
3
|
+
* Keeps the original behavior: only copies properties whose values are truthy.
|
|
4
|
+
*
|
|
5
|
+
* Example:
|
|
6
|
+
* extractObjectWithProperties({ a: 1, b: 0, c: 'x' }, ['a', 'b', 'c'])
|
|
7
|
+
* -> { a: 1, c: 'x' } // 'b' is omitted because 0 is falsy
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractObjectWithProperties<T extends Record<string, unknown>, K extends keyof T>(obj: T | null | undefined, arrayOfProperties: ReadonlyArray<K>): Partial<Pick<T, K>>;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application-level error handling utilities.
|
|
3
|
+
* Use throwXxxError functions in services/controllers.
|
|
4
|
+
* Use appErrorHandler + notFound404 in app.js.
|
|
5
|
+
*/
|
|
6
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
7
|
+
export interface AppErrorParams {
|
|
8
|
+
userMessage?: string | null;
|
|
9
|
+
details?: unknown;
|
|
10
|
+
}
|
|
11
|
+
export type ErrorInfo = {
|
|
12
|
+
message: string;
|
|
13
|
+
code: string;
|
|
14
|
+
status: number;
|
|
15
|
+
};
|
|
16
|
+
export declare class AppError extends Error {
|
|
17
|
+
readonly code: string;
|
|
18
|
+
readonly userMessage: string | null;
|
|
19
|
+
readonly details: unknown;
|
|
20
|
+
constructor(message: string, code: string, params?: AppErrorParams);
|
|
21
|
+
}
|
|
22
|
+
export declare const ERROR_TYPES: {
|
|
23
|
+
readonly VALIDATION_FAILURE: {
|
|
24
|
+
readonly message: "Validation_Failure";
|
|
25
|
+
readonly code: "VALIDATION_FAILURE";
|
|
26
|
+
readonly status: 401;
|
|
27
|
+
};
|
|
28
|
+
readonly WRONG_CREDENTIALS: {
|
|
29
|
+
readonly message: "Wrong_Credentials";
|
|
30
|
+
readonly code: "WRONG_CREDENTIALS";
|
|
31
|
+
readonly status: 401;
|
|
32
|
+
};
|
|
33
|
+
readonly USED_TOKEN: {
|
|
34
|
+
readonly message: "Used_Token";
|
|
35
|
+
readonly code: "USED_TOKEN";
|
|
36
|
+
readonly status: 401;
|
|
37
|
+
};
|
|
38
|
+
readonly BAD_VISITOR_TOKEN: {
|
|
39
|
+
readonly message: "Bad_Visitor_Token";
|
|
40
|
+
readonly code: "BAD_VISITOR_TOKEN";
|
|
41
|
+
readonly status: 401;
|
|
42
|
+
};
|
|
43
|
+
readonly LOGIN_REQUIRED: {
|
|
44
|
+
readonly message: "Login_Required";
|
|
45
|
+
readonly code: "LOGIN_REQUIRED";
|
|
46
|
+
readonly status: 401;
|
|
47
|
+
};
|
|
48
|
+
readonly NOT_AUTHORIZED: {
|
|
49
|
+
readonly message: "Not_Authorized";
|
|
50
|
+
readonly code: "NOT_AUTHORIZED";
|
|
51
|
+
readonly status: 401;
|
|
52
|
+
};
|
|
53
|
+
readonly BAD_INPUT: {
|
|
54
|
+
readonly message: "Bad_Input";
|
|
55
|
+
readonly code: "BAD_INPUT";
|
|
56
|
+
readonly status: 400;
|
|
57
|
+
};
|
|
58
|
+
readonly INPUT_NOT_UUID: {
|
|
59
|
+
readonly message: "Input_Not_Uuid";
|
|
60
|
+
readonly code: "INPUT_NOT_UUID";
|
|
61
|
+
readonly status: 400;
|
|
62
|
+
};
|
|
63
|
+
readonly RECORD_NOT_SAVED: {
|
|
64
|
+
readonly message: "Record_NotSaved";
|
|
65
|
+
readonly code: "RECORD_NOT_SAVED";
|
|
66
|
+
readonly status: 400;
|
|
67
|
+
};
|
|
68
|
+
readonly UPDATE_FAILED: {
|
|
69
|
+
readonly message: "Update_Failed";
|
|
70
|
+
readonly code: "UPDATE_FAILED";
|
|
71
|
+
readonly status: 400;
|
|
72
|
+
};
|
|
73
|
+
readonly TRANSACTION_FAILED: {
|
|
74
|
+
readonly message: "Transaction_Failed";
|
|
75
|
+
readonly code: "TRANSACTION_FAILED";
|
|
76
|
+
readonly status: 400;
|
|
77
|
+
};
|
|
78
|
+
readonly RECORD_EXIST: {
|
|
79
|
+
readonly message: "Record_Exist";
|
|
80
|
+
readonly code: "RECORD_EXIST";
|
|
81
|
+
readonly status: 409;
|
|
82
|
+
};
|
|
83
|
+
readonly RECORD_NOT_FOUND: {
|
|
84
|
+
readonly message: "Record_NotFound";
|
|
85
|
+
readonly code: "RECORD_NOT_FOUND";
|
|
86
|
+
readonly status: 404;
|
|
87
|
+
};
|
|
88
|
+
readonly FILE_FORMAT_NOT_SUPPORTED: {
|
|
89
|
+
readonly message: "File_Format_Not_Supported";
|
|
90
|
+
readonly code: "FILE_FORMAT_NOT_SUPPORTED";
|
|
91
|
+
readonly status: 415;
|
|
92
|
+
};
|
|
93
|
+
readonly FILE_TOO_LARGE: {
|
|
94
|
+
readonly message: "File too large";
|
|
95
|
+
readonly code: "FILE_TOO_LARGE";
|
|
96
|
+
readonly status: 413;
|
|
97
|
+
};
|
|
98
|
+
readonly INVALID_TIME_VALUE: {
|
|
99
|
+
readonly message: "Invalid time value";
|
|
100
|
+
readonly code: "INVALID_TIME_VALUE";
|
|
101
|
+
readonly status: 403;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
export type ErrorTypeKey = keyof typeof ERROR_TYPES;
|
|
105
|
+
export declare function throwAppError(type: ErrorTypeKey, params?: AppErrorParams): never;
|
|
106
|
+
export declare const throwValidationFailureError: (p?: AppErrorParams) => never;
|
|
107
|
+
export declare const throwWrongCredentialsError: (p?: AppErrorParams) => never;
|
|
108
|
+
export declare const throwRecordExistError: (p?: AppErrorParams) => never;
|
|
109
|
+
export declare const throwRecordNotFoundError: (p?: AppErrorParams) => never;
|
|
110
|
+
export declare const throwRecordNotSavedError: (p?: AppErrorParams) => never;
|
|
111
|
+
export declare const throwLoginRequiredError: (p?: AppErrorParams) => never;
|
|
112
|
+
export declare const throwUpdateFailedError: (p?: AppErrorParams) => never;
|
|
113
|
+
export declare const throwTransactionFailedError: (p?: AppErrorParams) => never;
|
|
114
|
+
export declare const throwUsedTokenError: (p?: AppErrorParams) => never;
|
|
115
|
+
export declare const throwBadVisitorTokenError: (p?: AppErrorParams) => never;
|
|
116
|
+
export declare const throwFileFormatNotSupportedError: (p?: AppErrorParams) => never;
|
|
117
|
+
export declare const throwNotAuthorizedError: (p?: AppErrorParams) => never;
|
|
118
|
+
export declare const throwBadInputError: (p?: AppErrorParams) => never;
|
|
119
|
+
export declare const throwInputNotUuidError: (p?: AppErrorParams) => never;
|
|
120
|
+
export declare const throwFileTooLargeError: (p?: AppErrorParams) => never;
|
|
121
|
+
export declare const throwInvalidTimeValueError: (p?: AppErrorParams) => never;
|
|
122
|
+
export declare function notFound404(req: Request, res: Response, _next: NextFunction): void;
|
|
123
|
+
export declare function appErrorHandler(err: {
|
|
124
|
+
message?: string;
|
|
125
|
+
code?: string | number | null;
|
|
126
|
+
userMessage?: string | null;
|
|
127
|
+
details?: unknown;
|
|
128
|
+
}, _req: Request, res: Response, _next: NextFunction): void;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/* appErrors.ts */
|
|
2
|
+
/* --------------------------------------------------
|
|
3
|
+
* Base Error Class
|
|
4
|
+
* -------------------------------------------------- */
|
|
5
|
+
export class AppError extends Error {
|
|
6
|
+
constructor(message, code, params = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'AppError';
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.userMessage = params.userMessage ?? null;
|
|
11
|
+
this.details = params.details ?? null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/* --------------------------------------------------
|
|
15
|
+
* Error Registry (single source of truth)
|
|
16
|
+
* -------------------------------------------------- */
|
|
17
|
+
export const ERROR_TYPES = {
|
|
18
|
+
VALIDATION_FAILURE: {
|
|
19
|
+
message: 'Validation_Failure',
|
|
20
|
+
code: 'VALIDATION_FAILURE',
|
|
21
|
+
status: 401,
|
|
22
|
+
},
|
|
23
|
+
WRONG_CREDENTIALS: {
|
|
24
|
+
message: 'Wrong_Credentials',
|
|
25
|
+
code: 'WRONG_CREDENTIALS',
|
|
26
|
+
status: 401,
|
|
27
|
+
},
|
|
28
|
+
USED_TOKEN: {
|
|
29
|
+
message: 'Used_Token',
|
|
30
|
+
code: 'USED_TOKEN',
|
|
31
|
+
status: 401,
|
|
32
|
+
},
|
|
33
|
+
BAD_VISITOR_TOKEN: {
|
|
34
|
+
message: 'Bad_Visitor_Token',
|
|
35
|
+
code: 'BAD_VISITOR_TOKEN',
|
|
36
|
+
status: 401,
|
|
37
|
+
},
|
|
38
|
+
LOGIN_REQUIRED: {
|
|
39
|
+
message: 'Login_Required',
|
|
40
|
+
code: 'LOGIN_REQUIRED',
|
|
41
|
+
status: 401,
|
|
42
|
+
},
|
|
43
|
+
NOT_AUTHORIZED: {
|
|
44
|
+
message: 'Not_Authorized',
|
|
45
|
+
code: 'NOT_AUTHORIZED',
|
|
46
|
+
status: 401,
|
|
47
|
+
},
|
|
48
|
+
BAD_INPUT: {
|
|
49
|
+
message: 'Bad_Input',
|
|
50
|
+
code: 'BAD_INPUT',
|
|
51
|
+
status: 400,
|
|
52
|
+
},
|
|
53
|
+
INPUT_NOT_UUID: {
|
|
54
|
+
message: 'Input_Not_Uuid',
|
|
55
|
+
code: 'INPUT_NOT_UUID',
|
|
56
|
+
status: 400,
|
|
57
|
+
},
|
|
58
|
+
RECORD_NOT_SAVED: {
|
|
59
|
+
message: 'Record_NotSaved',
|
|
60
|
+
code: 'RECORD_NOT_SAVED',
|
|
61
|
+
status: 400,
|
|
62
|
+
},
|
|
63
|
+
UPDATE_FAILED: {
|
|
64
|
+
message: 'Update_Failed',
|
|
65
|
+
code: 'UPDATE_FAILED',
|
|
66
|
+
status: 400,
|
|
67
|
+
},
|
|
68
|
+
TRANSACTION_FAILED: {
|
|
69
|
+
message: 'Transaction_Failed',
|
|
70
|
+
code: 'TRANSACTION_FAILED',
|
|
71
|
+
status: 400,
|
|
72
|
+
},
|
|
73
|
+
RECORD_EXIST: {
|
|
74
|
+
message: 'Record_Exist',
|
|
75
|
+
code: 'RECORD_EXIST',
|
|
76
|
+
status: 409,
|
|
77
|
+
},
|
|
78
|
+
RECORD_NOT_FOUND: {
|
|
79
|
+
message: 'Record_NotFound',
|
|
80
|
+
code: 'RECORD_NOT_FOUND',
|
|
81
|
+
status: 404,
|
|
82
|
+
},
|
|
83
|
+
FILE_FORMAT_NOT_SUPPORTED: {
|
|
84
|
+
message: 'File_Format_Not_Supported',
|
|
85
|
+
code: 'FILE_FORMAT_NOT_SUPPORTED',
|
|
86
|
+
status: 415,
|
|
87
|
+
},
|
|
88
|
+
FILE_TOO_LARGE: {
|
|
89
|
+
message: 'File too large',
|
|
90
|
+
code: 'FILE_TOO_LARGE',
|
|
91
|
+
status: 413,
|
|
92
|
+
},
|
|
93
|
+
INVALID_TIME_VALUE: {
|
|
94
|
+
message: 'Invalid time value',
|
|
95
|
+
code: 'INVALID_TIME_VALUE',
|
|
96
|
+
status: 403,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
/* --------------------------------------------------
|
|
100
|
+
* Generic throw helper
|
|
101
|
+
* -------------------------------------------------- */
|
|
102
|
+
export function throwAppError(type, params = {}) {
|
|
103
|
+
const { message, code } = ERROR_TYPES[type];
|
|
104
|
+
throw new AppError(message, code, params);
|
|
105
|
+
}
|
|
106
|
+
/* --------------------------------------------------
|
|
107
|
+
* Backward-compatible helpers (optional)
|
|
108
|
+
* -------------------------------------------------- */
|
|
109
|
+
export const throwValidationFailureError = (p = {}) => throwAppError('VALIDATION_FAILURE', p);
|
|
110
|
+
export const throwWrongCredentialsError = (p = {}) => throwAppError('WRONG_CREDENTIALS', p);
|
|
111
|
+
export const throwRecordExistError = (p = {}) => throwAppError('RECORD_EXIST', p);
|
|
112
|
+
export const throwRecordNotFoundError = (p = {}) => throwAppError('RECORD_NOT_FOUND', p);
|
|
113
|
+
export const throwRecordNotSavedError = (p = {}) => throwAppError('RECORD_NOT_SAVED', p);
|
|
114
|
+
export const throwLoginRequiredError = (p = {}) => throwAppError('LOGIN_REQUIRED', p);
|
|
115
|
+
export const throwUpdateFailedError = (p = {}) => throwAppError('UPDATE_FAILED', p);
|
|
116
|
+
export const throwTransactionFailedError = (p = {}) => throwAppError('TRANSACTION_FAILED', p);
|
|
117
|
+
export const throwUsedTokenError = (p = {}) => throwAppError('USED_TOKEN', p);
|
|
118
|
+
export const throwBadVisitorTokenError = (p = {}) => throwAppError('BAD_VISITOR_TOKEN', p);
|
|
119
|
+
export const throwFileFormatNotSupportedError = (p = {}) => throwAppError('FILE_FORMAT_NOT_SUPPORTED', p);
|
|
120
|
+
export const throwNotAuthorizedError = (p = {}) => throwAppError('NOT_AUTHORIZED', p);
|
|
121
|
+
export const throwBadInputError = (p = {}) => throwAppError('BAD_INPUT', p);
|
|
122
|
+
export const throwInputNotUuidError = (p = {}) => throwAppError('INPUT_NOT_UUID', p);
|
|
123
|
+
export const throwFileTooLargeError = (p = {}) => throwAppError('FILE_TOO_LARGE', p);
|
|
124
|
+
export const throwInvalidTimeValueError = (p = {}) => throwAppError('INVALID_TIME_VALUE', p);
|
|
125
|
+
/* --------------------------------------------------
|
|
126
|
+
* Express middleware
|
|
127
|
+
* -------------------------------------------------- */
|
|
128
|
+
export function notFound404(req, res, _next) {
|
|
129
|
+
res.status(404).send({
|
|
130
|
+
error: {
|
|
131
|
+
code: 'NOT_FOUND',
|
|
132
|
+
message: 'Not found',
|
|
133
|
+
details: null,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export function appErrorHandler(err, _req, res, _next) {
|
|
138
|
+
// Iteration without `.find` or `Object.values` for maximum compatibility
|
|
139
|
+
let errorType = undefined;
|
|
140
|
+
const keys = Object.keys(ERROR_TYPES);
|
|
141
|
+
for (const key of keys) {
|
|
142
|
+
const candidate = ERROR_TYPES[key];
|
|
143
|
+
if (candidate.message === err?.message) {
|
|
144
|
+
errorType = candidate;
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const status = errorType?.status ?? 500;
|
|
149
|
+
res.status(status).send({
|
|
150
|
+
error: {
|
|
151
|
+
code: err?.code ?? errorType?.code ?? null,
|
|
152
|
+
message: err?.userMessage ?? errorType?.message ?? null,
|
|
153
|
+
details: err?.details ?? null,
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.
|
|
3
|
+
* It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full
|
|
4
|
+
* response if the content was not changed. Additionally, etags help to prevent simultaneous updates of
|
|
5
|
+
* a resource from overwriting each other ("mid-air collisions").
|
|
6
|
+
*
|
|
7
|
+
* If the resource at a given URL changes, a new Etag value must be generated. A comparison of them can
|
|
8
|
+
* determine whether two representations of a resource are the same. Etags are therefore similar to fingerprints, a
|
|
9
|
+
* nd might also be used for tracking purposes by some servers. They might also be set to persist indefinitely
|
|
10
|
+
* by a tracking server.
|
|
11
|
+
*
|
|
12
|
+
* For example, when editing a wiki, the current wiki content may be hashed and put into an Etag header
|
|
13
|
+
* in the response:
|
|
14
|
+
*
|
|
15
|
+
* ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
16
|
+
*
|
|
17
|
+
* When saving changes to a wiki page (posting data), the POST request will contain the If-Match header containing
|
|
18
|
+
* the ETag values to check freshness against.
|
|
19
|
+
*
|
|
20
|
+
* If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
21
|
+
*
|
|
22
|
+
* If the hashes don't match, it means that the document has been edited in-between and a 412 Precondition Failed
|
|
23
|
+
* error is thrown.
|
|
24
|
+
*
|
|
25
|
+
* @param res
|
|
26
|
+
* @param ETag
|
|
27
|
+
* @returns {*}
|
|
28
|
+
*/
|
|
29
|
+
export declare function setOk200(res: {
|
|
30
|
+
set: (arg0: {
|
|
31
|
+
ETag: any;
|
|
32
|
+
}) => void;
|
|
33
|
+
status: (arg0: number) => void;
|
|
34
|
+
}, ETag: any): any;
|
|
35
|
+
/**
|
|
36
|
+
* The 201 (Created) status code indicates that the request has been
|
|
37
|
+
* fulfilled and has resulted in one or more new resources being
|
|
38
|
+
* created. The primary resource created by the request is identified
|
|
39
|
+
* by either a Location header field in the response or, if no Location
|
|
40
|
+
* field is received, by the effective request URI.
|
|
41
|
+
*
|
|
42
|
+
* The 201 response payload typically describes and links to the
|
|
43
|
+
* resource(s) created. See
|
|
44
|
+
* https://datatracker.ietf.org/doc/html/rfc7231#section-7.2
|
|
45
|
+
* for a discussion of the meaning and purpose of validator header
|
|
46
|
+
* fields, such as ETag and Last-Modified, in a 201 response.
|
|
47
|
+
*
|
|
48
|
+
* @param res
|
|
49
|
+
* @returns {*}
|
|
50
|
+
*/
|
|
51
|
+
export declare function setCreated201(res: {
|
|
52
|
+
status: (arg0: number) => void;
|
|
53
|
+
}): any;
|
|
54
|
+
/**
|
|
55
|
+
* The 400 (Bad Request) status code indicates that the server cannot or
|
|
56
|
+
* will not process the request due to something that is perceived to be
|
|
57
|
+
* a client error (e.g., malformed request syntax, invalid request
|
|
58
|
+
* message framing, or deceptive request routing).
|
|
59
|
+
*
|
|
60
|
+
* @param res
|
|
61
|
+
* @returns {*}
|
|
62
|
+
*/
|
|
63
|
+
export declare function setBadRequest400ClientError(res: {
|
|
64
|
+
status: (arg0: number) => void;
|
|
65
|
+
}): any;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The ETag (or entity tag) HTTP response header is an identifier for a specific version of a resource.
|
|
3
|
+
* It lets caches be more efficient and save bandwidth, as a web server does not need to resend a full
|
|
4
|
+
* response if the content was not changed. Additionally, etags help to prevent simultaneous updates of
|
|
5
|
+
* a resource from overwriting each other ("mid-air collisions").
|
|
6
|
+
*
|
|
7
|
+
* If the resource at a given URL changes, a new Etag value must be generated. A comparison of them can
|
|
8
|
+
* determine whether two representations of a resource are the same. Etags are therefore similar to fingerprints, a
|
|
9
|
+
* nd might also be used for tracking purposes by some servers. They might also be set to persist indefinitely
|
|
10
|
+
* by a tracking server.
|
|
11
|
+
*
|
|
12
|
+
* For example, when editing a wiki, the current wiki content may be hashed and put into an Etag header
|
|
13
|
+
* in the response:
|
|
14
|
+
*
|
|
15
|
+
* ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
16
|
+
*
|
|
17
|
+
* When saving changes to a wiki page (posting data), the POST request will contain the If-Match header containing
|
|
18
|
+
* the ETag values to check freshness against.
|
|
19
|
+
*
|
|
20
|
+
* If-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
|
|
21
|
+
*
|
|
22
|
+
* If the hashes don't match, it means that the document has been edited in-between and a 412 Precondition Failed
|
|
23
|
+
* error is thrown.
|
|
24
|
+
*
|
|
25
|
+
* @param res
|
|
26
|
+
* @param ETag
|
|
27
|
+
* @returns {*}
|
|
28
|
+
*/
|
|
29
|
+
export function setOk200(res, ETag) {
|
|
30
|
+
res.set({ ETag: ETag });
|
|
31
|
+
res.status(200);
|
|
32
|
+
return res;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The 201 (Created) status code indicates that the request has been
|
|
36
|
+
* fulfilled and has resulted in one or more new resources being
|
|
37
|
+
* created. The primary resource created by the request is identified
|
|
38
|
+
* by either a Location header field in the response or, if no Location
|
|
39
|
+
* field is received, by the effective request URI.
|
|
40
|
+
*
|
|
41
|
+
* The 201 response payload typically describes and links to the
|
|
42
|
+
* resource(s) created. See
|
|
43
|
+
* https://datatracker.ietf.org/doc/html/rfc7231#section-7.2
|
|
44
|
+
* for a discussion of the meaning and purpose of validator header
|
|
45
|
+
* fields, such as ETag and Last-Modified, in a 201 response.
|
|
46
|
+
*
|
|
47
|
+
* @param res
|
|
48
|
+
* @returns {*}
|
|
49
|
+
*/
|
|
50
|
+
export function setCreated201(res) {
|
|
51
|
+
res.status(201);
|
|
52
|
+
return res;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The 400 (Bad Request) status code indicates that the server cannot or
|
|
56
|
+
* will not process the request due to something that is perceived to be
|
|
57
|
+
* a client error (e.g., malformed request syntax, invalid request
|
|
58
|
+
* message framing, or deceptive request routing).
|
|
59
|
+
*
|
|
60
|
+
* @param res
|
|
61
|
+
* @returns {*}
|
|
62
|
+
*/
|
|
63
|
+
export function setBadRequest400ClientError(res) {
|
|
64
|
+
res.status(400);
|
|
65
|
+
return res;
|
|
66
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create a new object containing only the properties listed in `arrayOfProperties`.
|
|
3
|
+
* Keeps the original behavior: only copies properties whose values are truthy.
|
|
4
|
+
*
|
|
5
|
+
* Example:
|
|
6
|
+
* extractObjectWithProperties({ a: 1, b: 0, c: 'x' }, ['a', 'b', 'c'])
|
|
7
|
+
* -> { a: 1, c: 'x' } // 'b' is omitted because 0 is falsy
|
|
8
|
+
*/
|
|
9
|
+
export declare function extractObjectWithProperties<T extends Record<string, unknown>, K extends keyof T>(obj: T | null | undefined, arrayOfProperties: ReadonlyArray<K>): Partial<Pick<T, K>>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// src/utilityFunctions.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a new object containing only the properties listed in `arrayOfProperties`.
|
|
4
|
+
* Keeps the original behavior: only copies properties whose values are truthy.
|
|
5
|
+
*
|
|
6
|
+
* Example:
|
|
7
|
+
* extractObjectWithProperties({ a: 1, b: 0, c: 'x' }, ['a', 'b', 'c'])
|
|
8
|
+
* -> { a: 1, c: 'x' } // 'b' is omitted because 0 is falsy
|
|
9
|
+
*/
|
|
10
|
+
export function extractObjectWithProperties(obj, arrayOfProperties) {
|
|
11
|
+
// Initialize with the correct type to avoid "element implicitly has an 'any' type"
|
|
12
|
+
const returnObj = {};
|
|
13
|
+
// If obj is null/undefined, return an empty object
|
|
14
|
+
if (!obj) {
|
|
15
|
+
return returnObj;
|
|
16
|
+
}
|
|
17
|
+
// Iterate using `for...of` to avoid lib/target issues with Array.prototype.find for older configs
|
|
18
|
+
for (const nameOfProperty of arrayOfProperties) {
|
|
19
|
+
const value = obj[nameOfProperty];
|
|
20
|
+
// Preserve original "truthy" filter: only copy if value is truthy
|
|
21
|
+
if (value) {
|
|
22
|
+
// Assign with proper typing
|
|
23
|
+
returnObj[nameOfProperty] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return returnObj;
|
|
27
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@carecard/common-util",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Common utility for MVC framework",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"author": "PK Singh",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https:github.com/CareCard-ca/pkg-common-util.git"
|
|
11
|
+
},
|
|
12
|
+
"type": "module",
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/cjs/index.cjs",
|
|
17
|
+
"module": "./dist/esm/index.js",
|
|
18
|
+
"types": "./dist/esm/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/esm/index.js",
|
|
22
|
+
"require": "./dist/cjs/index.cjs",
|
|
23
|
+
"types": "./dist/esm/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "npm run build:esm && npm run build:cjs",
|
|
31
|
+
"build:esm": "tsc -p tsconfig.esm.json",
|
|
32
|
+
"build:cjs": "tsc -p tsconfig.cjs.json && node ./scripts/rename-cjs.js",
|
|
33
|
+
"test": "NODE_NO_WARNINGS=1 jest --coverage",
|
|
34
|
+
"format": "prettier --write .",
|
|
35
|
+
"format:check": "prettier --check .",
|
|
36
|
+
"lint": "eslint",
|
|
37
|
+
"prepare": "husky"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/express": "5.0.6",
|
|
41
|
+
"@types/jest": "30.0.0",
|
|
42
|
+
"@types/supertest": "^6.0.3",
|
|
43
|
+
"eslint": "9.39.2",
|
|
44
|
+
"husky": "9.1.7",
|
|
45
|
+
"jest": "30.2.0",
|
|
46
|
+
"prettier": "3.7.4",
|
|
47
|
+
"supertest": "7.1.4",
|
|
48
|
+
"ts-jest": "29.4.6",
|
|
49
|
+
"typescript": "5.9.3",
|
|
50
|
+
"typescript-eslint": "8.50.1"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"express": "5.2.1"
|
|
54
|
+
}
|
|
55
|
+
}
|