@reldens/utils 0.52.0 → 0.54.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/CLAUDE.md ADDED
@@ -0,0 +1,87 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Package Overview
6
+
7
+ **@reldens/utils** is a core utility package used throughout the Reldens ecosystem. It provides essential utilities for:
8
+ - Object manipulation (Shortcuts class)
9
+ - Event management (EventsManager)
10
+ - Logging (Logger)
11
+ - Schema validation (SchemaValidator)
12
+ - Environment variables (EnvVar)
13
+ - Error handling (ErrorManager)
14
+ - Interaction area validation (InteractionArea)
15
+ - Pagination utilities (PageRangeProvider)
16
+ - Validator base interface (ValidatorInterface)
17
+
18
+ ## Key Commands
19
+
20
+ ```bash
21
+ # Run tests
22
+ npm test
23
+ ```
24
+
25
+ ## Architecture
26
+
27
+ ### Core Classes
28
+
29
+ **Shortcuts** (`lib/shortcuts.js`):
30
+ - Provides utility methods for object manipulation, property access, and validation
31
+ - Imported as `sc` throughout the Reldens codebase
32
+ - Key methods: `get`, `hasOwn`, `isObject`, `isArray`, `deepMerge`, `deepClone`, etc.
33
+ - Used instead of direct property access or Object.prototype methods
34
+
35
+ **EventsManager** (`lib/events-manager.js`):
36
+ - Singleton event system used across Reldens
37
+ - Supports both sync (`emitSync`) and async (`emit`) events
38
+ - Allows event listeners to modify data via reference
39
+ - Debug mode available via `debug` property
40
+
41
+ **Logger** (`lib/logger.js`):
42
+ - Centralized logging utility with multiple log levels
43
+ - Levels: emergency, alert, critical, error, warning, notice, info, debug
44
+ - Configurable log level and trace options
45
+ - Used instead of console.log
46
+
47
+ **SchemaValidator** (`lib/schema-validator.js`):
48
+ - JSON schema validation utility
49
+ - Validates data structures against defined schemas
50
+ - Returns validation results with errors
51
+
52
+ **EnvVar** (`lib/env-var.js`):
53
+ - Environment variable utilities
54
+ - Type conversion and validation
55
+ - Safe environment variable access
56
+
57
+ **ErrorManager** (`lib/error-manager.js`):
58
+ - Singleton error handling utility
59
+ - Configurable error tracing via `enableTrace` property
60
+ - Supports custom error callbacks via `callback` property
61
+ - Default behavior throws Error with a message
62
+
63
+ **InteractionArea** (`lib/interaction-area.js`):
64
+ - Validates if positions are within interaction range
65
+ - Used for player-to-player, player-to-object interactions
66
+ - Configurable interaction area/margin
67
+ - Key methods: `setupInteractionArea`, `isValidInteraction`, `getPosition`
68
+
69
+ **PageRangeProvider** (`lib/page-range-provider.js`):
70
+ - Singleton pagination utility
71
+ - Generates page ranges for UI pagination
72
+ - Key method: `fetch(page, totalPages, totalDisplayedPages, firstLabel, lastLabel)`
73
+ - Returns array of page objects with label and value
74
+
75
+ **ValidatorInterface** (`lib/validator-interface.js`):
76
+ - Base interface for validator implementations
77
+ - Provides default `validate()` method that returns true
78
+ - Extend this class to create custom validators
79
+
80
+ ## Important Notes
81
+
82
+ - This package has NO external dependencies
83
+ - All code must be pure JavaScript with no dependencies
84
+ - The Shortcuts class is used extensively across Reldens - any changes affect the entire ecosystem
85
+ - Singleton pattern is used for: ErrorManager, PageRangeProvider, and EventsManagerSingleton (exported alongside the EventsManager class)
86
+ - Logger methods should be used instead of console.log everywhere in Reldens
87
+ - InteractionArea is instantiable (not a singleton) - create instances for each interactive object
package/README.md CHANGED
@@ -78,14 +78,13 @@ Pagination helper that calculates page ranges for UI components, handling first/
78
78
 
