nodecore-kit 0.1.0 → 0.3.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/README.md +138 -0
- package/dist/index.cjs +675 -41
- package/dist/index.d.ts +205 -13
- package/dist/index.js +648 -37
- package/package.json +25 -6
package/dist/index.js
CHANGED
|
@@ -1,4 +1,178 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/transport/http.ts
|
|
2
|
+
import Axios from "axios";
|
|
3
|
+
var makeRequest = async ({
|
|
4
|
+
url,
|
|
5
|
+
method = "GET",
|
|
6
|
+
headers = {},
|
|
7
|
+
token = void 0,
|
|
8
|
+
data = void 0
|
|
9
|
+
}) => {
|
|
10
|
+
try {
|
|
11
|
+
headers["X-Requested-With"] = "XMLHttpRequest";
|
|
12
|
+
token && (headers["Authorization"] = token);
|
|
13
|
+
const payload = {
|
|
14
|
+
method,
|
|
15
|
+
url,
|
|
16
|
+
headers
|
|
17
|
+
};
|
|
18
|
+
if (data)
|
|
19
|
+
payload.data = data;
|
|
20
|
+
const result = await Axios(payload);
|
|
21
|
+
return result.data;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
throw err.response ? { ...err.response.data, httpStatusCode: err.response.status } : err;
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// src/core/error.ts
|
|
28
|
+
var HTTP_STATUS = {
|
|
29
|
+
OK: { code: 200, message: "OK" },
|
|
30
|
+
CREATED: { code: 201, message: "Created" },
|
|
31
|
+
NO_CONTENT: { code: 204, message: "No Content" },
|
|
32
|
+
BAD_REQUEST: { code: 400, message: "Bad Request" },
|
|
33
|
+
UNAUTHORIZED: { code: 401, message: "Unauthorized" },
|
|
34
|
+
FORBIDDEN: { code: 403, message: "Forbidden" },
|
|
35
|
+
NOT_FOUND: { code: 404, message: "Not Found" },
|
|
36
|
+
CONFLICT: { code: 409, message: "Conflict" },
|
|
37
|
+
UNPROCESSABLE_ENTITY: { code: 422, message: "Unprocessable Entity" },
|
|
38
|
+
TOKEN_EXPIRED: { code: 498, message: "Token Expired" },
|
|
39
|
+
TOKEN_INVALID: { code: 499, message: "Token Invalid" },
|
|
40
|
+
SERVER_ERROR: { code: 500, message: "Internal Server Error" }
|
|
41
|
+
};
|
|
42
|
+
var HTTP_STATUS_CODE_ERROR = {
|
|
43
|
+
400: "VALIDATION_ERROR",
|
|
44
|
+
401: "AUTHENTICATION_ERROR",
|
|
45
|
+
402: "PAYMENT_REQUIRED_ERROR",
|
|
46
|
+
403: "AUTHORIZATION_ERROR",
|
|
47
|
+
404: "NOT_FOUND",
|
|
48
|
+
409: "ENTRY_EXISTS",
|
|
49
|
+
422: "VALIDATION_ERROR",
|
|
50
|
+
498: "TOKEN_EXPIRED",
|
|
51
|
+
499: "TOKEN_INVALID",
|
|
52
|
+
500: "FATAL_ERROR"
|
|
53
|
+
};
|
|
54
|
+
var AppError = class extends Error {
|
|
55
|
+
constructor(status, message, errorCode, meta) {
|
|
56
|
+
super(message || status.message);
|
|
57
|
+
this.name = new.target.name;
|
|
58
|
+
this.statusCode = status.code;
|
|
59
|
+
this.statusMessage = status.message;
|
|
60
|
+
this.errorCode = errorCode;
|
|
61
|
+
this.meta = meta;
|
|
62
|
+
Error.captureStackTrace(this, new.target);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var ValidationError = class extends AppError {
|
|
66
|
+
constructor(message, meta) {
|
|
67
|
+
super(
|
|
68
|
+
HTTP_STATUS.UNPROCESSABLE_ENTITY,
|
|
69
|
+
message,
|
|
70
|
+
"VALIDATION_ERROR",
|
|
71
|
+
meta
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
var AuthenticationError = class extends AppError {
|
|
76
|
+
constructor(message, meta) {
|
|
77
|
+
super(
|
|
78
|
+
HTTP_STATUS.UNAUTHORIZED,
|
|
79
|
+
message,
|
|
80
|
+
"AUTHENTICATION_ERROR",
|
|
81
|
+
meta
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
var AuthorizationError = class extends AppError {
|
|
86
|
+
constructor(message, meta) {
|
|
87
|
+
super(HTTP_STATUS.FORBIDDEN, message, "AUTHORIZATION_ERROR", meta);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var NotFoundError = class extends AppError {
|
|
91
|
+
constructor(message, meta) {
|
|
92
|
+
super(HTTP_STATUS.NOT_FOUND, message, "NOT_FOUND", meta);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var TokenExpiredError = class extends AppError {
|
|
96
|
+
constructor(message, meta) {
|
|
97
|
+
super(HTTP_STATUS.TOKEN_EXPIRED, message, "TOKEN_EXPIRED", meta);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var TokenInvalidError = class extends AppError {
|
|
101
|
+
constructor(message, meta) {
|
|
102
|
+
super(HTTP_STATUS.TOKEN_INVALID, message, "TOKEN_INVALID", meta);
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var BadRequestError = class extends AppError {
|
|
106
|
+
constructor(message, meta) {
|
|
107
|
+
super(HTTP_STATUS.BAD_REQUEST, message, "BAD_REQUEST", meta);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var ServerError = class extends AppError {
|
|
111
|
+
constructor(message, meta) {
|
|
112
|
+
super(HTTP_STATUS.SERVER_ERROR, message, "SERVER_ERROR", meta);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var ExistingError = class extends AppError {
|
|
116
|
+
constructor(message, meta) {
|
|
117
|
+
super(HTTP_STATUS.CONFLICT, message, "ENTRY_EXISTS", meta);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var NoContent = class extends AppError {
|
|
121
|
+
constructor(message, meta) {
|
|
122
|
+
super(HTTP_STATUS.NO_CONTENT, message, "NO_CONTENT", meta);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var errorHandler = (err, ERROR_TYPE = "FATAL_ERROR", service = "Unknown Service") => {
|
|
126
|
+
const response = {
|
|
127
|
+
success: false,
|
|
128
|
+
message: "Something went wrong",
|
|
129
|
+
error: ERROR_TYPE,
|
|
130
|
+
httpStatusCode: 500,
|
|
131
|
+
service
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
if (!err)
|
|
135
|
+
return response;
|
|
136
|
+
if (err instanceof AppError) {
|
|
137
|
+
return {
|
|
138
|
+
...response,
|
|
139
|
+
message: err.message,
|
|
140
|
+
error: err.errorCode || err.name,
|
|
141
|
+
httpStatusCode: err.statusCode
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
if (err.isAxiosError) {
|
|
145
|
+
return {
|
|
146
|
+
...response,
|
|
147
|
+
message: err?.response?.data?.message || err.message || response.message,
|
|
148
|
+
error: err?.response?.data?.error || HTTP_STATUS_CODE_ERROR[err?.response?.status] || ERROR_TYPE,
|
|
149
|
+
httpStatusCode: err?.response?.status || 500
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (err instanceof Error) {
|
|
153
|
+
return {
|
|
154
|
+
...response,
|
|
155
|
+
message: err.message,
|
|
156
|
+
error: err.name
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (typeof err === "string") {
|
|
160
|
+
return {
|
|
161
|
+
...response,
|
|
162
|
+
message: err
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return response;
|
|
166
|
+
} catch {
|
|
167
|
+
return response;
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
var expressErrorMiddleware = () => (err, req, res, next) => {
|
|
171
|
+
const error = errorHandler(err);
|
|
172
|
+
res.status(error.httpStatusCode).json(error);
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// src/core/utils.ts
|
|
2
176
|
var paginate = (totalCount, currentPage, perPage) => {
|
|
3
177
|
const previousPage = currentPage - 1;
|
|
4
178
|
return {
|
|
@@ -6,49 +180,486 @@ var paginate = (totalCount, currentPage, perPage) => {
|
|
|
6
180
|
offset: currentPage > 1 ? previousPage * perPage : 0
|
|
7
181
|
};
|
|
8
182
|
};
|
|
183
|
+
var formatDate = (date) => {
|
|
184
|
+
const day = date.getDate().toString().padStart(2, "0");
|
|
185
|
+
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
|
186
|
+
const year = date.getFullYear();
|
|
187
|
+
return `${day}/${month}/${year}`;
|
|
188
|
+
};
|
|
189
|
+
var parseJSON = (value) => {
|
|
190
|
+
try {
|
|
191
|
+
return JSON.parse(value);
|
|
192
|
+
} catch (err) {
|
|
193
|
+
return value;
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var stringifyJSON = (value) => {
|
|
197
|
+
try {
|
|
198
|
+
return JSON.stringify(value);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
var isObject = (val) => val && typeof val === "object" && !Array.isArray(val);
|
|
204
|
+
var sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
205
|
+
var capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);
|
|
206
|
+
var isEmpty = (val) => val === null || val === void 0 || typeof val === "object" && Object.keys(val).length === 0 || typeof val === "string" && val.trim() === "";
|
|
207
|
+
|
|
208
|
+
// src/core/uuid.ts
|
|
209
|
+
import { v1 as uuidV1, v4 as uuidV4, validate as UUIDValidaton } from "uuid";
|
|
210
|
+
var uuid = {
|
|
211
|
+
toBinary: (uuid2) => {
|
|
212
|
+
if (!uuid2)
|
|
213
|
+
uuid2 = uuidV1();
|
|
214
|
+
else if (typeof uuid2 !== "string" && Buffer.isBuffer(uuid2))
|
|
215
|
+
return uuid2;
|
|
216
|
+
const buf = Buffer.from(uuid2.replace(/-/g, ""), "hex");
|
|
217
|
+
return Buffer.concat([
|
|
218
|
+
buf.subarray(6, 8),
|
|
219
|
+
buf.subarray(4, 6),
|
|
220
|
+
buf.subarray(0, 4),
|
|
221
|
+
buf.subarray(8, 16)
|
|
222
|
+
]);
|
|
223
|
+
},
|
|
224
|
+
toString: (binary) => {
|
|
225
|
+
if (!binary)
|
|
226
|
+
throw new Error("Kindly supply binary UUID value");
|
|
227
|
+
if (typeof binary === "string")
|
|
228
|
+
return binary;
|
|
229
|
+
return [
|
|
230
|
+
binary.toString("hex", 4, 8),
|
|
231
|
+
binary.toString("hex", 2, 4),
|
|
232
|
+
binary.toString("hex", 0, 2),
|
|
233
|
+
binary.toString("hex", 8, 10),
|
|
234
|
+
binary.toString("hex", 10, 16)
|
|
235
|
+
].join("-");
|
|
236
|
+
},
|
|
237
|
+
get: (version) => {
|
|
238
|
+
const uuid2 = {
|
|
239
|
+
v1: uuidV1(),
|
|
240
|
+
v4: uuidV4()
|
|
241
|
+
};
|
|
242
|
+
return uuid2[version || "v1"];
|
|
243
|
+
},
|
|
244
|
+
isValid: (uuid2) => UUIDValidaton(uuid2),
|
|
245
|
+
manyToString: (data, keys = []) => {
|
|
246
|
+
if (!data)
|
|
247
|
+
return;
|
|
248
|
+
keys.forEach((key) => {
|
|
249
|
+
if (data[key])
|
|
250
|
+
data[key] = uuid.toString(data[key]);
|
|
251
|
+
});
|
|
252
|
+
return data;
|
|
253
|
+
},
|
|
254
|
+
manyToBinary: (data, keys = []) => {
|
|
255
|
+
if (!data)
|
|
256
|
+
return;
|
|
257
|
+
keys.forEach((key) => {
|
|
258
|
+
if (data[key])
|
|
259
|
+
data[key] = uuid.toBinary(data[key]);
|
|
260
|
+
});
|
|
261
|
+
return data;
|
|
262
|
+
}
|
|
263
|
+
};
|
|
9
264
|
|
|
10
|
-
// src/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
265
|
+
// src/transport/express/joiValidator.ts
|
|
266
|
+
var validate = (schema, object, option = { abortEarly: true, allowUnknown: false }) => {
|
|
267
|
+
const check = schema.validate(object, option);
|
|
268
|
+
if (check.error) {
|
|
269
|
+
throw new ValidationError(check.error.details[0].message);
|
|
270
|
+
}
|
|
271
|
+
return check.value;
|
|
272
|
+
};
|
|
273
|
+
function joiValidator(constraint, isMiddleware = true) {
|
|
274
|
+
if (!constraint)
|
|
275
|
+
throw new ValidationError(
|
|
276
|
+
"Kindly supply validation schema to joiValidator"
|
|
277
|
+
);
|
|
278
|
+
if (!isMiddleware) {
|
|
279
|
+
return validate(constraint.schema, constraint.data, constraint.option);
|
|
280
|
+
}
|
|
281
|
+
return async (req, res, next) => {
|
|
282
|
+
try {
|
|
283
|
+
if (constraint.body) {
|
|
284
|
+
req.body = validate(
|
|
285
|
+
constraint.body.schema,
|
|
286
|
+
req.body,
|
|
287
|
+
constraint.body.options
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
if (constraint.params)
|
|
291
|
+
req.params = validate(
|
|
292
|
+
constraint.params.schema,
|
|
293
|
+
req.params,
|
|
294
|
+
constraint.params.options
|
|
295
|
+
);
|
|
296
|
+
if (constraint.query)
|
|
297
|
+
req.query = validate(
|
|
298
|
+
constraint.query.schema,
|
|
299
|
+
req.query,
|
|
300
|
+
constraint.query.options
|
|
301
|
+
);
|
|
302
|
+
if (constraint.headers)
|
|
303
|
+
req.headers = validate(
|
|
304
|
+
constraint.headers.schema,
|
|
305
|
+
req.headers,
|
|
306
|
+
constraint.headers.options
|
|
307
|
+
);
|
|
308
|
+
return next();
|
|
309
|
+
} catch (err) {
|
|
310
|
+
next(err);
|
|
311
|
+
}
|
|
17
312
|
};
|
|
18
|
-
|
|
19
|
-
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// src/adapters/redis.ts
|
|
316
|
+
import RedisClient from "ioredis";
|
|
317
|
+
var Redis = class {
|
|
318
|
+
constructor(url, options = {}) {
|
|
319
|
+
if (!url)
|
|
320
|
+
throw new ValidationError("Redis connection URL is required");
|
|
321
|
+
this.client = new RedisClient(url, {
|
|
322
|
+
maxRetriesPerRequest: 3,
|
|
323
|
+
enableReadyCheck: true,
|
|
324
|
+
lazyConnect: true,
|
|
325
|
+
...options
|
|
326
|
+
});
|
|
327
|
+
this.registerListeners();
|
|
20
328
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
};
|
|
36
|
-
|
|
329
|
+
registerListeners() {
|
|
330
|
+
this.client.on("connect", () => {
|
|
331
|
+
console.info("\u{1F534} Redis connected");
|
|
332
|
+
});
|
|
333
|
+
this.client.on("ready", () => {
|
|
334
|
+
console.info("\u{1F7E2} Redis ready");
|
|
335
|
+
});
|
|
336
|
+
this.client.on("error", (err) => {
|
|
337
|
+
console.error("\u{1F534} Redis error:", err);
|
|
338
|
+
});
|
|
339
|
+
this.client.on("close", () => {
|
|
340
|
+
console.warn("\u{1F7E0} Redis connection closed");
|
|
341
|
+
});
|
|
342
|
+
this.client.on("reconnecting", () => {
|
|
343
|
+
console.warn("\u{1F7E1} Redis reconnecting...");
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
async start() {
|
|
347
|
+
try {
|
|
348
|
+
if (this.client.status === "ready")
|
|
349
|
+
return;
|
|
350
|
+
await this.client.connect();
|
|
351
|
+
} catch (err) {
|
|
352
|
+
throw new ServerError("Failed to connect to Redis", { cause: err });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async disconnect() {
|
|
356
|
+
try {
|
|
357
|
+
if (this.client.status !== "end") {
|
|
358
|
+
await this.client.quit();
|
|
359
|
+
}
|
|
360
|
+
} catch {
|
|
361
|
+
await this.client.disconnect();
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
async keys(pattern) {
|
|
365
|
+
if (!pattern || typeof pattern !== "string") {
|
|
366
|
+
throw new ValidationError("Redis key pattern must be a string");
|
|
367
|
+
}
|
|
368
|
+
return this.client.keys(pattern);
|
|
369
|
+
}
|
|
370
|
+
serialize(data) {
|
|
371
|
+
if (typeof data === "string")
|
|
372
|
+
return data;
|
|
373
|
+
if (typeof data === "number")
|
|
374
|
+
return String(data);
|
|
375
|
+
return JSON.stringify(data);
|
|
376
|
+
}
|
|
377
|
+
deserialize(data, parse = true) {
|
|
378
|
+
if (!parse || !data)
|
|
379
|
+
return data;
|
|
380
|
+
return parseJSON(data);
|
|
381
|
+
}
|
|
382
|
+
async set(key, data) {
|
|
383
|
+
if (!key || typeof key !== "string") {
|
|
384
|
+
throw new ValidationError("Redis key must be a string");
|
|
385
|
+
}
|
|
386
|
+
return this.client.set(key, this.serialize(data));
|
|
387
|
+
}
|
|
388
|
+
async setEx(key, data, duration) {
|
|
389
|
+
if (!key || typeof key !== "string") {
|
|
390
|
+
throw new ValidationError("Redis key must be a string");
|
|
391
|
+
}
|
|
392
|
+
const ttl = this.parseDuration(duration);
|
|
393
|
+
return this.client.setex(key, ttl, this.serialize(data));
|
|
394
|
+
}
|
|
395
|
+
async get(key, parse = true) {
|
|
396
|
+
if (!key || typeof key !== "string") {
|
|
397
|
+
throw new ValidationError("Redis key must be a string");
|
|
398
|
+
}
|
|
399
|
+
const data = await this.client.get(key);
|
|
400
|
+
return this.deserialize(data, parse);
|
|
401
|
+
}
|
|
402
|
+
async delete(key) {
|
|
403
|
+
if (!key || typeof key !== "string") {
|
|
404
|
+
throw new ValidationError("Redis key must be a string");
|
|
405
|
+
}
|
|
406
|
+
return Boolean(await this.client.del(key));
|
|
407
|
+
}
|
|
408
|
+
async deleteAll(prefix) {
|
|
409
|
+
const keys = await this.keys(prefix);
|
|
410
|
+
if (!keys.length)
|
|
411
|
+
return 0;
|
|
412
|
+
return this.client.del(...keys);
|
|
413
|
+
}
|
|
414
|
+
async exists(key) {
|
|
415
|
+
return Boolean(await this.client.exists(key));
|
|
416
|
+
}
|
|
417
|
+
async ttl(key) {
|
|
418
|
+
return this.client.ttl(key);
|
|
419
|
+
}
|
|
420
|
+
async expire(key, duration) {
|
|
421
|
+
const ttl = this.parseDuration(duration);
|
|
422
|
+
return Boolean(await this.client.expire(key, ttl));
|
|
423
|
+
}
|
|
424
|
+
async flush() {
|
|
425
|
+
await this.client.flushdb();
|
|
426
|
+
}
|
|
427
|
+
// ───────────────────────────────
|
|
428
|
+
// Auth Cache Helpers
|
|
429
|
+
// ───────────────────────────────
|
|
430
|
+
async getCachedUser(id, throwError = true) {
|
|
431
|
+
const userToken = `${id}-token`;
|
|
432
|
+
const user = await this.get(userToken);
|
|
433
|
+
if (!user && throwError) {
|
|
434
|
+
throw new AuthenticationError("Kindly login, user not found");
|
|
435
|
+
}
|
|
436
|
+
return user;
|
|
437
|
+
}
|
|
438
|
+
async cacheUser(user, ttl = "1 day") {
|
|
439
|
+
if (!user?.id || !user?.tokenRef) {
|
|
440
|
+
throw new ValidationError("Invalid user object for caching");
|
|
37
441
|
}
|
|
38
|
-
|
|
442
|
+
await Promise.all([
|
|
443
|
+
this.setEx(user.tokenRef, user, ttl),
|
|
444
|
+
this.setEx(`${user.id}-token`, user, ttl)
|
|
445
|
+
]);
|
|
446
|
+
}
|
|
447
|
+
async updateAuthData(userId, key, value, action = "ADD") {
|
|
448
|
+
const user = await this.getCachedUser(userId, false);
|
|
449
|
+
if (!user)
|
|
450
|
+
return null;
|
|
451
|
+
if (!Array.isArray(user[key]))
|
|
452
|
+
return user;
|
|
453
|
+
if (action === "ADD" && !user[key].includes(value)) {
|
|
454
|
+
user[key].push(value);
|
|
455
|
+
}
|
|
456
|
+
if (action === "REMOVE") {
|
|
457
|
+
user[key] = user[key].filter((v) => v !== value);
|
|
458
|
+
}
|
|
459
|
+
await this.cacheUser(user);
|
|
460
|
+
return user;
|
|
461
|
+
}
|
|
462
|
+
// ───────────────────────────────
|
|
463
|
+
// Helpers
|
|
464
|
+
// ───────────────────────────────
|
|
465
|
+
parseDuration(duration) {
|
|
466
|
+
if (typeof duration === "number")
|
|
467
|
+
return duration;
|
|
468
|
+
const [valueStr, unit] = duration.split(" ");
|
|
469
|
+
const value = Number(valueStr);
|
|
470
|
+
if (Number.isNaN(value)) {
|
|
471
|
+
throw new ValidationError(`Invalid duration format: ${duration}`);
|
|
472
|
+
}
|
|
473
|
+
switch (unit) {
|
|
474
|
+
case "days":
|
|
475
|
+
case "day":
|
|
476
|
+
return value * 86400;
|
|
477
|
+
case "hours":
|
|
478
|
+
case "hour":
|
|
479
|
+
return value * 3600;
|
|
480
|
+
case "minutes":
|
|
481
|
+
case "minute":
|
|
482
|
+
return value * 60;
|
|
483
|
+
case "seconds":
|
|
484
|
+
case "second":
|
|
485
|
+
return value;
|
|
486
|
+
default:
|
|
487
|
+
throw new ValidationError(`Invalid duration unit: ${unit}`);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
// src/adapters/sqs.ts
|
|
493
|
+
import AWS from "aws-sdk";
|
|
494
|
+
var SQS = class {
|
|
495
|
+
constructor(config, logger) {
|
|
496
|
+
this.logger = logger || {
|
|
497
|
+
info: (msg, meta) => console.info(msg, meta),
|
|
498
|
+
error: (msg, meta) => console.error(msg, meta),
|
|
499
|
+
warn: (msg, meta) => console.warn(msg, meta),
|
|
500
|
+
debug: (msg, meta) => console.debug(msg, meta)
|
|
501
|
+
};
|
|
502
|
+
this.client = new AWS.SQS({
|
|
503
|
+
region: config.region,
|
|
504
|
+
accessKeyId: config.accessKeyId,
|
|
505
|
+
secretAccessKey: config.secretAccessKey
|
|
506
|
+
});
|
|
507
|
+
this.logger.info("SQS client initialized", { region: config.region });
|
|
508
|
+
}
|
|
509
|
+
async enqueue({ queueUrl, message }) {
|
|
510
|
+
try {
|
|
511
|
+
await this.client.sendMessage({
|
|
512
|
+
QueueUrl: queueUrl,
|
|
513
|
+
MessageBody: typeof message === "string" ? message : JSON.stringify(message)
|
|
514
|
+
}).promise();
|
|
515
|
+
this.logger.info("Message enqueued", { queueUrl });
|
|
516
|
+
return true;
|
|
517
|
+
} catch (err) {
|
|
518
|
+
this.logger.error("SQSEnqueueError", { err, queueUrl });
|
|
519
|
+
throw new ServerError("Failed to enqueue SQS message", { cause: err });
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
async dequeue(fields) {
|
|
523
|
+
const {
|
|
524
|
+
queueUrl,
|
|
525
|
+
consumerFunction,
|
|
526
|
+
dlqUrl,
|
|
527
|
+
maxNumberOfMessages = 10,
|
|
528
|
+
waitTimeSeconds = 20
|
|
529
|
+
} = fields;
|
|
530
|
+
while (true) {
|
|
531
|
+
try {
|
|
532
|
+
const { Messages } = await this.client.receiveMessage({
|
|
533
|
+
QueueUrl: queueUrl,
|
|
534
|
+
MaxNumberOfMessages: maxNumberOfMessages,
|
|
535
|
+
WaitTimeSeconds: waitTimeSeconds
|
|
536
|
+
}).promise();
|
|
537
|
+
if (Messages?.length) {
|
|
538
|
+
for (const { Body, ReceiptHandle } of Messages) {
|
|
539
|
+
if (!Body || !ReceiptHandle)
|
|
540
|
+
continue;
|
|
541
|
+
try {
|
|
542
|
+
const message = parseJSON(Body);
|
|
543
|
+
await consumerFunction(message);
|
|
544
|
+
} catch (err) {
|
|
545
|
+
this.logger.error("SQSConsumerError", { err, queueUrl });
|
|
546
|
+
if (dlqUrl) {
|
|
547
|
+
await this.enqueue({ queueUrl: dlqUrl, message: Body });
|
|
548
|
+
}
|
|
549
|
+
} finally {
|
|
550
|
+
await this.client.deleteMessage({
|
|
551
|
+
QueueUrl: queueUrl,
|
|
552
|
+
ReceiptHandle
|
|
553
|
+
}).promise();
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
} catch (err) {
|
|
558
|
+
this.logger.error("SQSPollingError", { err, queueUrl });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/adapters/loggers/winston.ts
|
|
565
|
+
import winston from "winston";
|
|
566
|
+
var WinstonLogger = class {
|
|
567
|
+
constructor() {
|
|
568
|
+
this.logger = winston.createLogger({
|
|
569
|
+
transports: [new winston.transports.Console()]
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
info(message, meta) {
|
|
573
|
+
this.logger.info(message, meta);
|
|
574
|
+
}
|
|
575
|
+
error(message, meta) {
|
|
576
|
+
this.logger.error(message, meta);
|
|
577
|
+
}
|
|
578
|
+
warn(message, meta) {
|
|
579
|
+
this.logger.warn(message, meta);
|
|
580
|
+
}
|
|
581
|
+
debug(message, meta) {
|
|
582
|
+
this.logger.debug(message, meta);
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
// src/security/jwt.ts
|
|
587
|
+
import jwt from "jsonwebtoken";
|
|
588
|
+
var jwtService = {
|
|
589
|
+
async encode({
|
|
590
|
+
data,
|
|
591
|
+
secretKey,
|
|
592
|
+
expiresIn = "24h",
|
|
593
|
+
algorithm = "HS256"
|
|
594
|
+
}) {
|
|
595
|
+
if (!secretKey) {
|
|
596
|
+
throw new ValidationError("Secret key is required for JWT encoding");
|
|
597
|
+
}
|
|
598
|
+
const options = {
|
|
599
|
+
expiresIn,
|
|
600
|
+
algorithm
|
|
601
|
+
};
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
jwt.sign(data, secretKey, options, (err, token) => {
|
|
604
|
+
if (err || !token)
|
|
605
|
+
return reject(err);
|
|
606
|
+
resolve(token);
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
},
|
|
610
|
+
async decode({
|
|
611
|
+
token,
|
|
612
|
+
secretKey,
|
|
613
|
+
algorithms
|
|
614
|
+
}) {
|
|
615
|
+
if (!secretKey) {
|
|
616
|
+
throw new ValidationError("Secret key is required for JWT verification");
|
|
617
|
+
}
|
|
618
|
+
if (!token) {
|
|
619
|
+
throw new ValidationError("JWT token is required");
|
|
620
|
+
}
|
|
621
|
+
const options = {};
|
|
622
|
+
if (algorithms) {
|
|
623
|
+
options.algorithms = algorithms;
|
|
624
|
+
}
|
|
625
|
+
return new Promise((resolve, reject) => {
|
|
626
|
+
jwt.verify(token, secretKey, options, (err, decoded) => {
|
|
627
|
+
if (err)
|
|
628
|
+
return reject(err);
|
|
629
|
+
resolve(decoded);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
39
632
|
}
|
|
40
633
|
};
|
|
41
|
-
var get = (url, options) => makeRequest({ ...options, url, method: "GET" });
|
|
42
|
-
var post = (url, data, options) => makeRequest({ ...options, url, method: "POST", data });
|
|
43
|
-
var put = (url, data, options) => makeRequest({ ...options, url, method: "PUT", data });
|
|
44
|
-
var patch = (url, data, options) => makeRequest({ ...options, url, method: "PATCH", data });
|
|
45
|
-
var del = (url, options) => makeRequest({ ...options, url, method: "DELETE" });
|
|
46
634
|
export {
|
|
47
|
-
|
|
48
|
-
|
|
635
|
+
AppError,
|
|
636
|
+
AuthenticationError,
|
|
637
|
+
AuthorizationError,
|
|
638
|
+
BadRequestError,
|
|
639
|
+
ExistingError,
|
|
640
|
+
HTTP_STATUS,
|
|
641
|
+
HTTP_STATUS_CODE_ERROR,
|
|
642
|
+
NoContent,
|
|
643
|
+
NotFoundError,
|
|
644
|
+
Redis,
|
|
645
|
+
SQS,
|
|
646
|
+
ServerError,
|
|
647
|
+
TokenExpiredError,
|
|
648
|
+
TokenInvalidError,
|
|
649
|
+
ValidationError,
|
|
650
|
+
WinstonLogger,
|
|
651
|
+
capitalize,
|
|
652
|
+
errorHandler,
|
|
653
|
+
expressErrorMiddleware,
|
|
654
|
+
formatDate,
|
|
655
|
+
isEmpty,
|
|
656
|
+
isObject,
|
|
657
|
+
joiValidator,
|
|
658
|
+
jwtService,
|
|
49
659
|
makeRequest,
|
|
50
660
|
paginate,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
661
|
+
parseJSON,
|
|
662
|
+
sleep,
|
|
663
|
+
stringifyJSON,
|
|
664
|
+
uuid
|
|
54
665
|
};
|