@gerync/utils 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 @gerync
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,385 @@
1
+ # @gerync/utils
2
+
3
+ A collection of utilities for any type of project, providing logging, error handling, configuration management, and object manipulation helpers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @gerync/utils
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **Colored Logging**: Console logging with hex, RGB, and named color support
14
+ - **Error Handler Middleware**: Express-compatible error handler with multi-language support
15
+ - **Configuration Manager**: Dynamic setup for preferences and localized response messages
16
+ - **Object Utilities**: Helper functions for object key validation and inspection
17
+
18
+ ---
19
+
20
+ ## API Reference
21
+
22
+ ### 1. coloredlog
23
+
24
+ Log messages to the console with color support. Accepts hex, RGB, or CSS named colors.
25
+
26
+ **Import:**
27
+ ```typescript
28
+ import utils from '@gerync/utils';
29
+ utils.coloredlog(message, color);
30
+ ```
31
+
32
+ **Usage:**
33
+ ```typescript
34
+ // Named colors
35
+ utils.coloredlog('This is an error', 'red');
36
+ utils.coloredlog('This is a warning', 'orange');
37
+ utils.coloredlog('Success!', 'green');
38
+
39
+ // Hex colors
40
+ utils.coloredlog('Custom color', '#FF5733');
41
+ utils.coloredlog('Another shade', '#FF5733CC'); // with alpha
42
+
43
+ // RGB colors
44
+ utils.coloredlog('RGB color', 'rgb(255, 87, 51)');
45
+ utils.coloredlog('RGBA with alpha', 'rgba(255, 87, 51, 0.8)');
46
+
47
+ // If color is invalid, logs without styling
48
+ utils.coloredlog('No styling', 'invalid-color');
49
+ ```
50
+
51
+ **Parameters:**
52
+ - `message` (string): The text to log
53
+ - `color` (string): A valid hex (`#XXX`, `#XXXX`, `#XXXXXX`, `#XXXXXXXX`), RGB (`rgb(r, g, b)`), RGBA (`rgba(r, g, b, a)`), or CSS named color
54
+
55
+ ---
56
+
57
+ ### 2. config
58
+
59
+ Manage application preferences and localized response messages.
60
+
61
+ **Import:**
62
+ ```typescript
63
+ import utils from '@gerync/utils';
64
+ utils.configure({ ... });
65
+ ```
66
+
67
+ **Usage:**
68
+
69
+ #### Initial Setup
70
+
71
+ ```typescript
72
+ import utils from '@gerync/utils';
73
+
74
+ utils.configure({
75
+ prefs: {
76
+ acceptedLanguages: ['en', 'fr', 'de'],
77
+ noDupesAllowedof: ['email', 'username', 'phone']
78
+ },
79
+ responses: {
80
+ en: {
81
+ dupes: {
82
+ email: 'Email already exists.',
83
+ username: 'Username is taken.',
84
+ phone: 'Phone number is already in use.'
85
+ },
86
+ general: {
87
+ error: 'An error occurred. Please try again later.'
88
+ }
89
+ },
90
+ fr: {
91
+ dupes: {
92
+ email: 'Cet email existe déjà.',
93
+ username: 'Ce nom d\'utilisateur est pris.',
94
+ phone: 'Ce numéro est déjà utilisé.'
95
+ },
96
+ general: {
97
+ error: 'Une erreur est survenue. Veuillez réessayer.'
98
+ }
99
+ }
100
+ }
101
+ });
102
+ ```
103
+
104
+ #### Retrieve Configuration
105
+
106
+ ```typescript
107
+ const responses = utils.config.getResponses();
108
+ const prefs = utils.config.getPrefs();
109
+
110
+ console.log(responses['en'].dupes.email); // "Email already exists."
111
+ console.log(prefs.acceptedLanguages); // ['en', 'fr', 'de']
112
+ ```
113
+
114
+ **Methods:**
115
+ - `configure(options)`: Set preferences and responses at runtime
116
+ - `getResponses()`: Return the current response messages object
117
+ - `getPrefs()`: Return the current preferences object
118
+
119
+ ---
120
+
121
+ ### 3. errorHandler
122
+
123
+ Express middleware for centralized error handling with multi-language support, smart logging, and user-friendly messages.
124
+
125
+ **Import:**
126
+ ```typescript
127
+ import utils from '@gerync/utils';
128
+ app.use(utils.errorHandler);
129
+ ```
130
+
131
+ **Setup:**
132
+
133
+ ```typescript
134
+ import express from 'express';
135
+ import utils from '@gerync/utils';
136
+
137
+ const app = express();
138
+
139
+ // Configure responses and preferences BEFORE using the middleware
140
+ utils.configure({
141
+ prefs: {
142
+ acceptedLanguages: ['en', 'hu', 'es'],
143
+ noDupesAllowedof: ['email', 'username', 'phone']
144
+ },
145
+ responses: {
146
+ en: {
147
+ dupes: {
148
+ email: 'Email address already exists.',
149
+ username: 'Username is already taken.',
150
+ phone: 'Phone number is already in use.'
151
+ },
152
+ general: {
153
+ error: 'An internal server error occurred.'
154
+ }
155
+ }
156
+ }
157
+ });
158
+
159
+ // Use the middleware at the end
160
+ app.use(utils.errorHandler);
161
+ ```
162
+
163
+ **Features:**
164
+
165
+ - **Language Detection**: Detects language from `req.lang`, `req.language`, or `Accept-Language` header
166
+ - **User-Friendly Responses**: Returns localized messages to clients based on their language
167
+ - **Smart Logging**: Only logs detailed error reports for server-side issues (5xx), not user errors (4xx)
168
+ - **Multi-Colored Console Output**: Color-coded error reports for easy debugging
169
+ - **Automatic Error Classification**: Recognizes user-caused errors (validation, duplicates, etc.)
170
+
171
+ **Error Response Format:**
172
+
173
+ ```json
174
+ {
175
+ "status": "error",
176
+ "code": "ER_DUP_ENTRY",
177
+ "message": "Email address already exists."
178
+ }
179
+ ```
180
+
181
+ **Console Output (Server Errors Only):**
182
+
183
+ ```
184
+ ===================== ERROR REPORT =====================
185
+ Time: 2025-12-29 19:52:09
186
+ Route: POST /api/users
187
+ RequestId: abc123def456
188
+ Status: 500
189
+ Code: DATABASE_ERROR
190
+ UserMessage: An internal server error occurred.
191
+ RawMessage: Connection timeout
192
+ Stack: Error: Connection timeout
193
+ at Database.query (...)
194
+ at ...
195
+ ========================================================
196
+ ```
197
+
198
+ **Supported Error Codes:**
199
+
200
+ User-caused (4xx):
201
+ - `BAD_REQUEST`: Malformed request
202
+ - `UNAUTHORIZED`: Auth required
203
+ - `FORBIDDEN`: Permission denied
204
+ - `NOT_FOUND`: Resource not found
205
+ - `CONFLICT`: Resource conflict
206
+ - `METHOD_NOT_ALLOWED`
207
+ - `NOT_ACCEPTABLE`
208
+ - `UNSUPPORTED_MEDIA_TYPE`
209
+ - `PAYLOAD_TOO_LARGE`
210
+ - `UNPROCESSABLE_ENTITY`
211
+ - `TOO_MANY_REQUESTS`
212
+ - `VALIDATION_ERROR`: User input validation failure
213
+ - `ER_DUP_ENTRY`: Duplicate database entry
214
+
215
+ Server-side (5xx):
216
+ - `INTERNAL_SERVER_ERROR`: Unhandled error
217
+ - `DATABASE_ERROR`: Database operation failed
218
+ - `SERVICE_UNAVAILABLE`: Temporary service outage
219
+ - `BAD_GATEWAY`
220
+ - `GATEWAY_TIMEOUT`
221
+ - `NOT_IMPLEMENTED`
222
+ - `NETWORK_ERROR`
223
+ - `TIMEOUT`
224
+
225
+ ---
226
+
227
+ ### 4. object utilities
228
+
229
+ Helper functions for validating and inspecting object keys.
230
+
231
+ **Import:**
232
+ ```typescript
233
+ import utils from '@gerync/utils';
234
+ const { Keysamount, KeysInRange, KeysInRangeDetailed, AllowedKeys } = utils.object;
235
+ ```
236
+
237
+ #### Keysamount
238
+ Get the number of keys in an object.
239
+
240
+ ```typescript
241
+ const user = { name: 'John', email: 'john@example.com', age: 30 };
242
+ const count = utils.object.Keysamount(user); // 3
243
+ ```
244
+
245
+ #### KeysInRange
246
+ Check if an object has a key count within a specified range.
247
+
248
+ ```typescript
249
+ const user = { name: 'John', email: 'john@example.com', age: 30 };
250
+
251
+ // Exact count
252
+ utils.object.KeysInRange(user, 3); // true
253
+ utils.object.KeysInRange(user, 2); // false
254
+
255
+ // Range check
256
+ utils.object.KeysInRange(user, 2, 5); // true (3 is between 2 and 5)
257
+ utils.object.KeysInRange(user, 4, 10); // false (3 is not between 4 and 10)
258
+ ```
259
+
260
+ #### KeysInRangeDetailed
261
+ Check key count within range and return detailed status.
262
+
263
+ ```typescript
264
+ const user = { name: 'John', email: 'john@example.com' };
265
+
266
+ utils.object.KeysInRangeDetailed(user, 1, 5); // 0 (within range)
267
+ utils.object.KeysInRangeDetailed(user, 5, 10); // -1 (below minimum)
268
+ utils.object.KeysInRangeDetailed(user, 1, 1); // 1 (above maximum)
269
+ ```
270
+
271
+ Returns:
272
+ - `0`: Key count is within range
273
+ - `-1`: Key count is below minimum
274
+ - `1`: Key count is above maximum
275
+
276
+ #### AllowedKeys
277
+ Validate that an object contains only allowed keys, with required and optional key support.
278
+
279
+ ```typescript
280
+ const createUserPayload = { name: 'John', email: 'john@example.com' };
281
+ const updateUserPayload = { name: 'Jane' };
282
+
283
+ // Required keys: name, email
284
+ // Optional keys: phone, bio
285
+ const requiredKeys = ['name', 'email'];
286
+ const optionalKeys = ['phone', 'bio'];
287
+
288
+ utils.object.AllowedKeys(createUserPayload, requiredKeys, optionalKeys); // true
289
+ utils.object.AllowedKeys(updateUserPayload, requiredKeys, optionalKeys); // false (missing email)
290
+
291
+ // All keys in object must be allowed
292
+ const invalidPayload = { name: 'John', email: 'john@example.com', admin: true };
293
+ utils.object.AllowedKeys(invalidPayload, requiredKeys, optionalKeys); // false (admin not allowed)
294
+ ```
295
+
296
+ **Parameters:**
297
+ - `obj` (Record<string, any>): Object to validate
298
+ - `keys` (string[]): Required keys that must be present
299
+ - `optionalKeys` (string[], default `[]`): Optional keys that may be present
300
+
301
+ **Returns:** `true` if validation passes, `false` otherwise
302
+
303
+ ---
304
+
305
+ ## Complete Example
306
+
307
+ ```typescript
308
+ import express from 'express';
309
+ import utils from '@gerync/utils';
310
+
311
+ const app = express();
312
+
313
+ // Setup Config
314
+ utils.configure({
315
+ prefs: {
316
+ acceptedLanguages: ['en', 'es'],
317
+ noDupesAllowedof: ['email', 'username']
318
+ },
319
+ responses: {
320
+ en: {
321
+ dupes: {
322
+ email: 'Email already exists.',
323
+ username: 'Username is taken.'
324
+ },
325
+ general: {
326
+ error: 'An error occurred.'
327
+ }
328
+ },
329
+ es: {
330
+ dupes: {
331
+ email: 'El email ya existe.',
332
+ username: 'El nombre de usuario ya está en uso.'
333
+ },
334
+ general: {
335
+ error: 'Ocurrió un error.'
336
+ }
337
+ }
338
+ }
339
+ });
340
+
341
+ app.use(express.json());
342
+
343
+ // Routes
344
+ app.post('/users', (req, res, next) => {
345
+ try {
346
+ // Validate request payload
347
+ const requiredKeys = ['name', 'email'];
348
+ const optionalKeys = ['phone'];
349
+
350
+ if (!utils.object.AllowedKeys(req.body, requiredKeys, optionalKeys)) {
351
+ return res.status(400).json({
352
+ status: 'error',
353
+ message: 'Invalid request payload'
354
+ });
355
+ }
356
+
357
+ utils.coloredlog(`User creation requested: ${req.body.email}`, 'cyan');
358
+
359
+ // Simulate user creation
360
+ const user = { id: 1, ...req.body };
361
+ res.json(user);
362
+ } catch (error) {
363
+ next(error);
364
+ }
365
+ });
366
+
367
+ // Error handler middleware (must be last)
368
+ app.use(utils.errorHandler);
369
+
370
+ app.listen(3000, () => {
371
+ utils.coloredlog('Server running on port 3000', 'green');
372
+ });
373
+ ```
374
+
375
+ ---
376
+
377
+ ## License
378
+
379
+ This project is licensed under the MIT License.
380
+
381
+ - Permissions: Commercial use, modification, distribution, private use
382
+ - Conditions: Include copyright and license notices
383
+ - Limitations: No warranty; authors are not liable for damages
384
+
385
+ See the full license text in [LICENSE](LICENSE).
package/SECURITY.md ADDED
@@ -0,0 +1,45 @@
1
+ # Security Policy
2
+
3
+ ## Overview
4
+
5
+ @gerync/utils is a lightweight utility library with no sensitive data handling, no network calls, and no authentication mechanisms. It primarily provides:
6
+ - Console logging utilities
7
+ - Error handling and configuration
8
+ - Object validation helpers
9
+
10
+ As such, security risks are minimal. However, we appreciate responsible disclosure.
11
+
12
+ ## Reporting Security Issues
13
+
14
+ If you discover a security concern:
15
+
16
+ 1. **Please do not open a public issue** on GitHub
17
+ 2. Email: contact@gerync.com with details
18
+ 3. Include a description of the issue and steps to reproduce (if applicable)
19
+
20
+ We'll acknowledge receipt and respond as time permits.
21
+
22
+ ## Known Limitations
23
+
24
+ - This is a hobby/utility project with no guaranteed maintenance SLA
25
+ - Security updates may take time or may not happen immediately
26
+ - Users are responsible for validating their own use cases
27
+
28
+ ## Best Practices for Users
29
+
30
+ - **Sanitize user input** before passing to object validation functions
31
+ - **Avoid logging sensitive data** (credentials, tokens, PII) through coloredlog
32
+ - **Configure error responses** carefully to avoid exposing internal details to clients
33
+ - **Keep dependencies updated** in your own projects
34
+
35
+ ## Third-Party Dependencies
36
+
37
+ - **express**: Security maintained by the Express community
38
+ - **typescript**: Security maintained by the TypeScript team
39
+
40
+ We don't actively monitor these, but we recommend keeping them updated in your own projects.
41
+
42
+ ## No Warranty
43
+
44
+ This library is provided "as-is" without warranty. See [LICENSE](LICENSE) for full details.
45
+
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Log a message to console with color support.
3
+ * Accepts hex, RGB, RGBA, and CSS named colors.
4
+ * Falls back to unstyled console.log if color is invalid.
5
+ *
6
+ * @param message - Text to log
7
+ * @param color - Valid hex (#XXX, #XXXX, #XXXXXX, #XXXXXXXX),
8
+ * RGB (rgb(r, g, b)), RGBA (rgba(r, g, b, a)),
9
+ * or CSS named color
10
+ *
11
+ * @example
12
+ * coloredlog('Error!', 'red');
13
+ * coloredlog('Success', '#00FF00');
14
+ * coloredlog('Info', 'rgb(0, 0, 255)');
15
+ */
16
+ export default function Coloredlog(message: string, color: string): void;
17
+ //# sourceMappingURL=Colorlog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Colorlog.d.ts","sourceRoot":"","sources":["../../functions/Colorlog.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CA8BvE"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Log a message to console with color support.
3
+ * Accepts hex, RGB, RGBA, and CSS named colors.
4
+ * Falls back to unstyled console.log if color is invalid.
5
+ *
6
+ * @param message - Text to log
7
+ * @param color - Valid hex (#XXX, #XXXX, #XXXXXX, #XXXXXXXX),
8
+ * RGB (rgb(r, g, b)), RGBA (rgba(r, g, b, a)),
9
+ * or CSS named color
10
+ *
11
+ * @example
12
+ * coloredlog('Error!', 'red');
13
+ * coloredlog('Success', '#00FF00');
14
+ * coloredlog('Info', 'rgb(0, 0, 255)');
15
+ */
16
+ export default function Coloredlog(message, color) {
17
+ // #region Color Validation Regexes
18
+ /** Regex patterns to validate different color formats */
19
+ const ColorRegex = {
20
+ /** Hex colors: #RGB, #RGBA, #RRGGBB, #RRGGBBAA */
21
+ hex: /^#([A-Fa-f0-9]{3}|[A-Fa-f0-9]{4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$/,
22
+ /** RGB/RGBA colors: rgb(r, g, b) or rgba(r, g, b, a) */
23
+ rgb: /^rgba?\(\s*(\d{1,3}%?)\s*[\s,]\s*(\d{1,3}%?)\s*[\s,]\s*(\d{1,3}%?)(?:\s*[\s,/]\s*((?:0|1|0?\.\d+)(?:%?)))?\s*\)$/,
24
+ /** CSS named colors (e.g., 'red', 'blue', 'aliceblue') */
25
+ named: /^(?:aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|mineskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered|palegoldenrod|palegreen|palevioletred)$/i,
26
+ };
27
+ // #endregion
28
+ // #region Validate Color Format
29
+ /** Test if color matches any valid format */
30
+ const isValid = ColorRegex.hex.test(color) ||
31
+ ColorRegex.rgb.test(color) ||
32
+ ColorRegex.named.test(color);
33
+ // #endregion
34
+ // #region Output
35
+ if (isValid) {
36
+ /** Use CSS styling for valid colors */
37
+ console.log(`%c${message}`, `color: ${color}; font-weight: bold;`);
38
+ }
39
+ else {
40
+ /** Fallback: plain console.log if color is invalid */
41
+ console.log(message);
42
+ }
43
+ // #endregion
44
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Runtime configuration manager for preferences and localized response messages.
3
+ * Stores global state for error messages, language preferences, and custom messages.
4
+ */
5
+ declare const Configs: {
6
+ /**
7
+ * Initialize configuration with custom responses and preferences.
8
+ * Merges or replaces the runtime state.
9
+ * @param options.responses - Localized message objects by language
10
+ * @param options.prefs - Preferences including acceptedLanguages and noDupesAllowedof
11
+ */
12
+ configure(options: {
13
+ responses: any;
14
+ prefs: any;
15
+ }): void;
16
+ /**
17
+ * Retrieve all configured localized response messages.
18
+ * @returns The current responses object (by language and code)
19
+ */
20
+ getResponses(): any;
21
+ /**
22
+ * Retrieve current preferences.
23
+ * @returns The current preferences object
24
+ */
25
+ getPrefs(): any;
26
+ /**
27
+ * Set or update a single message for a language and error code.
28
+ * Useful for dynamically adding custom messages at runtime.
29
+ * @param lang - Language code (e.g., 'en', 'es', 'fr')
30
+ * @param code - Error or message code (e.g., 'CUSTOM_ERROR', 'ObjectTooLong')
31
+ * @param message - The message text
32
+ */
33
+ setMessage(lang: string, code: string, message: string): void;
34
+ /**
35
+ * Retrieve a message by language and code with graceful fallbacks.
36
+ * Fallback chain: requested language → English → provided fallback → undefined
37
+ * @param lang - Language code to look up
38
+ * @param code - Message code to retrieve
39
+ * @param fallback - Optional fallback message if not found
40
+ * @returns The message, fallback, or undefined
41
+ */
42
+ getMessage(lang: string, code: string, fallback?: string): string | undefined;
43
+ };
44
+ export default Configs;
45
+ //# sourceMappingURL=Config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Config.d.ts","sourceRoot":"","sources":["../../functions/Config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAaH,QAAA,MAAM,OAAO;IAET;;;;;OAKG;uBACgB;QAAE,SAAS,EAAE,GAAG,CAAC;QAAC,KAAK,EAAE,GAAG,CAAA;KAAE;IAOjD;;;OAGG;;IAOH;;;OAGG;;IAOH;;;;;;OAMG;qBACc,MAAM,QAAQ,MAAM,WAAW,MAAM;IAetD;;;;;;;OAOG;qBACc,MAAM,QAAQ,MAAM,aAAa,MAAM,GAAG,MAAM,GAAG,SAAS;CAehF,CAAC;AAEF,eAAe,OAAO,CAAC"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Runtime configuration manager for preferences and localized response messages.
3
+ * Stores global state for error messages, language preferences, and custom messages.
4
+ */
5
+ // #region Runtime State
6
+ /** Cached localized response messages for all languages */
7
+ let runtimeResponses = {};
8
+ /** Cached preferences (languages, duplicate field names, etc.) */
9
+ let runtimePrefs = {
10
+ noDupesAllowedof: [],
11
+ acceptedLanguages: ['en']
12
+ };
13
+ // #endregion
14
+ const Configs = {
15
+ // #region Configure
16
+ /**
17
+ * Initialize configuration with custom responses and preferences.
18
+ * Merges or replaces the runtime state.
19
+ * @param options.responses - Localized message objects by language
20
+ * @param options.prefs - Preferences including acceptedLanguages and noDupesAllowedof
21
+ */
22
+ configure(options) {
23
+ runtimeResponses = options.responses;
24
+ runtimePrefs = options.prefs;
25
+ },
26
+ // #endregion
27
+ // #region Get Responses
28
+ /**
29
+ * Retrieve all configured localized response messages.
30
+ * @returns The current responses object (by language and code)
31
+ */
32
+ getResponses() {
33
+ return runtimeResponses;
34
+ },
35
+ // #endregion
36
+ // #region Get Preferences
37
+ /**
38
+ * Retrieve current preferences.
39
+ * @returns The current preferences object
40
+ */
41
+ getPrefs() {
42
+ return runtimePrefs;
43
+ },
44
+ // #endregion
45
+ // #region Set Message
46
+ /**
47
+ * Set or update a single message for a language and error code.
48
+ * Useful for dynamically adding custom messages at runtime.
49
+ * @param lang - Language code (e.g., 'en', 'es', 'fr')
50
+ * @param code - Error or message code (e.g., 'CUSTOM_ERROR', 'ObjectTooLong')
51
+ * @param message - The message text
52
+ */
53
+ setMessage(lang, code, message) {
54
+ // Initialize responses if not yet configured
55
+ if (!runtimeResponses || typeof runtimeResponses !== 'object') {
56
+ runtimeResponses = {};
57
+ }
58
+ // Initialize language pack if not exists
59
+ if (!runtimeResponses[lang] || typeof runtimeResponses[lang] !== 'object') {
60
+ runtimeResponses[lang] = {};
61
+ }
62
+ // Set the message
63
+ runtimeResponses[lang][code] = message;
64
+ },
65
+ // #endregion
66
+ // #region Get Message
67
+ /**
68
+ * Retrieve a message by language and code with graceful fallbacks.
69
+ * Fallback chain: requested language → English → provided fallback → undefined
70
+ * @param lang - Language code to look up
71
+ * @param code - Message code to retrieve
72
+ * @param fallback - Optional fallback message if not found
73
+ * @returns The message, fallback, or undefined
74
+ */
75
+ getMessage(lang, code, fallback) {
76
+ // Try requested language first
77
+ const langPack = runtimeResponses?.[lang];
78
+ if (langPack && typeof langPack[code] === 'string') {
79
+ return langPack[code];
80
+ }
81
+ // Fall back to English
82
+ const englishPack = runtimeResponses?.['en'];
83
+ if (englishPack && typeof englishPack[code] === 'string') {
84
+ return englishPack[code];
85
+ }
86
+ // Return provided fallback or undefined
87
+ return fallback;
88
+ }
89
+ // #endregion
90
+ };
91
+ export default Configs;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Object utilities for key validation and inspection.
3
+ * Provides functions to count, range-check, and validate object keys.
4
+ */
5
+ /**
6
+ * Count the number of keys in an object.
7
+ * @param obj - The object to inspect
8
+ * @returns The number of keys (properties) in the object
9
+ */
10
+ export declare function Keysamount(obj: Record<string, any>): number;
11
+ /**
12
+ * Check if an object's key count is within a specified range.
13
+ * If only min is provided, checks for exact match.
14
+ * @param obj - The object to inspect
15
+ * @param min - Minimum key count (or exact count if max is undefined)
16
+ * @param max - Maximum key count (optional)
17
+ * @returns true if key count is within range, false otherwise
18
+ */
19
+ export declare function KeysInRange(obj: Record<string, any>, min: number, max: number): boolean;
20
+ /**
21
+ * Check if an object's key count is within range and return detailed status.
22
+ * @param obj - The object to inspect
23
+ * @param min - Minimum key count
24
+ * @param max - Maximum key count
25
+ * @returns -1 if below minimum, 1 if above maximum, 0 if within range
26
+ */
27
+ export declare function KeysInRangeDetailed(obj: Record<string, any>, min: number, max: number): number;
28
+ /**
29
+ * Validate that an object contains only allowed keys.
30
+ * All required keys must be present; object can only contain required + optional keys.
31
+ * @param obj - The object to validate
32
+ * @param keys - Required keys that must be present
33
+ * @param optionalKeys - Optional keys that may be present (default: [])
34
+ * @returns true if validation passes, false if any rule violated
35
+ */
36
+ export declare function AllowedKeys(obj: Record<string, any>, keys: string[], optionalKeys?: string[]): boolean;
37
+ declare const _default: {
38
+ Keysamount: typeof Keysamount;
39
+ KeysInRange: typeof KeysInRange;
40
+ KeysInRangeDetailed: typeof KeysInRangeDetailed;
41
+ AllowedKeys: typeof AllowedKeys;
42
+ };
43
+ export default _default;
44
+ //# sourceMappingURL=ObjectKeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectKeys.d.ts","sourceRoot":"","sources":["../../functions/ObjectKeys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,MAAM,CAE3D;AAID;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAOvF;AAID;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAa9F;AAID;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,YAAY,GAAE,MAAM,EAAO,GAAG,OAAO,CAwB1G;;;;;;;AAID,wBAKE"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Object utilities for key validation and inspection.
3
+ * Provides functions to count, range-check, and validate object keys.
4
+ */
5
+ // #region Keysamount
6
+ /**
7
+ * Count the number of keys in an object.
8
+ * @param obj - The object to inspect
9
+ * @returns The number of keys (properties) in the object
10
+ */
11
+ export function Keysamount(obj) {
12
+ return Object.keys(obj).length;
13
+ }
14
+ // #endregion
15
+ // #region KeysInRange
16
+ /**
17
+ * Check if an object's key count is within a specified range.
18
+ * If only min is provided, checks for exact match.
19
+ * @param obj - The object to inspect
20
+ * @param min - Minimum key count (or exact count if max is undefined)
21
+ * @param max - Maximum key count (optional)
22
+ * @returns true if key count is within range, false otherwise
23
+ */
24
+ export function KeysInRange(obj, min, max) {
25
+ const keyCount = Keysamount(obj);
26
+ // If max not provided, treat min as exact count
27
+ if (max === undefined) {
28
+ max = min;
29
+ }
30
+ return keyCount >= min && keyCount <= max;
31
+ }
32
+ // #endregion
33
+ // #region KeysInRangeDetailed
34
+ /**
35
+ * Check if an object's key count is within range and return detailed status.
36
+ * @param obj - The object to inspect
37
+ * @param min - Minimum key count
38
+ * @param max - Maximum key count
39
+ * @returns -1 if below minimum, 1 if above maximum, 0 if within range
40
+ */
41
+ export function KeysInRangeDetailed(obj, min, max) {
42
+ const keyCount = Keysamount(obj);
43
+ // If max not provided, treat min as exact count
44
+ if (max === undefined) {
45
+ max = min;
46
+ }
47
+ if (keyCount < min) {
48
+ return -1;
49
+ }
50
+ else if (keyCount > max) {
51
+ return 1;
52
+ }
53
+ else {
54
+ return 0;
55
+ }
56
+ }
57
+ // #endregion
58
+ // #region AllowedKeys
59
+ /**
60
+ * Validate that an object contains only allowed keys.
61
+ * All required keys must be present; object can only contain required + optional keys.
62
+ * @param obj - The object to validate
63
+ * @param keys - Required keys that must be present
64
+ * @param optionalKeys - Optional keys that may be present (default: [])
65
+ * @returns true if validation passes, false if any rule violated
66
+ */
67
+ export function AllowedKeys(obj, keys, optionalKeys = []) {
68
+ const objKeys = Object.keys(obj);
69
+ const allowedSet = new Set([...keys, ...optionalKeys]);
70
+ // Check: object doesn't have extra keys
71
+ if (objKeys.length > allowedSet.size) {
72
+ return false;
73
+ }
74
+ // Check: all required keys are present
75
+ for (const key of keys) {
76
+ if (!(key in obj)) {
77
+ return false;
78
+ }
79
+ }
80
+ // Check: all keys in object are allowed
81
+ for (const key of objKeys) {
82
+ if (!allowedSet.has(key)) {
83
+ return false;
84
+ }
85
+ }
86
+ return true;
87
+ }
88
+ // #endregion
89
+ // #region Exports
90
+ export default {
91
+ Keysamount,
92
+ KeysInRange,
93
+ KeysInRangeDetailed,
94
+ AllowedKeys
95
+ };
96
+ // #endregion
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Express error handler middleware for centralized error handling
3
+ * with multi-language support, smart logging, and user-friendly messages.
4
+ *
5
+ * Usage: app.use(errorHandler) at the end of middleware chain
6
+ */
7
+ export default function errorHandler(err: any, req: any, res: any, next: any): void;
8
+ //# sourceMappingURL=handleError.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handleError.d.ts","sourceRoot":"","sources":["../../functions/handleError.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,GAAG,IAAI,CA+IlF"}
@@ -0,0 +1,137 @@
1
+ import Configs from './Config';
2
+ import Coloredlog from './Colorlog';
3
+ /**
4
+ * Express error handler middleware for centralized error handling
5
+ * with multi-language support, smart logging, and user-friendly messages.
6
+ *
7
+ * Usage: app.use(errorHandler) at the end of middleware chain
8
+ */
9
+ export default function errorHandler(err, req, res, next) {
10
+ // #region Initialize Configuration
11
+ const responses = Configs.getResponses();
12
+ const prefs = Configs.getPrefs();
13
+ // #endregion
14
+ // #region Extract Request Context
15
+ /** Detect user language from request or Accept-Language header */
16
+ const lang = (req.lang || req.language || (req.headers['accept-language'] ? req.headers['accept-language'].split(',')[0] : 'en'));
17
+ /** Extract error details from error object */
18
+ const statusCode = err.status || 500;
19
+ const code = err.code || 'INTERNAL_SERVER_ERROR';
20
+ const rawErrorMessage = err.message || '';
21
+ // #endregion
22
+ // #region Server Default Messages
23
+ /** Fallback messages for server-side (5xx) errors when localized responses unavailable */
24
+ const serverDefaultMessages = {
25
+ INTERNAL_SERVER_ERROR: 'An internal server error occurred.',
26
+ DATABASE_ERROR: 'A database error occurred.',
27
+ SERVICE_UNAVAILABLE: 'Service is temporarily unavailable.',
28
+ BAD_GATEWAY: 'Bad gateway.',
29
+ GATEWAY_TIMEOUT: 'Gateway timed out.',
30
+ NOT_IMPLEMENTED: 'This feature is not implemented.',
31
+ NETWORK_ERROR: 'A network error occurred.',
32
+ TIMEOUT: 'The operation timed out.'
33
+ };
34
+ // #endregion
35
+ // #region Build User-Facing Response Message
36
+ /** Get localized responses for detected language, fallback to English */
37
+ const possibleResponses = responses[lang] || responses['en'] || {};
38
+ let responseMessage = '';
39
+ /** Handle duplicate entry errors (e.g., unique constraint violations) */
40
+ if (code === 'ER_DUP_ENTRY' || rawErrorMessage.includes('ER_DUP_ENTRY')) {
41
+ const noDupesof = prefs.noDupesAllowedof || [];
42
+ for (const field of noDupesof) {
43
+ if (rawErrorMessage.includes(field)) {
44
+ responseMessage = possibleResponses.dupes?.[field];
45
+ break;
46
+ }
47
+ }
48
+ }
49
+ /** Handle validation errors with per-field messages */
50
+ else if (code === 'VALIDATION_ERROR' && err.errors) {
51
+ for (const fieldError of err.errors) {
52
+ const fieldName = fieldError.param || fieldError.path;
53
+ if (possibleResponses.validation?.[fieldName]) {
54
+ responseMessage = possibleResponses.validation[fieldName];
55
+ break;
56
+ }
57
+ }
58
+ }
59
+ /** Fallback: use raw error message if no specific handler matched */
60
+ else {
61
+ responseMessage = rawErrorMessage;
62
+ }
63
+ /** Apply fallback logic for message resolution */
64
+ if (!responseMessage) {
65
+ // Try localized message by error code
66
+ if (possibleResponses[code]) {
67
+ responseMessage = possibleResponses[code];
68
+ }
69
+ // Try server default for 5xx errors
70
+ else if (statusCode >= 500 && serverDefaultMessages[code]) {
71
+ responseMessage = serverDefaultMessages[code];
72
+ }
73
+ // Final fallback: generic error message
74
+ else {
75
+ responseMessage = possibleResponses.general?.error || serverDefaultMessages['INTERNAL_SERVER_ERROR'];
76
+ }
77
+ }
78
+ // #endregion
79
+ // #region Error Classification (User vs Server)
80
+ /**
81
+ * Classify errors to determine whether to log details for maintainers.
82
+ * User-caused errors (4xx) are silenced; server errors (5xx) are logged.
83
+ */
84
+ const userCausedCodes = new Set([
85
+ 'VALIDATION_ERROR',
86
+ 'ER_DUP_ENTRY',
87
+ 'BAD_REQUEST',
88
+ 'UNAUTHORIZED',
89
+ 'FORBIDDEN',
90
+ 'NOT_FOUND',
91
+ 'CONFLICT',
92
+ 'METHOD_NOT_ALLOWED',
93
+ 'NOT_ACCEPTABLE',
94
+ 'UNSUPPORTED_MEDIA_TYPE',
95
+ 'PAYLOAD_TOO_LARGE',
96
+ 'UNPROCESSABLE_ENTITY',
97
+ 'TOO_MANY_REQUESTS'
98
+ ]);
99
+ const isUserError = (statusCode >= 400 && statusCode < 500) || userCausedCodes.has(code);
100
+ // #endregion
101
+ // #region Maintainer Logging (Server Errors Only)
102
+ /** Extract route information for logging */
103
+ const routeInfo = `${req.method || ''} ${req.originalUrl || req.url || ''}`.trim();
104
+ const requestId = (req.id || req.requestId || '');
105
+ /** Log detailed, color-coded error report for maintainers (skip for user-caused errors) */
106
+ if (!isUserError) {
107
+ Coloredlog('===================== ERROR REPORT =====================', 'red');
108
+ // Timestamp
109
+ let exactTime = new Date().toISOString();
110
+ exactTime = exactTime.replace('T', ' ').replace('Z', '');
111
+ Coloredlog(`Time: ${exactTime}`, 'yellow');
112
+ // Request context
113
+ if (routeInfo)
114
+ Coloredlog(`Route: ${routeInfo}`, 'yellow');
115
+ if (requestId)
116
+ Coloredlog(`RequestId: ${requestId}`, 'yellow');
117
+ // Error details
118
+ Coloredlog(`Status: ${statusCode}`, 'magenta');
119
+ Coloredlog(`Code: ${code}`, 'magenta');
120
+ // Messages for comparison
121
+ Coloredlog(`UserMessage: ${responseMessage}`, 'cyan');
122
+ Coloredlog(`RawMessage: ${rawErrorMessage}`, 'white');
123
+ // Stack trace for debugging
124
+ Coloredlog('Stack:', 'gray');
125
+ Coloredlog(err && err.stack ? String(err.stack) : 'N/A', 'gray');
126
+ Coloredlog('========================================================', 'red');
127
+ }
128
+ // #endregion
129
+ // #region Send Response to Client
130
+ /** Return JSON response with error details (safe for client) */
131
+ res.status(statusCode).json({
132
+ status: 'error',
133
+ code: code,
134
+ message: responseMessage
135
+ });
136
+ // #endregion
137
+ }
@@ -0,0 +1,28 @@
1
+ import coloredlog from "./functions/Colorlog";
2
+ import errorHandler from "./functions/handleError";
3
+ declare const _default: {
4
+ coloredlog: typeof coloredlog;
5
+ errorHandler: typeof errorHandler;
6
+ config: {
7
+ configure(options: {
8
+ responses: any;
9
+ prefs: any;
10
+ }): void;
11
+ getResponses(): any;
12
+ getPrefs(): any;
13
+ setMessage(lang: string, code: string, message: string): void;
14
+ getMessage(lang: string, code: string, fallback?: string): string | undefined;
15
+ };
16
+ configure: (options: {
17
+ responses: any;
18
+ prefs: any;
19
+ }) => void;
20
+ object: {
21
+ Keysamount: typeof import("./functions/ObjectKeys").Keysamount;
22
+ KeysInRange: typeof import("./functions/ObjectKeys").KeysInRange;
23
+ KeysInRangeDetailed: typeof import("./functions/ObjectKeys").KeysInRangeDetailed;
24
+ AllowedKeys: typeof import("./functions/ObjectKeys").AllowedKeys;
25
+ };
26
+ };
27
+ export default _default;
28
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,sBAAsB,CAAC;AAC9C,OAAO,YAAY,MAAM,yBAAyB,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;AAKnD,wBAME"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ import coloredlog from "./functions/Colorlog";
2
+ import errorHandler from "./functions/handleError";
3
+ import config from "./functions/Config";
4
+ import object from "./functions/ObjectKeys";
5
+ export default {
6
+ coloredlog,
7
+ errorHandler,
8
+ config,
9
+ configure: config.configure,
10
+ object
11
+ };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@gerync/utils",
3
+ "version": "1.0.0",
4
+ "description": "A collection of utilities for any type of project, providing logging, error handling, configuration management, and object manipulation helpers.",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE",
18
+ "SECURITY.md"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "prepublishOnly": "npm run build",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "keywords": [
27
+ "utilities",
28
+ "error-handling",
29
+ "logging",
30
+ "express",
31
+ "middleware",
32
+ "validation",
33
+ "configuration",
34
+ "i18n",
35
+ "object-manipulation",
36
+ "typescript",
37
+ "multi-language",
38
+ "utils",
39
+ "multilingual"
40
+ ],
41
+ "author": "gerync",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/gerync/utils.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/gerync/utils/issues"
49
+ },
50
+ "homepage": "https://github.com/gerync/utils#readme",
51
+ "peerDependencies": {
52
+ "express": "^5.0.0"
53
+ },
54
+ "dependencies": {},
55
+ "devDependencies": {
56
+ "@types/node": "^20.11.30",
57
+ "@types/express": "^4.17.21",
58
+ "express": "^5.2.1",
59
+ "typescript": "^5.6.3",
60
+ "vitest": "^1.6.0"
61
+ }
62
+ }