@cldmv/slothlet 2.0.1 → 2.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.
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # @cldmv/slothlet
2
2
 
3
3
  <div align="center">
4
- <img src="images/slothlet-logo-v1-horizontal-transparent.png" alt="Slothlet Logo" width="600">
4
+ <img src="https://github.com/CLDMV/slothlet/raw/HEAD/images/slothlet-logo-v1-horizontal-transparent.png" alt="Slothlet Logo" width="600">
5
5
  </div>
6
6
 
7
7
  **@cldmv/slothlet** is a sophisticated module loading framework that revolutionizes how you work with massive APIs in Node.js. Built for developers who demand smart, efficient module loading without compromising performance or developer experience.
@@ -52,6 +52,14 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
52
52
  - **Smart Function Naming**: Preserves original capitalization (`autoIP`, `parseJSON`, `getHTTPStatus`)
53
53
  - **Multi-Execution Environments**: Singleton, VM, worker, fork isolation modes (experimental)
54
54
 
55
+ ### 🔧 **Advanced Sanitization Control** ⭐ NEW
56
+
57
+ - **Custom API Naming**: Control how filenames become API property names through sanitize options
58
+ - **Boundary Pattern Matching**: Use `**string**` patterns for precise transformations (`**url**` → `buildURLWithParams`)
59
+ - **Glob Pattern Support**: Apply rules with wildcards (`*json*`, `auto*`, `http*`) for flexible naming control
60
+ - **Case-Sensitive Rules**: Preserve important naming patterns (acronyms, technical terms, branding)
61
+ - **Mixed Rule Types**: Combine exact matches, globs, and boundary patterns for sophisticated naming strategies
62
+
55
63
  ### 📊 **Performance Optimizations**
56
64
 
57
65
  - **Startup**: Lazy mode 4.3x faster (564.17Ξs vs 2.45ms)
@@ -76,7 +84,7 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
76
84
 
77
85
  ### ⚡ Performance Excellence
78
86
 
79
- - **📊 For comprehensive performance analysis, benchmarks, and recommendations, see [PERFORMANCE.md](PERFORMANCE.md)**
87
+ - **📊 For comprehensive performance analysis, benchmarks, and recommendations, see [PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)**
80
88
 
81
89
  ### 🔧 **Smart API Management**
82
90
 
@@ -87,7 +95,7 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
87
95
  - **Hybrid Exports**: Support for callable APIs with methods, default + named exports, and mixed patterns
88
96
 
89
97
  > [!TIP]
90
- > **📁 For comprehensive examples of API flattening, naming conventions, and function preservation patterns, see the test modules in [api_tests/](api_tests/) and their documentation in [docs/api_tests/](docs/api_tests/)**
98
+ > **📁 For comprehensive examples of API flattening, naming conventions, and function preservation patterns, see the test modules in [api_tests/](https://github.com/CLDMV/slothlet/blob/HEAD/api_tests) and their documentation in [docs/api_tests/](https://github.com/CLDMV/slothlet/blob/HEAD/docs/api_tests)**
91
99
 
92
100
  ### 🔗 **Advanced Binding System**
93
101
 
@@ -215,10 +223,52 @@ const api = await slothlet({
215
223
  helpers: {
216
224
  /* ... */
217
225
  }
226
+ },
227
+ sanitize: {
228
+ // 🔧 NEW: Control API property naming
229
+ lowerFirst: false, // Keep first character casing
230
+ rules: {
231
+ leave: ["parseJSON", "autoIP"], // Preserve exact names
232
+ leaveInsensitive: ["*xml*"], // Case-insensitive preservation
233
+ upper: ["**url**", "api", "http*"], // Force uppercase (including boundary patterns)
234
+ lower: ["id", "uuid", "*id"] // Force lowercase
235
+ }
218
236
  }
219
237
  });
