tsledge-core 0.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +100 -0
  3. package/dist/app.d.ts +14 -0
  4. package/dist/app.d.ts.map +1 -0
  5. package/dist/bin/repl.js +1181 -0
  6. package/dist/bin/repl.js.map +7 -0
  7. package/dist/core/http.d.ts +15 -0
  8. package/dist/core/http.d.ts.map +1 -0
  9. package/dist/core/index.d.ts +4 -0
  10. package/dist/core/index.d.ts.map +1 -0
  11. package/dist/core/query-builder.d.ts +96 -0
  12. package/dist/core/query-builder.d.ts.map +1 -0
  13. package/dist/core/types.d.ts +58 -0
  14. package/dist/core/types.d.ts.map +1 -0
  15. package/dist/db/index.d.ts +2 -0
  16. package/dist/db/index.d.ts.map +1 -0
  17. package/dist/db/mongodb.d.ts +6 -0
  18. package/dist/db/mongodb.d.ts.map +1 -0
  19. package/dist/exitCodes.d.ts +4 -0
  20. package/dist/exitCodes.d.ts.map +1 -0
  21. package/dist/fluent-interface/fluent-pattern-handler.d.ts +60 -0
  22. package/dist/fluent-interface/fluent-pattern-handler.d.ts.map +1 -0
  23. package/dist/fluent-interface/index.d.ts +3 -0
  24. package/dist/fluent-interface/index.d.ts.map +1 -0
  25. package/dist/fluent-interface/types.d.ts +26 -0
  26. package/dist/fluent-interface/types.d.ts.map +1 -0
  27. package/dist/index.d.ts +10 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/middleware/authentication/index.d.ts +4 -0
  30. package/dist/middleware/authentication/index.d.ts.map +1 -0
  31. package/dist/middleware/authentication/session.d.ts +63 -0
  32. package/dist/middleware/authentication/session.d.ts.map +1 -0
  33. package/dist/middleware/authentication/types.d.ts +34 -0
  34. package/dist/middleware/authentication/types.d.ts.map +1 -0
  35. package/dist/middleware/authentication/validation.d.ts +64 -0
  36. package/dist/middleware/authentication/validation.d.ts.map +1 -0
  37. package/dist/middleware/file-storage.d.ts +4 -0
  38. package/dist/middleware/file-storage.d.ts.map +1 -0
  39. package/dist/middleware/index.d.ts +5 -0
  40. package/dist/middleware/index.d.ts.map +1 -0
  41. package/dist/middleware/logger.d.ts +4 -0
  42. package/dist/middleware/logger.d.ts.map +1 -0
  43. package/dist/middleware/types.d.ts +2 -0
  44. package/dist/middleware/types.d.ts.map +1 -0
  45. package/dist/models/auth-token-blocklist.d.ts +13 -0
  46. package/dist/models/auth-token-blocklist.d.ts.map +1 -0
  47. package/dist/models/auth-user.d.ts +15 -0
  48. package/dist/models/auth-user.d.ts.map +1 -0
  49. package/dist/models/index.d.ts +3 -0
  50. package/dist/models/index.d.ts.map +1 -0
  51. package/dist/src/index.js +1159 -0
  52. package/dist/src/index.js.map +7 -0
  53. package/dist/tests/main.js +1111 -0
  54. package/dist/tests/main.js.map +7 -0
  55. package/dist/types.d.ts +26 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/utils/date.d.ts +2 -0
  58. package/dist/utils/date.d.ts.map +1 -0
  59. package/dist/utils/encoding.d.ts +13 -0
  60. package/dist/utils/encoding.d.ts.map +1 -0
  61. package/dist/utils/env.d.ts +9 -0
  62. package/dist/utils/env.d.ts.map +1 -0
  63. package/dist/utils/index.d.ts +6 -0
  64. package/dist/utils/index.d.ts.map +1 -0
  65. package/dist/utils/mongo-relation.d.ts +21 -0
  66. package/dist/utils/mongo-relation.d.ts.map +1 -0
  67. package/dist/utils/validation.d.ts +59 -0
  68. package/dist/utils/validation.d.ts.map +1 -0
  69. package/package.json +76 -0