79
79
  ```javascript
80
80
  const { PageRangeProvider } = require('@reldens/utils');
81
- let pageProvider = new PageRangeProvider();
82
81
 
83
- // Generate page range
84
- let range = pageProvider.fetch(5, 20, 7, 'First', 'Last');
82
+ // Generate page range (it's a singleton, use directly)
83
+ let range = PageRangeProvider.fetch(5, 20, 7, 'First', 'Last');
85
84
  // Returns: [{label: 'First', value: 1}, {label: 2, value: 2}, ...]
86
85
 
87
86
  // Simple range
88
- let simpleRange = pageProvider.fetch(3, 10, 5);
87
+ let simpleRange = PageRangeProvider.fetch(3, 10, 5);
89
88
  // Returns pages around current page 3
90
89
  ```
91
90
 
@@ -98,8 +97,8 @@ const { SchemaValidator } = require('@reldens/utils');
98
97
  let schema = {
99
98
  username: { type: 'string', min: 3, max: 20 },
100
99
  age: { type: 'int', min: 18 },
101
- profile: {
102
- type: 'object',
100
+ profile: {
101
+ type: 'object',
103
102
  nested: {
104
103
  email: { type: 'string', required: true }
105
104
  }
@@ -111,6 +110,94 @@ let userData = { username: 'player1', age: 25, profile: { email: 'test@example.c
111
110
  let isValid = validator.validate(userData);
112
111
  ```
113
112
 
113
+ ### Shortcuts (sc)
114
+ Core utility class providing essential object manipulation, property access, and validation methods used throughout Reldens.
115
+
116
+ ```javascript
117
+ const { sc } = require('@reldens/utils');
118
+
119
+ // Safe property access
120
+ let value = sc.get(obj, 'nested.property.path', 'defaultValue');
121
+
122
+ // Check property ownership
123
+ if (sc.hasOwn(obj, 'propertyName')) {
124
+ // Property exists
125
+ }
126
+
127
+ // Type checking
128
+ sc.isObject(value); // true/false
129
+ sc.isArray(value); // true/false
130
+ sc.isFunction(value); // true/false
131
+
132
+ // Deep operations
133
+ let cloned = sc.deepClone(originalObject);
134
+ let merged = sc.deepMerge(obj1, obj2);
135
+
136
+ // Property manipulation
137
+ sc.getDef(obj, 'key', 'default'); // Get with default
138
+ sc.getOneDef(obj, ['key1', 'key2'], 'default'); // Get first found key
139
+ ```
140
+
141
+ ### ErrorManager
142
+ Singleton error handling utility with configurable tracing and custom error callbacks.
143
+
144
+ ```javascript
145
+ const { ErrorManager } = require('@reldens/utils');
146
+
147
+ // Enable stack traces
148
+ ErrorManager.enableTrace = true;
149
+
150
+ // Custom error handling
151
+ ErrorManager.callback = (message) => {
152
+ // Custom error handling logic
153
+ console.error('Custom error:', message);
154
+ // Return false to prevent default throw
155
+ return false;
156
+ };
157
+
158
+ // Trigger error
159
+ ErrorManager.error('Something went wrong');
160
+ ```
161
+
162
+ ### EnvVar
163
+ Environment variable utilities with type conversion and validation for safe environment variable access.
164
+
165
+ ```javascript
166
+ const { EnvVar } = require('@reldens/utils');
167
+
168
+ // Get environment variables with type conversion
169
+ let port = EnvVar.integer(process.env, 'PORT', 3000); // Returns integer or default
170
+ let enabled = EnvVar.boolean(process.env, 'FEATURE_ENABLED', false); // Returns boolean
171
+ let apiUrl = EnvVar.string(process.env, 'API_URL', 'http://localhost'); // Returns string
172
+
173
+ // Other type helpers
174
+ let portNumber = EnvVar.port(process.env, 'PORT', 8080); // Validates port range 1-65535
175
+ let config = EnvVar.json(process.env, 'CONFIG', {}); // Parse JSON string
176
+ let items = EnvVar.array(process.env, 'ITEMS', [], ','); // Split string to array
177
+ let url = EnvVar.url(process.env, 'API_URL', 'http://localhost'); // Validate URL
178
+ ```
179
+
180
+ ### ValidatorInterface
181
+ Base interface for creating custom validators with a standard validate method.
182
+
183
+ ```javascript
184
+ const { ValidatorInterface } = require('@reldens/utils');
185
+
186
+ // Extend to create custom validators
187
+ class CustomValidator extends ValidatorInterface {
188
+ validate(data) {
189
+ // Custom validation logic
190
+ if (!data.requiredField) {
191
+ return false;
192
+ }
193
+ return true;
194
+ }
195
+ }
196
+
197
+ let validator = new CustomValidator();
198
+ let isValid = validator.validate({ requiredField: 'value' });
199
+ ```
200
+
114
201
  ---
115
202
 
116
203
  Need something specific?
@@ -21,10 +21,11 @@ class EventsManager
21
21
  this.maxEventKeyLength = 1000;
22
22
  this.maxListeners = 10000;
23
23
  this.maxEventArgs = 50;
24
- this.sensitiveFields = ['password', 'token', 'secret', 'key', 'auth', 'credential'];
24
+ this.sensitiveFields = ['password', 'token', 'secret', 'auth', 'credential'];
25
25
  this.hasLoggedMaxListeners = false;
26
26
  this.symbolString = '--[[await-event-emitter]]--';
27
27
  this.typeKeyName = sc.isFunction(Symbol) ? Symbol.for(this.symbolString) : this.symbolString;
28
+ this.enableSensitiveDataFiltering = false;
28
29
  }
29
30
 
30
31
  assertType(type)
@@ -81,6 +82,9 @@ class EventsManager
81
82
  Logger.warning('Event arguments exceed maximum: '+args.length);
82
83
  return [];
83
84
  }
85
+ if(!this.enableSensitiveDataFiltering){
86
+ return args;
87
+ }
84
88
  return args.map(arg => {
85
89
  if(sc.isObject(arg)){
86
90
  return this.filterSensitiveData(arg);
@@ -89,11 +93,21 @@ class EventsManager
89
93
  });
90
94
  }
91
95
 
92
- filterSensitiveData(obj)
96
+ filterSensitiveData(obj, visited = new WeakSet())
93
97
  {
94
98
  if(!sc.isObject(obj)){
95
99
  return obj;
96
100
  }
101
+ if(visited.has(obj)){
102
+ return '[CIRCULAR]';
103
+ }
104
+ if(obj instanceof Map || obj instanceof Set || obj instanceof Date || obj instanceof Array){
105
+ return obj;
106
+ }
107
+ if(Object.getPrototypeOf(obj) !== Object.prototype){
108
+ return obj;
109
+ }
110
+ visited.add(obj);
97
111
  let filtered = {};
98
112
  for(let key of Object.keys(obj)){
99
113
  if(sc.hasDangerousKeys(null, key)){
@@ -107,11 +121,16 @@ class EventsManager
107
121
  continue;
108
122
  }
109
123
  if(sc.isObject(obj[key])){
110
- filtered[key] = this.filterSensitiveData(obj[key]);
124
+ filtered[key] = this.filterSensitiveData(obj[key], visited);
125
+ continue;
126
+ }
127
+ if(sc.isFunction(obj[key])) {
128
+ filtered[key] = obj[key];
111
129
  continue;
112
130
  }
113
131
  filtered[key] = obj[key];
114
132
  }
133
+ visited.delete(obj);
115
134
  return filtered;
116
135
  }
117
136
 
@@ -442,7 +461,9 @@ class EventsManager
442
461
  }
443
462
  let logMessage = type+' Event: '+key;
444
463
  if(args && 0 < args.length){
445
- let filteredArgs = args.map(arg => this.filterSensitiveData(arg));
464
+ let filteredArgs = this.enableSensitiveDataFiltering
465
+ ? args.map(arg => this.filterSensitiveData(arg))
466
+ : args;
446
467
  logMessage += ' with '+filteredArgs.length+' arguments';
447
468
  }
448
469
  Logger.debug(logMessage);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/utils",
3
3
  "scope": "@reldens",
4
- "version": "0.52.0",
4
+ "version": "0.54.0",
5
5
  "description": "Reldens - Utils",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -1,3 +1,9 @@
1
+ /**
2
+ *
3
+ * Reldens - TestEventsManager
4
+ *
5
+ */
6
+
1
7
  const EventsManager = require('../lib/events-manager');
2
8
 
3
9
  class TestEventsManager
@@ -8,6 +14,7 @@ class TestEventsManager
8
14
  this.testResults = [];
9
15
  this.testCount = 0;
10
16
  this.passedCount = 0;
17
+ this.currentTestMethod = '';
11
18
  }
12
19
 
13
20
  test(name, testFn)
@@ -15,11 +22,13 @@ class TestEventsManager
15
22
  this.testCount++;
16
23
  try{
17
24
  testFn();
18
- console.log('✓ PASS:', name);
25
+ let logMessage = '✓ PASS: '+this.currentTestMethod+' - '+name;
26
+ console.log(logMessage);
19
27
  this.passedCount++;
20
28
  this.testResults.push({name, status: 'PASS'});
21
29
  } catch(error){
22
- console.log('✗ FAIL:', name, '-', error.message);
30
+ let logMessage = '✗ FAIL: '+this.currentTestMethod+' - '+name+' - '+error.message;
31
+ console.log(logMessage);
23
32
  this.testResults.push({name, status: 'FAIL', error: error.message});
24
33
  }
25
34
  }
@@ -31,21 +40,23 @@ class TestEventsManager
31
40
  }
32
41
  }
33
42
 
34
- runAllTests()
43
+ async runAllTests()
35
44
  {
36
45
  console.log('Running tests for EventsManager...\n');
37
-
38
46
  let methodNames = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
39
47
  let testMethods = methodNames.filter(name =>
40
48
  name.startsWith('test') &&
41
49
  'function' === typeof this[name] &&
42
50
  name !== 'test'
43
51
  );
44
-
45
52
  for(let methodName of testMethods){
46
- this[methodName]();
53
+ this.currentTestMethod = methodName;
54
+ try{
55
+ await this[methodName]();
56
+ } catch(error){
57
+ console.error('Test method failed:', methodName, error.message);
58
+ }
47
59
  }
48
-
49
60
  this.printSummary();
50
61
  }
51
62
 
@@ -289,16 +300,17 @@ class TestEventsManager
289
300
  let loggedMessages = [];
290
301
  let originalDebug = console.log;
291
302
 
292
- console.log = (...args) => loggedMessages.push(args.join(' '));
293
- process.env.RELDENS_LOG_LEVEL = 8;
294
-
295
- for(let i = 0; i < 10; i++){
296
- emitter.on('test-max-'+i, () => {});
303
+ try{
304
+ console.log = (...args) => loggedMessages.push(args.join(' '));
305
+ process.env.RELDENS_LOG_LEVEL = 8;
306
+ for(let i = 0; i < 10; i++){
307
+ emitter.on('test-max-'+i, () => {});
308
+ }
309
+ } finally{
310
+ console.log = originalDebug;
311
+ delete process.env.RELDENS_LOG_LEVEL;
297
312
  }
298
313
 
299
- console.log = originalDebug;
300
- delete process.env.RELDENS_LOG_LEVEL;
301
-
302
314
  let debugLogs = loggedMessages.filter(log => log.includes('High listener count detected'));
303
315
  this.assert(1 === debugLogs.length, 'Should log high listener count only once');
304
316
  this.assert(true === emitter.hasLoggedMaxListeners, 'Should set flag after logging');
@@ -498,14 +510,15 @@ class TestEventsManager
498
510
  let loggedMessages = [];
499
511
  let originalLog = console.log;
500
512
 
501
- console.log = (...args) => loggedMessages.push(args.join(' '));
502
- process.env.RELDENS_LOG_LEVEL = 8;
503
-
504
- let sensitiveArgs = [{password: 'secret', safe: 'data'}];
505
- emitter.logDebugEvent('test', 'Fire', sensitiveArgs);
506
-
507
- console.log = originalLog;
508
- delete process.env.RELDENS_LOG_LEVEL;
513
+ try{
514
+ console.log = (...args) => loggedMessages.push(args.join(' '));
515
+ process.env.RELDENS_LOG_LEVEL = 8;
516
+ let sensitiveArgs = [{password: 'secret', safe: 'data'}];
517
+ emitter.logDebugEvent('test', 'Fire', sensitiveArgs);
518
+ } finally{
519
+ console.log = originalLog;
520
+ delete process.env.RELDENS_LOG_LEVEL;
521
+ }
509
522
 
510
523
  let debugLog = loggedMessages.find(log => log.includes('Fire Event: test'));
511
524
  this.assert(debugLog, 'Should log debug event');
@@ -580,13 +593,14 @@ class TestEventsManager
580
593
 
581
594
  let loggedMessages = [];
582
595
  let originalLog = console.log;
583
- console.log = (...args) => loggedMessages.push(args.join(' '));
584
- process.env.RELDENS_LOG_LEVEL = 8;
585
-
586
- emitter.logDebugEvent('non-matching-key', 'Listen');
587
-
588
- console.log = originalLog;
589
- delete process.env.RELDENS_LOG_LEVEL;
596
+ try{
597
+ console.log = (...args) => loggedMessages.push(args.join(' '));
598
+ process.env.RELDENS_LOG_LEVEL = 8;
599
+ emitter.logDebugEvent('non-matching-key', 'Listen');
600
+ } finally{
601
+ console.log = originalLog;
602
+ delete process.env.RELDENS_LOG_LEVEL;
603
+ }
590
604
 
591
605
  let debugLogs = loggedMessages.filter(log => log.includes('Listen Event:'));
592
606
  this.assert(0 === debugLogs.length, 'Should not log when no pattern matches');
@@ -894,17 +908,16 @@ class TestEventsManager
894
908
  let emitter = new EventsManager();
895
909
  let loggedEvents = [];
896
910
  let originalLog = console.log;
897
-
898
- console.log = (...args) => loggedEvents.push(args.join(' '));
899
- process.env.RELDENS_LOG_LEVEL = 8;
900
- emitter.debug = 'all';
901
-
902
- emitter.on('debug-test', () => {});
903
- await emitter.emit('debug-test');
904
-
905
- console.log = originalLog;
906
- delete process.env.RELDENS_LOG_LEVEL;
907
-
911
+ try{
912
+ console.log = (...args) => loggedEvents.push(args.join(' '));
913
+ process.env.RELDENS_LOG_LEVEL = 8;
914
+ emitter.debug = 'all';
915
+ emitter.on('debug-test', () => {});
916
+ await emitter.emit('debug-test');
917
+ } finally{
918
+ console.log = originalLog;
919
+ delete process.env.RELDENS_LOG_LEVEL;
920
+ }
908
921
  let hasListenLog = loggedEvents.some(log => log.includes('Listen Event:'));
909
922
  let hasFireLog = loggedEvents.some(log => log.includes('Fire Event:'));
910
923
  this.assert(hasListenLog, 'Should log listen events');
@@ -1058,6 +1071,17 @@ class TestEventsManager
1058
1071
  });
1059
1072
  }
1060
1073
 
1074
+ testFilterSensitiveDataWithCircularReferences()
1075
+ {
1076
+ this.test('filterSensitiveData handles circular references', () => {
1077
+ let emitter = new EventsManager();
1078
+ let obj = {safe: 'data'};
1079
+ obj.circular = obj;
1080
+ let result = emitter.filterSensitiveData(obj);
1081
+ this.assert('[CIRCULAR]' === result.circular, 'Should handle circular refs');
1082
+ });
1083
+ }
1084
+
1061
1085
  printSummary()
1062
1086
  {
1063
1087
  console.log('\n'+'='.repeat(50));
@@ -1067,7 +1091,6 @@ class TestEventsManager
1067
1091
  console.log('Passed:', this.passedCount);
1068
1092
  console.log('Failed:', this.testCount - this.passedCount);
1069
1093
  console.log('Success rate:', Math.round((this.passedCount / this.testCount) * 100)+'%');
1070
-
1071
1094
  if(this.testCount - this.passedCount > 0){
1072
1095
  console.log('\nFailed tests:');
1073
1096
  for(let result of this.testResults){
@@ -1076,9 +1099,13 @@ class TestEventsManager
1076
1099
  }
1077
1100
  }
1078
1101
  }
1102
+ console.log('\n'+'='.repeat(60));
1103
+ if(this.testCount - this.passedCount === 0){
1104
+ console.log('All tests completed successfully!');
1105
+ }
1106
+ console.log('='.repeat(60));
1079
1107
  }
1080
1108
 
1081
1109
  }
1082
1110
 
1083
- let testRunner = new TestEventsManager();
1084
- testRunner.runAllTests();
1111
+ module.exports.TestEventsManager = TestEventsManager;
package/tests/run.js CHANGED
@@ -1,13 +1,15 @@
1
1
  console.log('Running all tests...\n');
2
2
 
3
- try{
3
+ async function runTests(){
4
4
  console.log('='.repeat(60));
5
- console.log('TESTING NEW IMPLEMENTATION');
5
+ console.log('TESTING IMPLEMENTATION');
6
6
  console.log('='.repeat(60));
7
- require('./events-manager-test.js');
7
+ const { TestEventsManager } = require('./events-manager-test.js');
8
+ let testInstance = new TestEventsManager();
9
+ await testInstance.runAllTests();
10
+ }
8
11
 
9
- console.log('\n\nAll tests completed successfully!');
10
- } catch(error){
12
+ runTests().catch(error => {
11
13
  console.error('\nTest execution failed:', error.message);
12
14
  process.exit(1);
13
- }
15
+ });