220
238
  ```
221
239
 
240
+ ### Sanitize Options Examples
241
+
242
+ Transform module filenames into clean, professional API property names:
243
+
244
+ ```javascript
245
+ // Without sanitize options (default behavior)
246
+ const api = await slothlet({ dir: "./api" });
247
+ // Files: build-url-with-params.mjs, parse-json-data.mjs, auto-ip.mjs
248
+ // Result: api.buildUrlWithParams, api.parseJsonData, api.autoIp
249
+
250
+ // With sanitize options (custom naming control)
251
+ const api = await slothlet({
252
+ dir: "./api",
253
+ sanitize: {
254
+ lowerFirst: false,
255
+ rules: {
256
+ leave: ["parseJSON"], // Exact match preservation
257
+ upper: ["**url**", "ip", "http*"], // Boundary + glob patterns
258
+ leaveInsensitive: ["*xml*"] // Case-insensitive globs
259
+ }
260
+ }
261
+ });
262
+ // Result: api.buildURLWithParams, api.parseJSON, api.autoIP
263
+ ```
264
+
265
+ **Sanitize Pattern Types:**
266
+
267
+ - **Exact Match**: `"parseJSON"` - Matches exact string only
268
+ - **Glob Patterns**: `"*json*"`, `"auto*"`, `"http*"` - Wildcard matching
269
+ - **Boundary Patterns**: `"**url**"` - Only matches when surrounded by word boundaries
270
+ - **Case Control**: `leaveInsensitive` for case-insensitive matching
271
+
222
272
  ### Multiple Instances
223
273
 
224
274
  In v2.x, each call to `slothlet(options)` automatically creates a new isolated instance with its own context and configuration:
@@ -284,6 +334,7 @@ Creates and loads an API instance with the specified configuration.
284
334
  | `api_mode` | `string` | `"auto"` | API structure behavior when root-level default functions exist:<br/>â€Ē `"auto"`: Automatically detects if root has default function export and creates callable API<br/>â€Ē `"function"`: Forces API to be callable (use when you have root-level default function exports)<br/>â€Ē `"object"`: Forces API to be object-only (use when you want object interface regardless of exports) |
285
335
  | `context` | `object` | `{}` | Context data object injected into live-binding `context` reference. Available to all loaded modules via `import { context } from '@cldmv/slothlet/runtime'` |
286
336
  | `reference` | `object` | `{}` | Reference object merged into the API root level. Properties not conflicting with loaded modules are added directly to the API |
337
+ | `sanitize` | `object` | `{}` | **🔧 NEW**: Control how filenames become API property names. Supports exact matches, glob patterns (`*json*`), and boundary patterns (`**url**`). Configure `lowerFirst` and `rules` for `leave`, `leaveInsensitive`, `upper`, and `lower` transformations |
287
338
 
288
339
  #### `slothlet.getApi()` ⇒ `object`
289
340
 
@@ -310,7 +361,7 @@ Gracefully shuts down the API and cleans up resources.
310
361
  **Returns:** `Promise<void>` - Resolves when shutdown is complete
311
362
 
312
363
  > [!NOTE]
313
- > **📚 For detailed API documentation with comprehensive parameter descriptions, method signatures, and examples, see [docs/API.md](docs/API.md)**
364
+ > **📚 For detailed API documentation with comprehensive parameter descriptions, method signatures, and examples, see [docs/API.md](https://github.com/CLDMV/slothlet/blob/HEAD/docs/API.md)**
314
365
 
315
366
  ### Live Bindings
316
367
 
@@ -809,7 +860,7 @@ We welcome contributions! The experimental modes in particular need development
809
860
  4. **Provide feedback** on API design and performance
810
861
  5. **Documentation improvements** are always appreciated
811
862
 
812
- See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
863
+ See [CONTRIBUTING.md](https://github.com/CLDMV/slothlet/blob/HEAD/CONTRIBUTING.md) for detailed contribution guidelines.
813
864
 
814
865
  [![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
815
866
 
@@ -819,7 +870,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
819
870
 
820
871
  For comprehensive performance benchmarks, analysis, and recommendations:
821
872
 
822
- **📈 [See PERFORMANCE.md](PERFORMANCE.md)**
873
+ **📈 [See PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)**
823
874
 
824
875
  Key highlights:
825
876
 
@@ -836,11 +887,11 @@ Key highlights:
836
887
 
837
888
  ## 📚 Documentation
838
889
 
839
- - **[API Documentation](docs/API.md)** - Complete API reference with examples
840
- - **[Performance Analysis](PERFORMANCE.md)** - Detailed benchmarks and recommendations
841
- - **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
842
- - **[Security Policy](SECURITY.md)** - Security guidelines and reporting
843
- - **[Test Documentation](api_tests/)** - Comprehensive test module examples
890
+ - **[API Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/docs/API.md)** - Complete API reference with examples
891
+ - **[Performance Analysis](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)** - Detailed benchmarks and recommendations
892
+ - **[Contributing Guide](https://github.com/CLDMV/slothlet/blob/HEAD/CONTRIBUTING.md)** - How to contribute to the project
893
+ - **[Security Policy](https://github.com/CLDMV/slothlet/blob/HEAD/SECURITY.md)** - Security guidelines and reporting
894
+ - **[Test Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/api_tests)** - Comprehensive test module examples
844
895
 
845
896
  ---
846
897
 
@@ -895,7 +946,7 @@ Slothlet v2.0 represents a complete architectural rewrite with enterprise-grade
895
946
  [repo size]: https://img.shields.io/github/repo-size/CLDMV/slothlet?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
896
947
  [repo_size_url]: https://github.com/CLDMV/slothlet
897
948
  [github license]: https://img.shields.io/github/license/CLDMV/slothlet.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
898
- [github_license_url]: LICENSE
949
+ [github_license_url]: https://github.com/CLDMV/slothlet/blob/HEAD/LICENSE
899
950
  [npm license]: https://img.shields.io/npm/l/%40cldmv%2Fslothlet.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837
900
951
  [npm_license_url]: https://www.npmjs.com/package/@cldmv/slothlet
901
952
  [contributors]: https://img.shields.io/github/contributors/CLDMV/slothlet.svg?style=for-the-badge&logo=github&logoColor=white&labelColor=181717
@@ -17,43 +17,230 @@
17
17
 
18
18
 
19
19
 
20
+ function globToRegex(pattern, caseSensitive = true) {
21
+ try {
22
+
23
+ if (pattern.startsWith("**") && pattern.endsWith("**") && pattern.length > 4) {
24
+ const innerString = pattern.slice(2, -2);
25
+
26
+ const escapedString = innerString.replace(/[.+^${}()|[\]\\*?]/g, "\\$&");
27
+
28
+
29
+ const flags = caseSensitive ? "" : "i";
30
+ return new RegExp(`(?<=.)${escapedString}(?=.)`, flags);
31
+ }
32
+
33
+
34
+
35
+ let regexPattern = pattern
36
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
37
+ .replace(/\*/g, ".*")
38
+ .replace(/\?/g, ".");
39
+
40
+ const flags = caseSensitive ? "" : "i";
41
+ return new RegExp(`^${regexPattern}$`, flags);
42
+ } catch (_) {
43
+ return null;
44
+ }
45
+ }
46
+
47
+
20
48
  export function sanitizePathName(input, opts = {}) {
21
49
  const { lowerFirst = true, rules = {} } = opts;
22
50
 
23
- const L = (rules.leave || []).map((s) => String(s).toLowerCase());
24
- const U = (rules.upper || []).map((s) => String(s).toLowerCase());
25
- const W = (rules.lower || []).map((s) => String(s).toLowerCase());
26
-
27
- const isValidId = (s) => /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(s);
51
+ const leaveRules = (rules.leave || []).map((s) => String(s));
52
+ const leaveInsensitiveRules = (rules.leaveInsensitive || []).map((s) => String(s));
53
+ const upperRules = (rules.upper || []).map((s) => String(s));
54
+ const lowerRules = (rules.lower || []).map((s) => String(s));
28
55
 
29
56
  let s = String(input).trim();
30
57
 
31
58
 
32
- if (isValidId(s)) return s;
59
+
33
60
 
34
61
 
35
62
  let parts = s.split(/[^A-Za-z0-9_$]+/).filter(Boolean);
36
63
  if (parts.length === 0) return "_";
37
64
 
38
65
 
39
-
40
66
  while (parts.length && !/^[A-Za-z_$]/.test(parts[0][0])) {
41
67
  parts[0] = parts[0].replace(/^[^A-Za-z_$]+/, "");
42
68
  if (!parts[0]) parts.shift();
43
69
  }
44
70
  if (parts.length === 0) return "_";
45
71
 
72
+
73
+ const segmentMatchesPreSplitPattern = (segment, patterns, caseSensitive = false) => {
74
+ for (const pattern of patterns) {
75
+
76
+
77
+
78
+
79
+ if (pattern.includes("*") || pattern.includes("?")) {
80
+ const regex = globToRegex(pattern, caseSensitive);
81
+ if (regex && regex.test(s)) {
82
+
83
+
84
+ const literalParts = pattern.split(/[*?]+/).filter(Boolean);
85
+
86
+ for (const literal of literalParts) {
87
+
88
+ const cleanLiteral = literal.replace(/[^A-Za-z0-9_$]/g, "");
89
+ if (cleanLiteral) {
90
+ const match = caseSensitive ? segment === cleanLiteral : segment.toLowerCase() === cleanLiteral.toLowerCase();
91
+ if (match) {
92
+ return true;
93
+ }
94
+ }
95
+ }
96
+ }
97
+ } else {
98
+
99
+ const match = caseSensitive ? segment === pattern : segment.toLowerCase() === pattern.toLowerCase();
100
+ if (match) {
101
+ return true;
102
+ }
103
+ }
104
+ }
105
+ return false;
106
+ };
107
+
46
108
  const applyRule = (seg, index) => {
47
- const key = seg.toLowerCase();
109
+
110
+ if (segmentMatchesPreSplitPattern(seg, leaveRules, true)) {
111
+ return seg;
112
+ }
48
113
 
49
114
 
50
- if (L.includes(key)) return seg;
115
+ if (segmentMatchesPreSplitPattern(seg, leaveInsensitiveRules, false)) {
116
+ return seg;
117
+ }
118
+
119
+
120
+ if (segmentMatchesPreSplitPattern(seg, upperRules, false)) {
121
+ return seg.toUpperCase();
122
+ }
123
+
124
+
125
+ if (segmentMatchesPreSplitPattern(seg, lowerRules, false)) {
126
+ return seg.toLowerCase();
127
+ }
51
128
 
52
129
 
53
- if (U.includes(key)) return seg.toUpperCase();
130
+ let transformedSeg = seg;
54
131
 
55
132
 
56
- if (W.includes(key)) return seg.toLowerCase();
133
+ for (const pattern of upperRules) {
134
+ if (pattern.includes("*") || pattern.includes("?")) {
135
+
136
+ if (!segmentMatchesPreSplitPattern(seg, [pattern], false)) {
137
+
138
+ if (pattern.startsWith("**") && pattern.endsWith("**") && pattern.length > 4) {
139
+ const innerString = pattern.slice(2, -2);
140
+
141
+
142
+ const innerRegex = new RegExp(innerString.replace(/[.+^${}()|[\]\\*?]/g, "\\$&"), "gi");
143
+
144
+
145
+ const matches = [...transformedSeg.matchAll(innerRegex)];
146
+ for (const match of matches) {
147
+ const startPos = match.index;
148
+ const endPos = startPos + match[0].length;
149
+
150
+
151
+ const hasCharBefore = startPos > 0;
152
+ const hasCharAfter = endPos < transformedSeg.length;
153
+
154
+ if (hasCharBefore && hasCharAfter) {
155
+
156
+ transformedSeg = transformedSeg.substring(0, startPos) + innerString.toUpperCase() + transformedSeg.substring(endPos);
157
+ break;
158
+ }
159
+ }
160
+ } else {
161
+
162
+ const literalParts = pattern.split(/[*?]+/).filter(Boolean);
163
+ for (const literal of literalParts) {
164
+ if (literal) {
165
+
166
+ const literalRegex = new RegExp(literal.replace(/[.+^${}()|[\]\\]/g, "\\$&"), "gi");
167
+ transformedSeg = transformedSeg.replace(literalRegex, literal.toUpperCase());
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+ }
174
+
175
+
176
+ for (const pattern of lowerRules) {
177
+ if (pattern.includes("*") || pattern.includes("?")) {
178
+
179
+ if (!segmentMatchesPreSplitPattern(seg, [pattern], false)) {
180
+
181
+ if (pattern.startsWith("**") && pattern.endsWith("**") && pattern.length > 4) {
182
+ const innerString = pattern.slice(2, -2);
183
+
184
+
185
+ const innerRegex = new RegExp(innerString.replace(/[.+^${}()|[\]\\*?]/g, "\\$&"), "gi");
186
+
187
+
188
+ const matches = [...transformedSeg.matchAll(innerRegex)];
189
+ for (const match of matches) {
190
+ const startPos = match.index;
191
+ const endPos = startPos + match[0].length;
192
+
193
+
194
+ const hasCharBefore = startPos > 0;
195
+ const hasCharAfter = endPos < transformedSeg.length;
196
+
197
+ if (hasCharBefore && hasCharAfter) {
198
+
199
+ transformedSeg = transformedSeg.substring(0, startPos) + innerString.toLowerCase() + transformedSeg.substring(endPos);
200
+ break;
201
+ }
202
+ }
203
+ } else {
204
+
205
+ const literalParts = pattern.split(/[*?]+/).filter(Boolean);
206
+ for (const literal of literalParts) {
207
+ if (literal) {
208
+ const literalRegex = new RegExp(literal.replace(/[.+^${}()|[\]\\]/g, "\\$&"), "gi");
209
+ transformedSeg = transformedSeg.replace(literalRegex, literal.toLowerCase());
210
+ }
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+
218
+
219
+ for (const pattern of upperRules) {
220
+ if (!pattern.includes("*") && !pattern.includes("?")) {
221
+
222
+ const match = seg.toLowerCase() === pattern.toLowerCase();
223
+ if (match) {
224
+ return seg.toUpperCase();
225
+ }
226
+ }
227
+ }
228
+
229
+
230
+ for (const pattern of lowerRules) {
231
+ if (!pattern.includes("*") && !pattern.includes("?")) {
232
+
233
+ const match = seg.toLowerCase() === pattern.toLowerCase();
234
+ if (match) {
235
+ return seg.toLowerCase();
236
+ }
237
+ }
238
+ }
239
+
240
+
241
+ if (transformedSeg !== seg) {
242
+ return transformedSeg;
243
+ }
57
244
 
58
245
 
59
246
  if (index === 0) {
package/dist/slothlet.mjs CHANGED
@@ -112,7 +112,7 @@ const slothletObject = {
112
112
  reference: {},
113
113
  mode: "singleton",
114
114
  loaded: false,
115
- config: { lazy: false, apiDepth: Infinity, debug: DEBUG, dir: null },
115
+ config: { lazy: false, apiDepth: Infinity, debug: DEBUG, dir: null, sanitize: null },
116
116
  _dispose: null,
117
117
  _boundAPIShutdown: null,
118
118
 
@@ -179,11 +179,16 @@ const slothletObject = {
179
179
  let api;
180
180
  let dispose;
181
181
  if (mode === "singleton") {
182
- const { context = null, reference = null, ...loadConfig } = options;
182
+ const { context = null, reference = null, sanitize = null, ...loadConfig } = options;
183
183
  this.context = context;
184
184
  this.reference = reference;
185
185
 
186
186
 
187
+ if (sanitize !== null) {
188
+ this.config.sanitize = sanitize;
189
+ }
190
+
191
+
187
192
 
188
193
  if (this.api_mode === "function") {
189
194
  this.boundapi = function (...args) {
@@ -334,7 +339,7 @@ const slothletObject = {
334
339
 
335
340
 
336
341
  _toApiKey(name) {
337
- return sanitizePathName(name);
342
+ return sanitizePathName(name, this.config.sanitize || {});
338
343
 
339
344
  },
340
345
 
@@ -722,7 +727,7 @@ const slothletObject = {
722
727
 
723
728
 
724
729
 
725
- obj[exportName] = exportValue;
730
+ obj[this._toApiKey(exportName)] = exportValue;
726
731
  }
727
732
  }
728
733
 
@@ -759,7 +764,7 @@ const slothletObject = {
759
764
  for (const [exportName, exportValue] of namedExports) {
760
765
 
761
766
 
762
- apiExport[exportName] = exportValue;
767
+ apiExport[this._toApiKey(exportName)] = exportValue;
763
768
  }
764
769
  return apiExport;
765
770
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cldmv/slothlet",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "moduleVersions": {
5
5
  "lazy": "1.0.0",
6
6
  "eager": "1.0.0"
@@ -1,46 +1,3 @@
1
- /**
2
- * @fileoverview String sanitization utilities for slothlet API property names. Internal file (not exported in package.json).
3
- * @module @cldmv/slothlet.helpers.sanitize
4
- * @memberof module:@cldmv/slothlet.helpers
5
- * @internal
6
- * @package
7
- *
8
- * @description
9
- * Advanced string sanitization system for converting arbitrary file names into valid JavaScript
10
- * property names suitable for slothlet's dot-notation API access. Implements sophisticated
11
- * identifier validation, segment-based transformation rules, and configurable casing policies.
12
- *
13
- * Key features:
14
- * - Valid identifier detection with fast-path optimization
15
- * - Configurable first-segment casing (lowerFirst option)
16
- * - Advanced rule-based transformation (leave, upper, lower arrays)
17
- * - Cross-platform filename compatibility
18
- * - Edge case handling for special characters and numeric prefixes
19
- * - Camel-case conversion for multi-segment identifiers
20
- *
21
- * Technical implementation:
22
- * - Uses regex-based validation for JavaScript identifier compliance
23
- * - Segment splitting on non-identifier characters [^A-Za-z0-9_$]
24
- * - Rule precedence: leave → upper → lower → default casing
25
- * - Safety fallbacks for empty results and invalid identifier starts
26
- * - Performance-optimized with early returns for valid identifiers
27
- *
28
- * Usage context:
29
- * - File-to-API mapping in slothlet module loading
30
- * - Dynamic property name generation for module namespaces
31
- * - Sanitization of user-provided file names into safe property accessors
32
- *
33
- *
34
- * @example
35
- * // ESM (internal)
36
- * import { sanitizePathName } from '@cldmv/slothlet/helpers/sanitize';
37
- * // Internal example using package.json exports
38
- *
39
- * @example
40
- * // Relative import (internal)
41
- * import { sanitizePathName } from './sanitize.mjs';
42
- * const apiKey = sanitizePathName('auto-ip.mjs');
43
- */
44
1
  /**
45
2
  * @function sanitizePathName
46
3
  * @package
@@ -48,37 +5,19 @@
48
5
  * @param {string} input - The input string to sanitize (e.g., file name, path segment)
49
6
  * @param {Object} [opts={}] - Sanitization configuration options
50
7
  * @param {boolean} [opts.lowerFirst=true] - Lowercase the first character of the first segment for camelCase convention
51
- * @param {Object} [opts.rules={}] - Advanced segment transformation rules
52
- * @param {string[]} [opts.rules.leave=[]] - Segments to preserve exactly as-is (case-sensitive)
53
- * @param {string[]} [opts.rules.upper=[]] - Segments to force to UPPERCASE
54
- * @param {string[]} [opts.rules.lower=[]] - Segments to force to lowercase
8
+ * @param {Object} [opts.rules={}] - Advanced segment transformation rules (supports glob patterns: *, ?, **STRING**)
9
+ * @param {string[]} [opts.rules.leave=[]] - Segments to preserve exactly as-is (case-sensitive, supports globs)
10
+ * @param {string[]} [opts.rules.leaveInsensitive=[]] - Segments to preserve exactly as-is (case-insensitive, supports globs)
11
+ * @param {string[]} [opts.rules.upper=[]] - Segments to force to UPPERCASE (supports globs and **STRING** boundary patterns)
12
+ * @param {string[]} [opts.rules.lower=[]] - Segments to force to lowercase (supports globs and **STRING** boundary patterns)
55
13
  * @returns {string} Valid JavaScript identifier safe for dot-notation property access
56
14
  * @throws {TypeError} When input parameter is not a string
57
15
  *
58
16
  * @description
59
17
  * Sanitize a string into a JS identifier suitable for dot-path usage.
60
- * Core sanitization function that converts arbitrary strings (typically file names) into
61
- * valid JavaScript identifiers following slothlet's API naming conventions.
62
- *
63
- * Sanitization algorithm:
64
- * 1. **Fast path**: If input is already a valid JS identifier, return unchanged
65
- * 2. **Segmentation**: Split on non-identifier characters [^A-Za-z0-9_$]
66
- * 3. **Prefix cleanup**: Remove leading digits/invalid chars from first segment
67
- * 4. **Rule application**: Apply leave/upper/lower rules with precedence
68
- * 5. **Default casing**: First segment respects lowerFirst, others get title case
69
- * 6. **Safety checks**: Ensure result starts with valid identifier character
70
- *
71
- * Rule precedence (applied in order):
72
- * - `leave` rules: Preserve segment exactly as provided
73
- * - `upper` rules: Force segment to UPPERCASE
74
- * - `lower` rules: Force segment to lowercase
75
- * - Default behavior: Apply standard camelCase conversion
76
- *
77
- * Edge case handling:
78
- * - Empty input → "_" (safe fallback identifier)
79
- * - Numeric prefixes → Stripped from first segment
80
- * - All invalid chars → Returns "_" + cleaned content
81
- * - No valid segments → Returns "_"
18
+ * Advanced sanitization function that applies rules intelligently before splitting while
19
+ * maintaining proper camelCase transformation. Uses sophisticated tracking to ensure
20
+ * rule-matched segments are preserved correctly through the transformation process.
82
21
  *
83
22
  * @example
84
23
  * // Basic sanitization (already valid identifiers unchanged)
@@ -93,44 +32,46 @@
93
32
  * sanitizePathName("foo-bar-baz"); // "fooBarBaz" (multi-segment camelCase)
94
33
  *
95
34
  * @example
96
- * // Numeric prefix handling
97
- * sanitizePathName("2autoIP"); // "autoIP" (leading digits stripped)
98
- * sanitizePathName("123-test-file"); // "testFile" (digits stripped, camelCase applied)
35
+ * // Pre-split pattern matching (matches original filename patterns)
36
+ * sanitizePathName("auto-ip", {
37
+ * rules: {
38
+ * upper: ["*-ip"] // Matches before splitting
39
+ * }
40
+ * }); // Result: "autoIP" (ip becomes IP due to *-ip pattern)
99
41
  *
100
42
  * @example
101
- * // First character casing control
102
- * sanitizePathName("My-File"); // "myFile" (lowerFirst=true default)
103
- * sanitizePathName("My-File", { lowerFirst: false }); // "MyFile" (preserve capital first)
104
- * sanitizePathName("API-util", { lowerFirst: false }); // "APIUtil" (preserve capital first)
43
+ * // Complex pattern matching with intelligent tracking
44
+ * sanitizePathName("get-api-status", {
45
+ * rules: {
46
+ * upper: ["*-api-*"] // Matches api in middle of filename
47
+ * }
48
+ * }); // Result: "getAPIStatus" (api becomes API due to pattern)
105
49
  *
106
50
  * @example
107
- * // Advanced rule-based transformation
108
- * sanitizePathName("foo-api-json", {
51
+ * // Boundary-requiring patterns with **STRING** syntax
52
+ * sanitizePathName("buildUrlWithParams", {
109
53
  * rules: {
110
- * leave: ["foo"], // Keep "foo" exactly as-is
111
- * upper: ["api"], // Force "api" to "API"
112
- * lower: ["JSON"] // Force "JSON" to "json"
54
+ * upper: ["**url**"] // Only matches "url" when surrounded by other characters
113
55
  * }
114
- * }); // Result: "fooAPIjson"
56
+ * }); // Result: "buildURLWithParams" (url becomes URL, surrounded by other chars)
115
57
  *
116
- * @example
117
- * // Real-world slothlet file mapping scenarios
118
- * sanitizePathName("auto-ip.mjs"); // "autoIp" (common filename pattern)
119
- * sanitizePathName("parseJSON.mjs"); // "parseJSON" (preserve common acronym)
120
- * sanitizePathName("get-HTTP-status.js"); // "getHTTPStatus" (multi-acronym handling)
121
- * sanitizePathName("root-math.mjs"); // "rootMath" (typical slothlet module name)
58
+ * sanitizePathName("url", {
59
+ * rules: {
60
+ * upper: ["**url**"] // Does NOT match standalone "url" (no surrounding chars)
61
+ * }
62
+ * }); // Result: "url" (unchanged - no surrounding characters)
122
63
  *
123
- * @example
124
- * // Edge cases and safety handling
125
- * sanitizePathName(""); // "_" (empty string fallback)
126
- * sanitizePathName("123"); // "_" (all numeric becomes fallback)
127
- * sanitizePathName("!@#$%"); // "_" (all special chars becomes fallback)
128
- * sanitizePathName("valid@#$invalid"); // "validInvalid" (special chars removed)
64
+ * sanitizePathName("parseJsonData", {
65
+ * rules: {
66
+ * upper: ["**json**"] // Matches "json" surrounded by other characters
67
+ * }
68
+ * }); // Result: "parseJSONData" (json becomes JSON)
129
69
  */
130
70
  export function sanitizePathName(input: string, opts?: {
131
71
  lowerFirst?: boolean;
132
72
  rules?: {
133
73
  leave?: string[];
74
+ leaveInsensitive?: string[];
134
75
  upper?: string[];
135
76
  lower?: string[];
136
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/sanitize.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqFG;AACH,wCAlFW,MAAM,SAEd;IAAuB,UAAU,GAAzB,OAAO;IACO,KAAK,GAC3B;QAA8B,KAAK,GAA3B,MAAM,EAAE;QACc,KAAK,GAA3B,MAAM,EAAE;QACc,KAAK,GAA3B,MAAM,EAAE;KAChB;CAAA,GAAU,MAAM,CAkIlB"}
1
+ {"version":3,"file":"sanitize.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/sanitize.mjs"],"names":[],"mappings":"AAmEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoEG;AACH,wCAjEW,MAAM,SAEd;IAAuB,UAAU,GAAzB,OAAO;IACO,KAAK,GAC3B;QAA8B,KAAK,GAA3B,MAAM,EAAE;QACc,gBAAgB,GAAtC,MAAM,EAAE;QACc,KAAK,GAA3B,MAAM,EAAE;QACc,KAAK,GAA3B,MAAM,EAAE;KAChB;CAAA,GAAU,MAAM,CA+QlB"}
@@ -96,6 +96,20 @@ export type SlothletOptions = {
96
96
  * - Useful for utility functions, constants, or external service connections.
97
97
  */
98
98
  reference?: object;
99
+ /**
100
+ * - Filename sanitization options for API property names.
101
+ * - Controls how file names are converted to valid JavaScript identifiers.
102
+ * - Default behavior: camelCase conversion with lowerFirst=true.
103
+ */
104
+ sanitize?: {
105
+ lowerFirst?: boolean;
106
+ rules?: {
107
+ leave?: string[];
108
+ leaveInsensitive?: string[];
109
+ upper?: string[];
110
+ lower?: string[];
111
+ };
112
+ };
99
113
  };
100
114
  /**
101
115
  * Creates a slothlet API instance with the specified configuration.
@@ -1 +1 @@
1
- {"version":3,"file":"slothlet.d.mts","sourceRoot":"","sources":["../../dist/slothlet.mjs"],"names":[],"mappings":"AA44CA;;;;;;;;;GASG;AACH,kDARW,WAAS,MAAM,UACf,WAAS,MAAM,QAwCzB;AApzCD;;;;;GAKG;AACH,sBAJU,MAAM,CAIoE;AAKpF;;;;;;;GAOG;AACH,mBAJU,MAAM,CAIO;AAEvB;;;;;GAKG;AACH,sBAJU,MAAM,CAIU;AAE1B;;;;;GAKG;AACH,wBAJU,MAAM,CAIY;;;;;;;;;UA4xCd,MAAM;;;;;;WAIN,OAAO;;;;;;;eAGP,MAAM;;;;;;;;YAIN,OAAO;;;;;;;;WAKP,MAAM;;;;;;;eAKN,MAAM;;;;;;cAIN,MAAM;;;;;;gBAGN,MAAM;;AAtzCpB;;;;;;;;GAQG;AACH,mCAJW,eAAe,GACb,OAAO,CAAC,WAAS,MAAM,CAAC,CAiCpC"}
1
+ {"version":3,"file":"slothlet.d.mts","sourceRoot":"","sources":["../../dist/slothlet.mjs"],"names":[],"mappings":"AAi5CA;;;;;;;;;GASG;AACH,kDARW,WAAS,MAAM,UACf,WAAS,MAAM,QAwCzB;AAzzCD;;;;;GAKG;AACH,sBAJU,MAAM,CAIoE;AAKpF;;;;;;;GAOG;AACH,mBAJU,MAAM,CAIO;AAEvB;;;;;GAKG;AACH,sBAJU,MAAM,CAIU;AAE1B;;;;;GAKG;AACH,wBAJU,MAAM,CAIY;;;;;;;;;UAiyCd,MAAM;;;;;;WAIN,OAAO;;;;;;;eAGP,MAAM;;;;;;;;YAIN,OAAO;;;;;;;;WAKP,MAAM;;;;;;;eAKN,MAAM;;;;;;cAIN,MAAM;;;;;;gBAGN,MAAM;;;;;;eAMjB;QAA8B,UAAU,GAA7B,OAAO;QACW,KAAK,GAClC;YAAqC,KAAK,GAA/B,MAAM,EAAE;YACkB,gBAAgB,GAA1C,MAAM,EAAE;YACkB,KAAK,GAA/B,MAAM,EAAE;YACkB,KAAK,GAA/B,MAAM,EAAE;SACrB;KAAA;;AAv0CD;;;;;;;;GAQG;AACH,mCAJW,eAAe,GACb,OAAO,CAAC,WAAS,MAAM,CAAC,CAiCpC"}