@@ -0,0 +1,1181 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/index.ts
8
+ var src_exports = {};
9
+ __export(src_exports, {
10
+ AuthTokenBlocklistModel: () => AuthTokenBlocklistModel,
11
+ AuthUserModel: () => AuthUserModel,
12
+ Codec: () => Codec,
13
+ EXIT_CODE_GENERAL_ERROR: () => EXIT_CODE_GENERAL_ERROR,
14
+ EXIT_CODE_INVALID_CONFIG: () => EXIT_CODE_INVALID_CONFIG,
15
+ EXIT_CODE_SUCCESS: () => EXIT_CODE_SUCCESS,
16
+ FluentPatternHandler: () => FluentPatternHandler,
17
+ HttpMethod: () => HttpMethod,
18
+ JoinRelation: () => JoinRelation,
19
+ JwtRefreshSecret: () => JwtRefreshSecret,
20
+ JwtSecret: () => JwtSecret,
21
+ QueryBuilder: () => QueryBuilder,
22
+ authLogin: () => authLogin,
23
+ authLogout: () => authLogout,
24
+ authRefresh: () => authRefresh,
25
+ authRegister: () => authRegister,
26
+ clamp: () => clamp,
27
+ connectMongoDB: () => connectMongoDB,
28
+ createApp: () => createApp,
29
+ decodeFromBase64: () => decodeFromBase64,
30
+ diskFileUpload: () => diskFileUpload,
31
+ encodeToBase64: () => encodeToBase64,
32
+ errorLogger: () => errorLogger,
33
+ fluentRequestQueryAttributes: () => fluentRequestQueryAttributes,
34
+ formDataParser: () => formDataParser,
35
+ getCurrentDateString: () => getCurrentDateString,
36
+ httpRequest: () => httpRequest,
37
+ insertCollectionRelations: () => insertCollectionRelations,
38
+ isDebug: () => isDebug,
39
+ isNonEmptyObjectOrArray: () => isNonEmptyObjectOrArray,
40
+ isNumberInRange: () => isNumberInRange,
41
+ jwtRefreshRequired: () => jwtRefreshRequired,
42
+ jwtRequired: () => jwtRequired,
43
+ memoryFileUpload: () => memoryFileUpload,
44
+ mergeCollectionRelations: () => mergeCollectionRelations,
45
+ mergeObjects: () => mergeObjects,
46
+ parseBoolean: () => parseBoolean,
47
+ parseList: () => parseList,
48
+ parseNumber: () => parseNumber,
49
+ requestLogger: () => requestLogger,
50
+ socketToken: () => socketToken,
51
+ validateString: () => validateString,
52
+ verifyToken: () => verifyToken
53
+ });
54
+
55
+ // src/db/mongodb.ts
56
+ import mongoose from "mongoose";
57
+
58
+ // src/exitCodes.ts
59
+ var EXIT_CODE_SUCCESS = 0;
60
+ var EXIT_CODE_GENERAL_ERROR = 1;
61
+ var EXIT_CODE_INVALID_CONFIG = 2;
62
+
63
+ // src/utils/date.ts
64
+ function getCurrentDateString() {
65
+ let date = /* @__PURE__ */ new Date();
66
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
67
+ }
68
+
69
+ // src/utils/mongo-relation.ts
70
+ function insertCollectionRelations(model, relations, compareFunc, validateFunc = void 0) {
71
+ if (relations == void 0) {
72
+ return;
73
+ }
74
+ let relationStack = [];
75
+ for (let relation of relations) {
76
+ if (validateFunc) {
77
+ relation = validateFunc(relation);
78
+ }
79
+ let inStack = false;
80
+ for (let duplicateRelationCheck of relationStack) {
81
+ if (compareFunc(relation, duplicateRelationCheck)) {
82
+ inStack = true;
83
+ }
84
+ }
85
+ if (!inStack) {
86
+ relationStack.push(relation);
87
+ }
88
+ }
89
+ if (relationStack.length > 0) {
90
+ model.insertMany(relationStack);
91
+ }
92
+ }
93
+ async function mergeCollectionRelations(model, relationsToMerge, match, compareFunc, validateFunc) {
94
+ const relationStack = [];
95
+ if (relationsToMerge != null) {
96
+ for (const relation of relationsToMerge) {
97
+ const validatedRelation = validateFunc(relation);
98
+ if (!relationStack.some(
99
+ (duplicateRelationCheck) => compareFunc(validatedRelation, duplicateRelationCheck)
100
+ )) {
101
+ relationStack.push(validatedRelation);
102
+ }
103
+ }
104
+ }
105
+ if (relationStack.length === 0) {
106
+ await model.deleteMany(match);
107
+ } else {
108
+ const existingRels = await model.find(match).lean();
109
+ const toRemoveRels = [];
110
+ for (const rel of existingRels) {
111
+ if (!relationStack.some((relationFromStack) => compareFunc(rel, relationFromStack))) {
112
+ toRemoveRels.push(rel);
113
+ }
114
+ }
115
+ const toCreateRels = [];
116
+ for (const relationFromStack of relationStack) {
117
+ if (!existingRels.some((rel) => compareFunc(rel, relationFromStack))) {
118
+ toCreateRels.push(relationFromStack);
119
+ }
120
+ }
121
+ if (toCreateRels.length > 0) {
122
+ await model.insertMany(toCreateRels);
123
+ }
124
+ if (toRemoveRels.length > 0) {
125
+ const idsToRemove = toRemoveRels.map((rel) => rel._id);
126
+ await model.deleteMany({ _id: { $in: idsToRemove } });
127
+ }
128
+ }
129
+ }
130
+
131
+ // src/utils/validation.ts
132
+ function isNonEmptyObjectOrArray(value) {
133
+ return value != null && value != void 0 && (Array.isArray(value) && value.length > 0 || typeof value === "object" && !Array.isArray(value) && Object.keys(value).length > 0);
134
+ }
135
+ function validateString(value, fallback = null) {
136
+ if (value !== void 0 && value !== null && value !== "" && value !== "undefined" && value !== "null") {
137
+ return value;
138
+ }
139
+ return fallback;
140
+ }
141
+ function mergeObjects(input, target, prefix = void 0) {
142
+ if (input && typeof input === "object" && !Array.isArray(input)) {
143
+ if (target && typeof target === "object" && !Array.isArray(target)) {
144
+ Object.entries(input).forEach(([key, value]) => {
145
+ if (value != void 0 && value != null) {
146
+ if (prefix) {
147
+ target[`${prefix}.${key}`] = value;
148
+ } else {
149
+ target[key] = value;
150
+ }
151
+ }
152
+ });
153
+ }
154
+ }
155
+ return target;
156
+ }
157
+ function parseList(list, delimiter = ",") {
158
+ if (!list) return [];
159
+ if (Array.isArray(list)) return list.map((s) => String(s).trim()).filter(Boolean);
160
+ if (typeof list === "string") {
161
+ try {
162
+ const parsed = JSON.parse(list);
163
+ if (Array.isArray(parsed)) return parsed.map((s) => String(s).trim()).filter(Boolean);
164
+ } catch {
165
+ }
166
+ return list.split(delimiter).map((s) => s.trim()).filter(Boolean);
167
+ }
168
+ return [];
169
+ }
170
+ function parseNumber(value) {
171
+ if (value === void 0 || value === null) return null;
172
+ if (typeof value === "number") {
173
+ return Number.isFinite(value) ? value : null;
174
+ }
175
+ const parsedValue = String(value).trim();
176
+ if (parsedValue === "" || parsedValue === "undefined" || parsedValue === "null") {
177
+ return null;
178
+ }
179
+ if (!/^[-+]?(?:\d+|\d*\.\d+)$/.test(parsedValue)) {
180
+ return null;
181
+ }
182
+ if (parsedValue.includes(".")) {
183
+ return parseFloat(parsedValue);
184
+ }
185
+ return parseInt(parsedValue, 10);
186
+ }
187
+ function isNumberInRange(value, from = 0, to = 1) {
188
+ value = parseNumber(value);
189
+ if (value == null) {
190
+ return false;
191
+ }
192
+ return !isNaN(value) && value >= from && value <= to;
193
+ }
194
+ function clamp(value, from = 0, to = 1) {
195
+ value = parseNumber(value);
196
+ if (value == null) {
197
+ return from;
198
+ }
199
+ if (isNaN(value)) return from;
200
+ return Math.min(Math.max(value, from), to);
201
+ }
202
+ function parseBoolean(value, fallback = false) {
203
+ if (value === void 0 || value === null) return fallback;
204
+ if (typeof value === "boolean") {
205
+ return value;
206
+ }
207
+ if (typeof value === "string") {
208
+ const parsedValue = value.trim().toLowerCase();
209
+ if (parsedValue === "true" || parsedValue === "1" || parsedValue == "yes") return true;
210
+ return false;
211
+ }
212
+ if (typeof value === "object") {
213
+ if (value instanceof Boolean) {
214
+ return value.valueOf();
215
+ }
216
+ return fallback;
217
+ }
218
+ if (typeof value === "number") {
219
+ if (value > 0) return true;
220
+ }
221
+ try {
222
+ return Boolean(value.trim().toLowerCase());
223
+ } catch {
224
+ }
225
+ return fallback;
226
+ }
227
+
228
+ // src/utils/env.ts
229
+ var JwtSecret = process.env.JWT_SECRET || "secret";
230
+ var JwtRefreshSecret = process.env.JWT_REFRESH_SECRET || "refresh_secret";
231
+ function isDebug(func = void 0) {
232
+ const dbg = process.env.DEBUG;
233
+ if (dbg !== void 0) {
234
+ const v = dbg.toString().trim().toLowerCase();
235
+ let validation = v === "1" || v === "true" || v === "yes" || v === "on";
236
+ if (validation && func != void 0) {
237
+ func();
238
+ }
239
+ return validation;
240
+ }
241
+ return false;
242
+ }
243
+
244
+ // src/utils/encoding.ts
245
+ function encodeToBase64(obj) {
246
+ return btoa(encodeURIComponent(JSON.stringify(obj)));
247
+ }
248
+ function decodeFromBase64(str) {
249
+ if (str == null || str == void 0) {
250
+ return void 0;
251
+ }
252
+ let decoded = str ? decodeURIComponent(atob(str)) : void 0;
253
+ return decoded ? JSON.parse(decoded) : void 0;
254
+ }
255
+
256
+ // src/db/mongodb.ts
257
+ async function connectMongoDB(uri) {
258
+ if (uri == null || uri == void 0 || uri.length == 0) {
259
+ console.error(`\u{1F6D1} [${getCurrentDateString()}] Error: MongoDB URI is not provided`);
260
+ process.exit(EXIT_CODE_INVALID_CONFIG);
261
+ }
262
+ try {
263
+ await mongoose.connect(uri);
264
+ console.log(`\u2705 [${getCurrentDateString()}] MongoDB connected`);
265
+ } catch (err) {
266
+ console.error(`\u{1F6D1} [${getCurrentDateString()}] Error: MongoDB connection failed`, err);
267
+ process.exit(EXIT_CODE_GENERAL_ERROR);
268
+ }
269
+ }
270
+
271
+ // src/middleware/file-storage.ts
272
+ import multer from "multer";
273
+ import path from "node:path";
274
+ import fs from "node:fs";
275
+ var uploadDir = path.resolve(process.cwd(), "src", "files");
276
+ fs.mkdirSync(uploadDir, { recursive: true });
277
+ var sanitizeFilename = (name) => name.replace(/[^a-zA-Z0-9._-]/g, "_");
278
+ var diskStorage = multer.diskStorage({
279
+ destination: (_req, _file, next) => next(null, uploadDir),
280
+ filename: (_req, file, next) => {
281
+ const ext = path.extname(file.originalname);
282
+ const base = path.basename(file.originalname, ext);
283
+ const safe = sanitizeFilename(base);
284
+ const unique = Date.now() + "-" + Math.round(Math.random() * 1e9);
285
+ next(null, `${safe}-${unique}${ext}`);
286
+ }
287
+ });
288
+ var memoryStorage = multer.memoryStorage();
289
+ var diskFileUpload = multer({ storage: diskStorage });
290
+ var memoryFileUpload = multer({ storage: memoryStorage });
291
+
292
+ // src/middleware/logger.ts
293
+ function requestLogger(req, res, next) {
294
+ res.on("finish", () => {
295
+ let emoji = "";
296
+ if (res.statusCode >= 100 && res.statusCode < 200) emoji = "\u{1F4A1}";
297
+ else if (res.statusCode >= 200 && res.statusCode < 300) emoji = "\u2705";
298
+ else if (res.statusCode >= 300 && res.statusCode < 400) emoji = "\u{1F6A6}";
299
+ else if (res.statusCode == 401) emoji = "\u{1F501}";
300
+ else if (res.statusCode >= 400 && res.statusCode < 500) emoji = "\u26A0\uFE0F";
301
+ else if (res.statusCode >= 500) emoji = "\u{1F525}";
302
+ console.log(
303
+ `${emoji} [${getCurrentDateString()}] ${req.method} ${req.originalUrl} - ${res.statusCode}`
304
+ );
305
+ });
306
+ next();
307
+ }
308
+ function errorLogger(err, req, res, next) {
309
+ console.error(`\u{1F6D1} [${getCurrentDateString()}] Error in ${req.method} ${req.originalUrl}:`, err);
310
+ res.status(500).json();
311
+ }
312
+
313
+ // src/middleware/authentication/session.ts
314
+ import express from "express";
315
+ import bcrypt from "bcrypt";
316
+ import jwt2 from "jsonwebtoken";
317
+
318
+ // src/middleware/authentication/validation.ts
319
+ import jwt from "jsonwebtoken";
320
+
321
+ // src/models/auth-user.ts
322
+ import mongoose2 from "mongoose";
323
+ var AuthUserSchema = new mongoose2.Schema(
324
+ {
325
+ identifier: { type: String, unique: true, required: true },
326
+ secretHash: { type: String, select: false },
327
+ blockedSince: { type: Date }
328
+ },
329
+ { collection: "auth_users", timestamps: true }
330
+ );
331
+ var AuthUserModel = mongoose2.model("AuthUser", AuthUserSchema);
332
+
333
+ // src/models/auth-token-blocklist.ts
334
+ import mongoose3 from "mongoose";
335
+ var AuthTokenBlocklistSchema = new mongoose3.Schema(
336
+ {
337
+ jti: { type: String, required: true }
338
+ },
339
+ { collection: "token_blocklist", timestamps: true }
340
+ );
341
+ var AuthTokenBlocklistModel = mongoose3.model(
342
+ "AuthTokenBlocklist",
343
+ AuthTokenBlocklistSchema
344
+ );
345
+
346
+ // src/middleware/authentication/validation.ts
347
+ var FORBIDDEN = 403;
348
+ var UNAUTHORIZED = 401;
349
+ async function jwtRequired(req, res, next) {
350
+ await validateJwt(req, res, next, JwtSecret);
351
+ }
352
+ async function jwtRefreshRequired(req, res, next) {
353
+ await validateJwt(req, res, next, JwtRefreshSecret);
354
+ }
355
+ async function verifyToken(token, jwtSecret) {
356
+ try {
357
+ const payload = jwt.verify(token, jwtSecret, { ignoreExpiration: true });
358
+ const jti = payload?.jti;
359
+ if (!jti) {
360
+ console.log("[WARN] JWT token without jti");
361
+ return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
362
+ }
363
+ const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
364
+ if (existingBlock) {
365
+ console.log("[WARN] JWT token is blocked");
366
+ return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
367
+ }
368
+ const identifier = payload.identifier;
369
+ if (identifier) {
370
+ const user = await AuthUserModel.findOne({ identifier });
371
+ if (!user) {
372
+ console.log("[WARN] JWT token for non-existing user");
373
+ return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
374
+ }
375
+ if (user.blockedSince != void 0) {
376
+ console.log("[WARN] JWT token for blocked user");
377
+ return { isTokenValid: false, isTokenExpired: false, isUserBlocked: true, payload };
378
+ }
379
+ } else {
380
+ console.log("[WARN] JWT token without identifier");
381
+ return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
382
+ }
383
+ const now = Math.floor(Date.now() / 1e3);
384
+ if (payload.exp && payload.exp < now) {
385
+ if (jwtSecret == JwtSecret) {
386
+ console.log("[WARN] Access token expired");
387
+ return { isTokenValid: false, isTokenExpired: true, isUserBlocked: false, payload };
388
+ } else {
389
+ console.log("[WARN] Refresh token expired");
390
+ return { isTokenValid: false, isTokenExpired: false, isUserBlocked: false, payload };
391
+ }
392
+ }
393
+ return { isTokenValid: true, isTokenExpired: false, isUserBlocked: false, payload };
394
+ } catch (err) {
395
+ console.log("[WARN] JWT verification error:", err.message);
396
+ return {
397
+ isTokenValid: false,
398
+ isTokenExpired: false,
399
+ payload: null,
400
+ isUserBlocked: false
401
+ };
402
+ }
403
+ }
404
+ async function validateJwt(req, res, next, jwtSecret) {
405
+ const authHeader = req.headers["authorization"];
406
+ if (!authHeader) return res.sendStatus(FORBIDDEN);
407
+ const token = authHeader.split(" ")[1];
408
+ if (!token) return res.sendStatus(FORBIDDEN);
409
+ try {
410
+ const result = await verifyToken(token, jwtSecret);
411
+ if (result.isTokenExpired) {
412
+ console.log("[WARN] Expired JWT token");
413
+ return res.sendStatus(UNAUTHORIZED);
414
+ }
415
+ if (!result.isTokenValid) {
416
+ console.log("[WARN] Invalid JWT token");
417
+ return res.sendStatus(FORBIDDEN);
418
+ }
419
+ if (result.isUserBlocked) {
420
+ console.log("[WARN] JWT token for blocked user");
421
+ return res.sendStatus(FORBIDDEN);
422
+ }
423
+ res.locals.authUserPayload = result.payload;
424
+ res.locals.token = token;
425
+ next();
426
+ } catch (err) {
427
+ console.log("[ERROR] JWT validation error:", err);
428
+ return res.sendStatus(FORBIDDEN);
429
+ }
430
+ }
431
+ async function socketToken(_socket, _next) {
432
+ const token = _socket.handshake.auth?.token;
433
+ if (!token) {
434
+ return _next();
435
+ }
436
+ try {
437
+ const verificationResult = await verifyToken(token, JwtSecret);
438
+ const isValidChar = verificationResult.isTokenValid && !verificationResult.isTokenExpired ? "\u{1F7E2}" : "\u{1F534}";
439
+ console.log(`${isValidChar} Socket verification result: ${JSON.stringify(verificationResult)}`);
440
+ if (!verificationResult.isTokenValid) {
441
+ if (verificationResult.isTokenExpired) {
442
+ return _next(new Error("expired_token"));
443
+ }
444
+ return _next(new Error("invalid_token"));
445
+ }
446
+ _socket.user = verificationResult.payload;
447
+ _next();
448
+ } catch (err) {
449
+ return _next(new Error("invalid_token"));
450
+ }
451
+ }
452
+
453
+ // src/middleware/authentication/session.ts
454
+ var router = express.Router();
455
+ var FORBIDDEN2 = 403;
456
+ var BAD_REQUEST = 400;
457
+ async function generateCredentials(auth) {
458
+ let jti = void 0;
459
+ let blocked = void 0;
460
+ do {
461
+ jti = crypto.randomUUID();
462
+ blocked = await AuthTokenBlocklistModel.findOne({ jti });
463
+ } while (blocked != void 0);
464
+ const user = await AuthUserModel.findOne({ identifier: auth.identifier }).lean();
465
+ if (!user) {
466
+ return void 0;
467
+ }
468
+ let appUser = void 0;
469
+ try {
470
+ appUser = encodeToBase64(user);
471
+ } catch (error) {
472
+ }
473
+ if (!appUser) {
474
+ return void 0;
475
+ }
476
+ let payload = {
477
+ identifier: auth.identifier,
478
+ jti
479
+ };
480
+ const accessToken = jwt2.sign(payload, JwtSecret, { expiresIn: "1h" });
481
+ const refreshToken = jwt2.sign(payload, JwtRefreshSecret, { expiresIn: "7d" });
482
+ return {
483
+ accessToken,
484
+ refreshToken,
485
+ appUser
486
+ };
487
+ }
488
+ async function authRegister(req, res, next) {
489
+ let { identifier = void 0, secret = void 0 } = req.body || {};
490
+ if (!identifier || !secret) {
491
+ res.sendStatus(FORBIDDEN2);
492
+ return;
493
+ }
494
+ identifier = identifier.toLowerCase();
495
+ let user = await AuthUserModel.findOne({ identifier });
496
+ if (user) {
497
+ res.sendStatus(BAD_REQUEST);
498
+ return;
499
+ }
500
+ res.locals.authUser = new AuthUserModel({
501
+ identifier,
502
+ secretHash: await bcrypt.hash(secret, 10)
503
+ });
504
+ next();
505
+ }
506
+ async function authLogin(req, res, next) {
507
+ let { identifier = void 0, secret = void 0 } = req.body || {};
508
+ if (!identifier || !secret) {
509
+ res.sendStatus(FORBIDDEN2);
510
+ return;
511
+ }
512
+ identifier = identifier.toLowerCase();
513
+ let user = await AuthUserModel.findOne({ identifier }).select("+secretHash");
514
+ if (!user || !user.secretHash) {
515
+ res.sendStatus(BAD_REQUEST);
516
+ return;
517
+ }
518
+ if (user.blockedSince) {
519
+ res.sendStatus(FORBIDDEN2);
520
+ return;
521
+ }
522
+ let isMatch = await bcrypt.compare(secret, user.secretHash);
523
+ if (!isMatch) {
524
+ res.sendStatus(BAD_REQUEST);
525
+ return;
526
+ }
527
+ let credentials = await generateCredentials(user);
528
+ if (!credentials) {
529
+ res.sendStatus(BAD_REQUEST);
530
+ return;
531
+ }
532
+ res.locals.credentials = credentials;
533
+ res.locals.authUser = user;
534
+ next();
535
+ }
536
+ async function authLogout(req, res, next) {
537
+ await jwtRefreshRequired(req, res, async () => {
538
+ let authUserPayload = res.locals.authUserPayload;
539
+ let user = await AuthUserModel.findOne({ identifier: authUserPayload.identifier }).select(
540
+ "+secretHash"
541
+ );
542
+ if (!user || !user.secretHash) {
543
+ res.sendStatus(BAD_REQUEST);
544
+ return;
545
+ }
546
+ const refreshToken = res.locals.token;
547
+ if (!refreshToken) {
548
+ res.sendStatus(BAD_REQUEST);
549
+ return;
550
+ }
551
+ const decoded = jwt2.decode(refreshToken);
552
+ const jti = decoded?.jti;
553
+ if (jti) {
554
+ const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
555
+ if (!existingBlock) {
556
+ await new AuthTokenBlocklistModel({ jti }).save();
557
+ }
558
+ }
559
+ let accessToken = validateString(req.body?.access_token);
560
+ if (accessToken) {
561
+ const accessTokenDecoded = jwt2.decode(accessToken);
562
+ let accessTokenJti = accessTokenDecoded?.jti;
563
+ if (accessTokenJti) {
564
+ const existing = await AuthTokenBlocklistModel.findOne({ jti: accessTokenJti });
565
+ if (!existing) {
566
+ await new AuthTokenBlocklistModel({ jti: accessTokenJti }).save();
567
+ }
568
+ }
569
+ }
570
+ res.locals.authUser = user;
571
+ next();
572
+ });
573
+ }
574
+ async function authRefresh(req, res, next) {
575
+ await jwtRefreshRequired(req, res, async () => {
576
+ let authUserPayload = res.locals.authUserPayload;
577
+ let user = await AuthUserModel.findOne({ identifier: authUserPayload.identifier }).select(
578
+ "+secretHash"
579
+ );
580
+ if (!user || !user.secretHash) {
581
+ res.sendStatus(BAD_REQUEST);
582
+ return;
583
+ }
584
+ const refreshToken = res.locals.token;
585
+ if (!refreshToken) {
586
+ res.sendStatus(BAD_REQUEST);
587
+ return;
588
+ }
589
+ try {
590
+ const decoded = jwt2.decode(refreshToken);
591
+ const jti = decoded?.jti;
592
+ if (jti) {
593
+ const existingBlock = await AuthTokenBlocklistModel.findOne({ jti });
594
+ if (!existingBlock) {
595
+ await new AuthTokenBlocklistModel({ jti }).save();
596
+ }
597
+ }
598
+ let accessToken = validateString(req.body?.access_token);
599
+ if (accessToken) {
600
+ const accessTokenDecoded = jwt2.decode(accessToken);
601
+ let accessTokenJti = accessTokenDecoded?.jti;
602
+ if (accessTokenJti) {
603
+ const existing = await AuthTokenBlocklistModel.findOne({
604
+ jti: accessTokenJti
605
+ });
606
+ if (!existing) {
607
+ await new AuthTokenBlocklistModel({ jti: accessTokenJti }).save();
608
+ }
609
+ }
610
+ }
611
+ const payload = jwt2.verify(refreshToken, JwtRefreshSecret);
612
+ let credentials = await generateCredentials(payload);
613
+ if (!credentials) {
614
+ res.sendStatus(BAD_REQUEST);
615
+ return;
616
+ }
617
+ res.locals.authUser = user;
618
+ res.locals.credentials = credentials;
619
+ next();
620
+ } catch (err) {
621
+ console.log("[WARN] refreshing JWT:", err);
622
+ res.sendStatus(BAD_REQUEST);
623
+ return;
624
+ }
625
+ });
626
+ }
627
+
628
+ // src/fluent-interface/fluent-pattern-handler.ts
629
+ import mongoose5 from "mongoose";
630
+
631
+ // src/fluent-interface/types.ts
632
+ var fluentRequestQueryAttributes = {
633
+ id: "",
634
+ ids: "",
635
+ filter: "",
636
+ offset: "",
637
+ limit: "",
638
+ order: "",
639
+ excluded: ""
640
+ };
641
+
642
+ // src/core/query-builder.ts
643
+ import mongoose4 from "mongoose";
644
+
645
+ // src/core/types.ts
646
+ var Codec = class {
647
+ constructor(content, code = 202) {
648
+ this.content = content;
649
+ this.returnCode = code;
650
+ }
651
+ sendToClient(res) {
652
+ if (this.content == null) {
653
+ res.status(404).json({});
654
+ }
655
+ res.status(this.returnCode).json(this.content);
656
+ }
657
+ is1xx() {
658
+ return this.returnCode >= 100 && this.returnCode <= 199;
659
+ }
660
+ is2xx() {
661
+ return this.returnCode >= 200 && this.returnCode <= 299;
662
+ }
663
+ is3xx() {
664
+ return this.returnCode >= 300 && this.returnCode <= 399;
665
+ }
666
+ is4xx() {
667
+ return this.returnCode >= 400 && this.returnCode <= 499;
668
+ }
669
+ };
670
+ var JoinRelation = class {
671
+ constructor(localField, ref, alias = void 0) {
672
+ this.ref = ref;
673
+ this.localField = localField;
674
+ this.alias = alias ? alias : ref.collection.name;
675
+ }
676
+ };
677
+ var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
678
+ HttpMethod2["POST"] = "post";
679
+ HttpMethod2["GET"] = "get";
680
+ HttpMethod2["DELETE"] = "delete";
681
+ HttpMethod2["PUT"] = "put";
682
+ return HttpMethod2;
683
+ })(HttpMethod || {});
684
+
685
+ // src/core/query-builder.ts
686
+ var QueryBuilder = class {
687
+ constructor(config) {
688
+ this._matchConditions = {};
689
+ this._stages = [];
690
+ this._relations = [];
691
+ this._unsetFields = [];
692
+ this._config = config;
693
+ this._applyPathOptions();
694
+ }
695
+ /**
696
+ * Generates the aggregation pipeline based on the current configuration of the QueryBuilder.
697
+ * @returns
698
+ */
699
+ getAggregationPipeline() {
700
+ return this._generatePipeline();
701
+ }
702
+ /**
703
+ * Returns the current configuration of the QueryBuilder, including model, select fields, and any applied options.
704
+ * @returns
705
+ */
706
+ getConfig() {
707
+ return this._config;
708
+ }
709
+ /**
710
+ * Applies schema-based options such as joins and unset fields.
711
+ */
712
+ _applyPathOptions() {
713
+ this._generateSchemaJoins(this._config.model.schema);
714
+ this._generateSchemaUnsetList(this._config);
715
+ }
716
+ /**
717
+ * Adds match conditions to the query builder.
718
+ * @param match - The match conditions to add.
719
+ * @param conjunction - The logical conjunction ('and' or 'or').
720
+ * @param append - Whether to append to existing conditions or replace them.
721
+ */
722
+ match(match, conjunction = "and", append = true) {
723
+ if (!match || (Array.isArray(match) ? match.length === 0 : Object.keys(match).length === 0))
724
+ return;
725
+ if (!append) {
726
+ this._matchConditions = Array.isArray(match) ? { $and: match } : match;
727
+ return;
728
+ }
729
+ const key = `$${conjunction}`;
730
+ if (!this._matchConditions[key]) this._matchConditions[key] = [];
731
+ if (Array.isArray(match)) {
732
+ this._matchConditions[key].push(...match);
733
+ } else {
734
+ this._matchConditions[key].push(match);
735
+ }
736
+ }
737
+ /**
738
+ * Adds aggregation stages to the query builder.
739
+ * @param stages
740
+ * @returns
741
+ */
742
+ stage(stages) {
743
+ if (!stages) return;
744
+ if (Array.isArray(stages)) {
745
+ this._stages.push(...globalThis.structuredClone(stages));
746
+ } else {
747
+ this._stages.push(globalThis.structuredClone(stages));
748
+ }
749
+ }
750
+ /**
751
+ * Adds join relations to the query builder.
752
+ * @param rels
753
+ * @returns
754
+ */
755
+ join(rels) {
756
+ if (!rels) return;
757
+ const list = Array.isArray(rels) ? rels : [rels];
758
+ list.forEach((rel) => this._relations.push(rel));
759
+ }
760
+ /**
761
+ * Calculates the $lookup stage for a given JoinRelation.
762
+ * @param relation
763
+ * @returns
764
+ */
765
+ _calculateJoin(relation) {
766
+ if (!(relation instanceof JoinRelation)) throw new Error("relation must be JoinRelation");
767
+ return {
768
+ $lookup: {
769
+ from: relation.ref.collection.name,
770
+ localField: relation.localField,
771
+ foreignField: "_id",
772
+ as: relation.alias
773
+ }
774
+ };
775
+ }
776
+ /**
777
+ * Automatically generates JoinRelation objects from schema refs.
778
+ * @param model The Mongoose model to scan.
779
+ * @param prefix Optional prefix for nested paths (e.g., 'alias.field').
780
+ * @returns Array of JoinRelation objects.
781
+ */
782
+ _generateSchemaJoins(schema, prefix = "") {
783
+ schema.eachPath((path2, schematype) => {
784
+ const fullPath = prefix ? `${prefix}.${path2}` : path2;
785
+ if (schematype.options?.ref) {
786
+ const refModelName = schematype.options.ref;
787
+ try {
788
+ const refModel = mongoose4.model(refModelName);
789
+ let alias = schematype.options?.alias ?? refModel.collection.name;
790
+ this.join(new JoinRelation(fullPath, refModel, alias));
791
+ } catch (err) {
792
+ console.warn(
793
+ `[QueryBuilder] Could not resolve ref model '${refModelName}' for path '${fullPath}'`
794
+ );
795
+ }
796
+ }
797
+ if (schematype instanceof mongoose4.Schema.Types.Array && schematype.caster?.options?.ref) {
798
+ const refModelName = schematype.caster.options.ref;
799
+ try {
800
+ const refModel = mongoose4.model(refModelName);
801
+ let alias = schematype.caster.options?.alias ?? refModel.collection.name;
802
+ this.join(new JoinRelation(fullPath, refModel, alias));
803
+ } catch (err) {
804
+ console.warn(
805
+ `[QueryBuilder] Could not resolve array ref model '${refModelName}' for path '${fullPath}'`
806
+ );
807
+ }
808
+ }
809
+ });
810
+ }
811
+ /**
812
+ * Generates the list of fields to unset based on schema select options.
813
+ * @param config - The query request configuration.
814
+ */
815
+ _generateSchemaUnsetList(config) {
816
+ this._unsetFields = [];
817
+ let unset = this._collectSelectFalse(config.model.schema, void 0, config.select);
818
+ for (const relation of this._relations) {
819
+ unset = unset.concat(
820
+ this._collectSelectFalse(relation.ref.schema, relation.alias, config.select)
821
+ );
822
+ }
823
+ this._unsetFields = Array.from(new Set(unset));
824
+ }
825
+ /**
826
+ * Collects paths from the schema where select is set to false.
827
+ * @param schema - The Mongoose schema to scan.
828
+ * @param prefix - Optional prefix for nested paths.
829
+ * @param select - Optional array of fields to select.
830
+ * @returns Array of paths to unset.
831
+ */
832
+ _collectSelectFalse(schema, prefix = void 0, select = void 0) {
833
+ const unset = [];
834
+ schema.eachPath((path2, schematype) => {
835
+ if (select && select.length > 0 && !select.includes(path2) && schematype?.options?.select !== false) {
836
+ return;
837
+ }
838
+ if (schematype?.options?.select === false) {
839
+ unset.push(prefix ? `${prefix}.${path2}` : path2);
840
+ }
841
+ });
842
+ return unset;
843
+ }
844
+ /**
845
+ * Generates the aggregation pipeline based on joins, stages, match conditions, and unset fields.
846
+ * @returns The aggregation pipeline array.
847
+ */
848
+ _generatePipeline() {
849
+ const pipeline = [];
850
+ for (const rel of this._relations) {
851
+ pipeline.push(this._calculateJoin(rel));
852
+ }
853
+ pipeline.push(...this._stages);
854
+ if (Object.keys(this._matchConditions).length) {
855
+ pipeline.push({ $match: this._matchConditions });
856
+ }
857
+ if (this._unsetFields.length) {
858
+ pipeline.push({ $unset: this._unsetFields });
859
+ }
860
+ return pipeline;
861
+ }
862
+ /**
863
+ * Executes the aggregation pipeline and returns the results.
864
+ * @param config - Parameters for the query execution.
865
+ * @returns The collection response wrapped in a Codec.
866
+ */
867
+ async exec(config) {
868
+ try {
869
+ const pipeline = this._generatePipeline();
870
+ const countPipeline = [...pipeline, { $count: "n" }];
871
+ const queryPipeline = [...pipeline];
872
+ if (config && !config.isOne) {
873
+ if (config.skip) queryPipeline.push({ $skip: config.skip });
874
+ if (config.limit) queryPipeline.push({ $limit: config.limit });
875
+ }
876
+ const [countRes, res] = await Promise.all([
877
+ this._config.model.aggregate(countPipeline).exec(),
878
+ this._config.model.aggregate(queryPipeline).exec()
879
+ ]);
880
+ const totalCount = countRes && countRes[0] ? countRes[0].n : 0;
881
+ const documents = config && config.isOne ? await this._processSingleDocument(res) : await this._processMultipleDocuments(res);
882
+ return new Codec({ data: documents, meta: { total: totalCount } }, 200);
883
+ } catch (err) {
884
+ console.error("[ERROR - QueryBuilder]", err);
885
+ return new Codec({ data: [], meta: { total: 0 } }, 500);
886
+ }
887
+ }
888
+ /**
889
+ * Processes a single document from the aggregation result.
890
+ * @param res - The aggregation result array.
891
+ * @returns The processed document or null if none.
892
+ */
893
+ async _processSingleDocument(res) {
894
+ if (!res || res.length === 0) {
895
+ return null;
896
+ }
897
+ let doc = this._config.model.hydrate(res[0]);
898
+ if (this._config.eachFunc) {
899
+ doc = this._config.eachFunc(doc);
900
+ } else if (this._config.asyncEachFunc) {
901
+ doc = await this._config.asyncEachFunc(doc);
902
+ }
903
+ return doc;
904
+ }
905
+ /**
906
+ * Processes multiple documents from the aggregation result.
907
+ * @param res - The aggregation result array.
908
+ * @returns The array of processed documents.
909
+ */
910
+ async _processMultipleDocuments(res) {
911
+ let final = (res || []).map((doc) => this._config.model.hydrate(doc));
912
+ if (this._config.eachFunc) {
913
+ final = final.map(this._config.eachFunc);
914
+ } else if (this._config.asyncEachFunc) {
915
+ const asyncFinal = [];
916
+ for (const doc of final) {
917
+ const newDoc = await this._config.asyncEachFunc(doc);
918
+ asyncFinal.push(newDoc);
919
+ }
920
+ final = asyncFinal;
921
+ }
922
+ return final;
923
+ }
924
+ };
925
+
926
+ // src/core/http.ts
927
+ function formDataParser(obj) {
928
+ return new URLSearchParams(obj).toString();
929
+ }
930
+ async function httpRequest(config) {
931
+ const url = new URL(config.url);
932
+ if (config.urlSearchParams) {
933
+ Object.keys(config.urlSearchParams).forEach((key) => {
934
+ url.searchParams.set(key, String(config.urlSearchParams[key]));
935
+ });
936
+ }
937
+ let body;
938
+ let defaultContentType = "application/x-www-form-urlencoded";
939
+ if (config.body) {
940
+ if (typeof config.body === "object") {
941
+ body = JSON.stringify(config.body);
942
+ defaultContentType = "application/json";
943
+ } else {
944
+ body = String(config.body);
945
+ }
946
+ }
947
+ const fetchImpl = globalThis.fetch;
948
+ if (!fetchImpl) {
949
+ throw new Error("fetch() is not available. Please use Node.js 18+ or add a fetch polyfill.");
950
+ }
951
+ const defaultHeaders = {};
952
+ if (!config.headers || !Object.keys(config.headers).find((key) => key.toLowerCase() === "content-type")) {
953
+ defaultHeaders["Content-Type"] = defaultContentType;
954
+ }
955
+ const response = await fetchImpl(url.toString(), {
956
+ method: config.method?.toUpperCase() || "GET",
957
+ body,
958
+ headers: {
959
+ ...defaultHeaders,
960
+ ...config.headers
961
+ }
962
+ });
963
+ const contentType = response.headers.get("content-type") || "";
964
+ const isJson = contentType.includes("application/json");
965
+ const isBlob = contentType.includes("application/octet-stream");
966
+ if (!response.ok) {
967
+ const text = await response.text().catch(() => "");
968
+ throw new Error(`HTTP ${response.status} ${response.statusText}: ${text}`);
969
+ }
970
+ let result;
971
+ if (isJson) {
972
+ result = await response.json().catch(async (error) => {
973
+ throw new Error(`Failed to parse JSON response: ${error.message}`);
974
+ });
975
+ } else if (isBlob) {
976
+ result = await response.blob().catch(async (error) => {
977
+ throw new Error(`Failed to get blob response: ${error.message}`);
978
+ });
979
+ } else {
980
+ result = await response.text().catch(async (error) => {
981
+ throw new Error(`Failed to get text response: ${error.message}`);
982
+ });
983
+ }
984
+ return result;
985
+ }
986
+
987
+ // src/fluent-interface/fluent-pattern-handler.ts
988
+ var FluentPatternHandler = class _FluentPatternHandler {
989
+ /**
990
+ * Constructor for FluentPatternHandler.
991
+ * @param execMiddleware - Optional array of middleware functions to be executed before the main query execution in the exec method.
992
+ */
993
+ constructor(execMiddleware = []) {
994
+ /**
995
+ * Array of middleware functions to be executed before the main query execution in the exec method. Each function receives the execution parameters and can modify them as needed.
996
+ */
997
+ this._execMiddlewareFunctions = [];
998
+ if (_FluentPatternHandler._singleton) {
999
+ throw new Error(
1000
+ "FluentPatternHandler is a singleton class. Use FluentPatternHandler.getInstance() to access the instance."
1001
+ );
1002
+ }
1003
+ this._execMiddlewareFunctions = execMiddleware;
1004
+ _FluentPatternHandler._singleton = this;
1005
+ }
1006
+ /**
1007
+ * Initializes the singleton instance of FluentPatternHandler with the provided options.
1008
+ * @param execMiddleware - Optional array of middleware functions to be executed before the main query execution in the exec method.
1009
+ * @returns Singleton instance of FluentPatternHandler.
1010
+ */
1011
+ static init(execMiddleware = []) {
1012
+ if (_FluentPatternHandler._singleton != void 0) {
1013
+ throw new Error("FluentPatternHandler is already initialized");
1014
+ }
1015
+ _FluentPatternHandler._singleton = new _FluentPatternHandler(execMiddleware);
1016
+ return _FluentPatternHandler._singleton;
1017
+ }
1018
+ /**
1019
+ * Returns the singleton instance of FluentPatternHandler.
1020
+ * @returns Singleton instance of FluentPatternHandler.
1021
+ */
1022
+ static getInstance() {
1023
+ if (_FluentPatternHandler._singleton == void 0) {
1024
+ throw new Error(
1025
+ "FluentPatternHandler instance has not been created yet. Please create an instance before calling getInstance()."
1026
+ );
1027
+ }
1028
+ return _FluentPatternHandler._singleton;
1029
+ }
1030
+ /**
1031
+ * Parses and validates query parameters from the request.
1032
+ * @param query - The query object from the request.
1033
+ * @returns Parsed query parameters.
1034
+ */
1035
+ _parseFluentRequestQuery(query) {
1036
+ const { filter, limit = "5", offset = "0", id, excluded: excludedJSON, ids: idsJSON } = query;
1037
+ const queryKeys = Object.keys(fluentRequestQueryAttributes);
1038
+ let filterFields = {};
1039
+ for (const [key, value] of Object.entries(query)) {
1040
+ if (queryKeys.includes(key)) continue;
1041
+ if (value == void 0) continue;
1042
+ try {
1043
+ filterFields[key] = typeof value === "string" ? value : JSON.stringify(value);
1044
+ } catch (e) {
1045
+ filterFields[key] = String(value);
1046
+ }
1047
+ }
1048
+ let excluded;
1049
+ if (excludedJSON) {
1050
+ try {
1051
+ excluded = JSON.parse(excludedJSON);
1052
+ if (!Array.isArray(excluded)) throw new Error("Excluded must be an array");
1053
+ } catch (error) {
1054
+ console.warn("[FluentPatternHandler] Invalid excluded parameter:", error);
1055
+ }
1056
+ }
1057
+ let ids;
1058
+ if (idsJSON) {
1059
+ try {
1060
+ ids = JSON.parse(idsJSON);
1061
+ if (!Array.isArray(ids)) throw new Error("Ids must be an array");
1062
+ } catch (error) {
1063
+ console.warn("[FluentPatternHandler] Invalid ids parameter:", error);
1064
+ }
1065
+ }
1066
+ return { filter, limit, offset, id, excluded, ids, filterFields };
1067
+ }
1068
+ /**
1069
+ * Applies filters to the query builder based on parsed parameters and options.
1070
+ * @param queryBuilder - The QueryBuilder instance.
1071
+ * @param params - Parsed query parameters.
1072
+ * @param config - Query request configuration.
1073
+ */
1074
+ _applyParameters(queryBuilder, params) {
1075
+ const { id, ids, filter, excluded } = params;
1076
+ if (id) {
1077
+ queryBuilder.match({ _id: new mongoose5.Types.ObjectId(id) });
1078
+ } else if (ids && ids.length > 0) {
1079
+ const objectIds = ids.map((id2) => new mongoose5.Types.ObjectId(id2));
1080
+ queryBuilder.match({ _id: { $in: objectIds } });
1081
+ } else {
1082
+ const modelFilterFields = this._getFilterFieldsForModel(queryBuilder.getConfig().model);
1083
+ if (filter && modelFilterFields.length > 0) {
1084
+ const ors = modelFilterFields.map((field) => ({
1085
+ [field]: { $regex: filter, $options: "i" }
1086
+ }));
1087
+ queryBuilder.match({ $or: ors });
1088
+ }
1089
+ if (params.filterFields && Object.keys(params.filterFields).length > 0) {
1090
+ for (const [field, value] of Object.entries(params.filterFields)) {
1091
+ if (!modelFilterFields.includes(field)) {
1092
+ continue;
1093
+ }
1094
+ const match = {};
1095
+ match[field] = { $regex: value, $options: "i" };
1096
+ queryBuilder.match(match);
1097
+ }
1098
+ }
1099
+ if (excluded && excluded.length > 0) {
1100
+ const objectIds = excluded.map((id2) => new mongoose5.Types.ObjectId(id2));
1101
+ queryBuilder.match({ _id: { $nin: objectIds } });
1102
+ }
1103
+ }
1104
+ }
1105
+ /**
1106
+ * Generates filter fields for the given Mongoose model based on schema options.
1107
+ * @param model - The Mongoose model.
1108
+ * @returns Array of filter fields.
1109
+ */
1110
+ _getFilterFieldsForModel(model) {
1111
+ let filterFields = [];
1112
+ let schema = model.schema;
1113
+ schema.eachPath((path2, type) => {
1114
+ if (type?.options?.filter == true) {
1115
+ filterFields.push(path2);
1116
+ }
1117
+ });
1118
+ return filterFields;
1119
+ }
1120
+ /**
1121
+ * Builds execution parameters for the query builder.
1122
+ * @param params - Parsed query parameters.
1123
+ * @returns Execution parameters.
1124
+ */
1125
+ _buildExecutionConfig(params) {
1126
+ const { id, limit, offset } = params;
1127
+ return {
1128
+ isOne: Boolean(id),
1129
+ limit: limit === "full" ? void 0 : parseInt(limit || "5", 10),
1130
+ skip: parseInt(offset || "0", 10)
1131
+ };
1132
+ }
1133
+ /**
1134
+ * Executes the query builder with applied filters and returns the result.
1135
+ * @param params Execution parameters including the query builder and request query.
1136
+ * @returns
1137
+ */
1138
+ async exec(params) {
1139
+ try {
1140
+ if (this._execMiddlewareFunctions && this._execMiddlewareFunctions.length > 0) {
1141
+ for (const func of this._execMiddlewareFunctions) {
1142
+ await func(params);
1143
+ }
1144
+ }
1145
+ const queryParams = this._parseFluentRequestQuery(params.req.query);
1146
+ this._applyParameters(params.queryBuilder, queryParams);
1147
+ const execConfig = this._buildExecutionConfig(queryParams);
1148
+ return await params.queryBuilder.exec(execConfig);
1149
+ } catch (err) {
1150
+ console.error("[ERROR - FluentPatternHandler]", err);
1151
+ return new Codec({ data: [], meta: { total: 0 } }, 500);
1152
+ }
1153
+ }
1154
+ };
1155
+
1156
+ // src/app.ts
1157
+ import express2 from "express";
1158
+ import cors from "cors";
1159
+ function createApp() {
1160
+ const app = express2();
1161
+ app.use(cors());
1162
+ app.use(express2.json());
1163
+ app.use(express2.urlencoded({ extended: true }));
1164
+ app.use(requestLogger);
1165
+ app.use(errorLogger);
1166
+ return app;
1167
+ }
1168
+
1169
+ // bin/repl.ts
1170
+ import { start } from "node:repl";
1171
+ global.tsledge = src_exports;
1172
+ console.log("------------------------------------------");
1173
+ console.log("\u{1F4A1} TSledge scope is available with 'global.tsledge'");
1174
+ console.log(" (To exit, press Ctrl+C again or Ctrl+D or type .exit)");
1175
+ console.log("------------------------------------------");
1176
+ var session = start({
1177
+ prompt: "tsledge > ",
1178
+ useGlobal: true
1179
+ // Wichtig, damit das globale 'tsledge' direkt erkannt wird
1180
+ });
1181
+ //# sourceMappingURL=repl.js.map