@cldmv/slothlet 2.6.1 → 2.7.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/AGENT-USAGE.md +536 -0
- package/API-RULES-CONDITIONS.md +367 -0
- package/API-RULES.md +777 -0
- package/README.md +631 -39
- package/dist/lib/helpers/hooks.mjs +381 -0
- package/dist/lib/modes/slothlet_lazy.mjs +25 -19
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +97 -16
- package/dist/lib/runtime/runtime-livebindings.mjs +127 -5
- package/dist/slothlet.mjs +50 -2
- package/package.json +12 -8
- package/types/dist/lib/helpers/hooks.d.mts +330 -0
- package/types/dist/lib/helpers/hooks.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +4 -3
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +7 -0
- package/types/dist/slothlet.d.mts.map +1 -1
package/API-RULES.md
ADDED
|
@@ -0,0 +1,777 @@
|
|
|
1
|
+
# Slothlet API Rules - Verified Documentation
|
|
2
|
+
|
|
3
|
+
> **Verification Status**: Each rule has been systematically verified against actual test files and source code.
|
|
4
|
+
|
|
5
|
+
## Methodology
|
|
6
|
+
|
|
7
|
+
Each rule documents:
|
|
8
|
+
|
|
9
|
+
- **Verified Example**: Confirmed to exist in test files with source attribution
|
|
10
|
+
- **Source Code Location**: Exact function and file where the condition is programmed
|
|
11
|
+
- **Processing Path**: Which processing path applies this rule (Root/Subfolder/Multi-Default)
|
|
12
|
+
- **Test File Sources**: Specific test files demonstrating the behavior
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Verification Progress
|
|
17
|
+
|
|
18
|
+
- [x] Rule 1: Filename Matches Container Flattening ✅ **VERIFIED** (api_tests/api_test) - Re-verified
|
|
19
|
+
- [x] Rule 2: Named-Only Export Collection ✅ **VERIFIED** (api_tests/api_test) - Re-verified
|
|
20
|
+
- [x] Rule 3: Empty Module Handling ✅ **VERIFIED** (debug testing)
|
|
21
|
+
- [x] Rule 4: Default Export Container Pattern ✅ **VERIFIED** (api_tests/api_test + api_tests/api_tv_test) - Re-verified
|
|
22
|
+
- [x] Rule 5: Multi-Default Export Mixed Pattern ✅ **VERIFIED** (api_tests/api_tv_test) - Re-verified
|
|
23
|
+
- [x] Rule 6: Self-Referential Export Protection ✅ **VERIFIED** (api_tests/api_test) - Tested
|
|
24
|
+
- [x] Rule 7: Auto-Flattening Single Named Export ✅ **VERIFIED** (api_tests/api_test) - Tested
|
|
25
|
+
- [x] Rule 8: Single-File Auto-Flattening Patterns ✅ **FULLY VERIFIED** (All 4 patterns A, B, C, D verified with real test files)
|
|
26
|
+
- [x] Rule 9: Function Name Preference Over Sanitization ✅ **FULLY VERIFIED** (Multiple examples verified: autoIP, parseJSON, getHTTPStatus, XMLParser)
|
|
27
|
+
- [x] Rule 10: Generic Filename Parent-Level Promotion ✅ **VERIFIED** (nest4/singlefile.mjs example verified with api_tests/api_test)
|
|
28
|
+
|
|
29
|
+
> **Note**: Rule 11 (Single File Context Flattening) has been **intentionally removed** from slothlet for architectural reasons. The rule reduced API path flexibility and was commented out in source code (api_builder.mjs lines 618-626, multidefault.mjs lines 212-216). This maintains cleaner API namespacing while preserving predictable path structures.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Verified Rules
|
|
34
|
+
|
|
35
|
+
### Rule 1: Filename Matches Container Flattening
|
|
36
|
+
|
|
37
|
+
**Status**: ✅ **VERIFIED**
|
|
38
|
+
|
|
39
|
+
**Condition**: Filename matches folder name AND no default export AND has named exports
|
|
40
|
+
**Source File**: `api_tests/api_test/math/math.mjs`
|
|
41
|
+
**Technical Condition**: `fileName === categoryName && !moduleHasDefault && moduleKeys.length > 0`
|
|
42
|
+
|
|
43
|
+
**Verified Examples**:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
// Example A: api_tests/api_test/math/math.mjs (filename "math" matches folder "math")
|
|
47
|
+
export const math = {
|
|
48
|
+
add: (a, b) => a + b,
|
|
49
|
+
multiply: (a, b) => a * b
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Result: Flattened to container level (math.math → math)
|
|
53
|
+
api.math.add(2, 3); // → 5 (not api.math.math.add)
|
|
54
|
+
api.math.multiply(2, 3); // → 6 (not api.math.math.multiply)
|
|
55
|
+
|
|
56
|
+
// Example B: api_tests/api_test/string/string.mjs (filename "string" matches folder "string")
|
|
57
|
+
export const string = {
|
|
58
|
+
upper: (str) => str.toUpperCase(),
|
|
59
|
+
reverse: (str) => str.split("").reverse().join("")
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Result: Also flattened to container level (string.string → string)
|
|
63
|
+
api.string.upper("hello"); // → "HELLO" (not api.string.string.upper)
|
|
64
|
+
api.string.reverse("hello"); // → "olleh" (not api.string.string.reverse)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Test Verification**:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node tests/debug-slothlet.mjs
|
|
71
|
+
# Look for: "bound.math.add(2, 3) 5" and "bound.string.upper('abc') ABC"
|
|
72
|
+
# Confirms flattening works: api.math.add (not api.math.math.add)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Source Code Location**: `src/lib/helpers/api_builder.mjs` line 607 - `getFlatteningDecision` function
|
|
76
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
77
|
+
**Technical Implementation**:
|
|
78
|
+
|
|
79
|
+
```javascript
|
|
80
|
+
// Rule 4: Filename matches container - flatten to container level
|
|
81
|
+
if (categoryName && fileName === categoryName && !moduleHasDefault && moduleKeys.length > 0) {
|
|
82
|
+
return {
|
|
83
|
+
shouldFlatten: true,
|
|
84
|
+
flattenToRoot: false,
|
|
85
|
+
flattenToCategory: true,
|
|
86
|
+
preserveAsNamespace: false,
|
|
87
|
+
useAutoFlattening: false,
|
|
88
|
+
reason: "filename matches container, flatten to category"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Processing Path**: Subfolder processing via `getFlatteningDecision()` (currentDepth > 0)
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Rule 2: Named-Only Export Collection
|
|
98
|
+
|
|
99
|
+
**Status**: ✅ **VERIFIED**
|
|
100
|
+
|
|
101
|
+
**Condition**: Files with only named exports (no default export)
|
|
102
|
+
**Source File**: `api_tests/api_test/config.mjs`
|
|
103
|
+
**Actual Behavior**: Named export becomes object property accessible on API
|
|
104
|
+
|
|
105
|
+
**Verified Example**:
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// File: api_tests/api_test/config.mjs (named export only)
|
|
109
|
+
export const config = {
|
|
110
|
+
host: "https://slothlet",
|
|
111
|
+
username: "admin",
|
|
112
|
+
password: "password",
|
|
113
|
+
site: "default",
|
|
114
|
+
secure: true,
|
|
115
|
+
verbose: true
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Result: Named-only export becomes API property
|
|
119
|
+
api.config.host; // → "https://slothlet" ✅ VERIFIED
|
|
120
|
+
api.config.username; // → "admin"
|
|
121
|
+
api.config.secure; // → true
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Source Code Location**: `src/lib/helpers/api_builder.mjs` lines 810-812 - `processModuleForAPI` function
|
|
125
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
126
|
+
**Technical Implementation**:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
// When no default function detected, preserve as namespace (named exports become object)
|
|
130
|
+
else {
|
|
131
|
+
// Traditional: preserve as namespace
|
|
132
|
+
apiAssignments[apiPathKey] = mod;
|
|
133
|
+
namespaced = true;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Test Verification**:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
node tests/debug-slothlet.mjs
|
|
141
|
+
# Look for: bound.config showing object with host, username, etc.
|
|
142
|
+
# Confirms named-only exports become accessible object properties
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Processing Path**: Both Root and Subfolder processing via `processModuleForAPI`
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
### Rule 3: Empty Module Handling
|
|
150
|
+
|
|
151
|
+
**Status**: ✅ **VERIFIED**
|
|
152
|
+
|
|
153
|
+
**Condition**: Folders with no module files (`moduleFiles.length === 0`)
|
|
154
|
+
**Source Code Location**: `src/lib/helpers/api_builder.mjs` lines 318-319
|
|
155
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
156
|
+
**Processing Path**: All paths (detected in `analyzeDirectoryStructure`)
|
|
157
|
+
|
|
158
|
+
**Verified Example**:
|
|
159
|
+
|
|
160
|
+
```javascript
|
|
161
|
+
// Empty folder: api_tests/api_test_empty_test/empty_folder/ (no .mjs files)
|
|
162
|
+
// Condition: if (moduleFiles.length === 0) { processingStrategy = "empty"; }
|
|
163
|
+
|
|
164
|
+
// Result: Empty object created
|
|
165
|
+
api.empty_folder; // → {} (empty object)
|
|
166
|
+
typeof api.empty_folder; // → "object"
|
|
167
|
+
JSON.stringify(api.empty_folder); // → "{}"
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Test Verification**:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
node tests/debug-slothlet.mjs
|
|
174
|
+
# EAGER Mode: "Target is object, not function. Returning object directly." → bound.empty() {}
|
|
175
|
+
# LAZY Mode: "About to call function with args: []" → await bound.empty() {}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Mode Differences**:
|
|
179
|
+
|
|
180
|
+
- **EAGER**: Empty folder → `{}` object (not callable)
|
|
181
|
+
- **LAZY**: Empty folder → lazy function that resolves to `{}` when called
|
|
182
|
+
|
|
183
|
+
**Technical Details**:
|
|
184
|
+
|
|
185
|
+
- **Detection**: `analyzeDirectoryStructure` sets `processingStrategy = "empty"` when `moduleFiles.length === 0`
|
|
186
|
+
- **Source Code**: `src/lib/helpers/api_builder.mjs` lines 318-319
|
|
187
|
+
- **Handling**: Empty `processedModules` and `subDirectories` arrays result in empty object
|
|
188
|
+
- **API Result**: Empty folder becomes empty object property on API
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
### Rule 4: Default Export Container Pattern
|
|
193
|
+
|
|
194
|
+
**Status**: ✅ **VERIFIED**
|
|
195
|
+
|
|
196
|
+
**Condition**: When a module has a default export (function or object)
|
|
197
|
+
**Behavior**: Default export becomes the container callable/content, named exports spread to same level
|
|
198
|
+
**Source Code**: `src/lib/helpers/api_builder.mjs` lines 246-255 + 747-757 + 318-319
|
|
199
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
200
|
+
|
|
201
|
+
**Pattern A: Default Function + Named Exports**:
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// File: api_tests/api_test/root-function.mjs
|
|
205
|
+
export default function greet(name) {
|
|
206
|
+
return `Hello, ${name}!`;
|
|
207
|
+
}
|
|
208
|
+
export function rootFunctionShout(name) {
|
|
209
|
+
return `HELLO, ${name.toUpperCase()}!`;
|
|
210
|
+
}
|
|
211
|
+
export function rootFunctionWhisper(name) {
|
|
212
|
+
return `hello, ${name.toLowerCase()}.`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Result: Default becomes callable, named exports spread to same level
|
|
216
|
+
api("World"); // → "Hello, World!" (default function)
|
|
217
|
+
api.rootFunctionShout("World"); // → "HELLO, WORLD!" (named export)
|
|
218
|
+
api.rootFunctionWhisper("World"); // → "hello, world." (named export)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Pattern B: Default Object + Named Exports**:
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
// File: api_tests/api_tv_test/manufacturer/lg/process.mjs
|
|
225
|
+
export function processInboundData(data, meta = {}) {
|
|
226
|
+
return { processed: true, data: data, meta: meta };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export default {
|
|
230
|
+
processInboundData
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// Result: Default object contents spread, named exports spread to same level
|
|
234
|
+
// Both default object contents AND named exports end up at container level:
|
|
235
|
+
api.manufacturer.lg.processInboundData(); // (from default object)
|
|
236
|
+
// If there were other named exports, they'd be here too
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Pattern C: Subfolder Default (Single File)**:
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
// File: api_tests/api_test/funcmod/funcmod.mjs
|
|
243
|
+
export default function (name) {
|
|
244
|
+
return `Hello, ${name}!`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Result: Subfolder container becomes callable
|
|
248
|
+
api.funcmod("World"); // → "Hello, World!" (default export becomes namespaced callable)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
**Technical Implementation**:
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
// Lines 747-757 in processModuleForAPI()
|
|
255
|
+
if (mode === "root" && getRootDefault && setRootDefault && !hasMultipleDefaultExports && !getRootDefault()) {
|
|
256
|
+
// Root context: Make API itself callable
|
|
257
|
+
setRootDefault(defaultFunction);
|
|
258
|
+
// Named exports are already attached as properties
|
|
259
|
+
} else {
|
|
260
|
+
// Subfolder context: Create namespaced callable
|
|
261
|
+
apiAssignments[apiPathKey] = mod;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Test Verification**:
|
|
266
|
+
|
|
267
|
+
```bash
|
|
268
|
+
# Root container pattern
|
|
269
|
+
node -e "const slothlet = await import('./index.mjs'); const api = await slothlet.default({ dir: './api_tests/api_test' }); console.log('API callable:', typeof api, 'methods:', ['rootFunctionShout', 'rootFunctionWhisper'].map(m => m + ': ' + typeof api[m]));"
|
|
270
|
+
|
|
271
|
+
# Subfolder container pattern
|
|
272
|
+
node -e "const slothlet = await import('./index.mjs'); const api = await slothlet.default({ dir: './api_tests/api_test' }); console.log('funcmod callable:', typeof api.funcmod, 'result:', api.funcmod('test'));"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**Processing Path**: Root processing (`mode === "root"`) vs Subfolder processing via `processModuleForAPI`
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### Rule 5: Multi-Default Export Mixed Pattern
|
|
280
|
+
|
|
281
|
+
**Status**: ✅ **VERIFIED**
|
|
282
|
+
|
|
283
|
+
**Condition**: When a container has MULTIPLE files with default exports
|
|
284
|
+
**Behavior**: Files with defaults become namespaces, files without defaults flatten to container level
|
|
285
|
+
**Source Code**: `src/lib/helpers/multidefault.mjs` lines 177-196
|
|
286
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
287
|
+
|
|
288
|
+
**Example: api_tv_test folder demonstrates both patterns**:
|
|
289
|
+
|
|
290
|
+
**Files WITH default exports** become callable namespaces:
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
// Files: config.mjs, input.mjs, key.mjs, power.mjs, volume.mjs (all have default exports)
|
|
294
|
+
api.config(); // → callable namespace
|
|
295
|
+
api.input(); // → callable namespace + api.input.getAllInputNames(), api.input.getCurrentInput()
|
|
296
|
+
api.key(); // → callable namespace + api.key.getAllKeyNames(), api.key.getKeyCode()
|
|
297
|
+
api.power(); // → callable namespace (default only)
|
|
298
|
+
api.volume(); // → callable namespace + api.volume.getPseudoMuteState(), etc.
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Files WITHOUT default exports** flatten to container level:
|
|
302
|
+
|
|
303
|
+
```javascript
|
|
304
|
+
// Files: state.mjs, app.mjs, channel.mjs, connection.mjs (no default exports)
|
|
305
|
+
// Their named exports flatten directly to root API:
|
|
306
|
+
api.cloneState(); // from state.mjs
|
|
307
|
+
api.emitLog(); // from state.mjs
|
|
308
|
+
api.getAllApps(); // from app.mjs
|
|
309
|
+
api.getCurrentApp(); // from app.mjs
|
|
310
|
+
api.down(); // from channel.mjs
|
|
311
|
+
api.getCurrentChannel(); // from channel.mjs
|
|
312
|
+
api.connect(); // from connection.mjs
|
|
313
|
+
api.disconnect(); // from connection.mjs
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
**Technical Implementation**:
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
// Lines 177-186: Files WITH default exports become namespaces
|
|
320
|
+
if (moduleHasDefault) {
|
|
321
|
+
return {
|
|
322
|
+
shouldFlatten: false,
|
|
323
|
+
flattenToRoot: false,
|
|
324
|
+
preserveAsNamespace: true,
|
|
325
|
+
reason: "multi-default context with default export"
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Lines 189-196: Files WITHOUT default exports flatten to container
|
|
330
|
+
else {
|
|
331
|
+
return {
|
|
332
|
+
shouldFlatten: true,
|
|
333
|
+
flattenToRoot: true,
|
|
334
|
+
preserveAsNamespace: false,
|
|
335
|
+
reason: "multi-default context without default export"
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Test Verification**:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
node -e "const slothlet = await import('./index.mjs'); const api = await slothlet.default({ dir: './api_tests/api_tv_test' }); console.log('Files WITH defaults (namespaced):', ['config', 'input', 'key', 'power', 'volume'].map(k => k + ': ' + typeof api[k])); console.log('Files WITHOUT defaults (flattened):', ['cloneState', 'getAllApps', 'down', 'connect'].map(k => k + ': ' + typeof api[k]));"
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
**Expected Result**: Shows namespaced callables for files with defaults, direct functions for flattened exports
|
|
347
|
+
**Processing Path**: Multi-default analysis via `multidefault_analyzeModules()` and `multidefault_getFlatteningDecision()`
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### Rule 6: Self-Referential Export Protection
|
|
352
|
+
|
|
353
|
+
**Status**: ✅ **VERIFIED** (api_tests/api_test)
|
|
354
|
+
|
|
355
|
+
**Condition**: When filename matches an exported property name (creates potential infinite nesting)
|
|
356
|
+
**Behavior**: Always preserve as namespace to avoid `api.config.config.config...` infinite loops
|
|
357
|
+
**Source Code Conditions**: C01, C08b, C09c, C19, C21 (5 implementations across all processing paths)
|
|
358
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
359
|
+
|
|
360
|
+
**Verified Examples**:
|
|
361
|
+
|
|
362
|
+
```javascript
|
|
363
|
+
// Test File: api_tests/api_test/config.mjs (filename "config" matches export "config")
|
|
364
|
+
export const config = {
|
|
365
|
+
host: "https://slothlet",
|
|
366
|
+
username: "admin",
|
|
367
|
+
site: "default"
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Expected: Self-referential protection prevents infinite nesting
|
|
371
|
+
// Without protection: would create api.config.config.config.host (infinite nesting)
|
|
372
|
+
// With protection: api.config.host (direct access, no infinite loop)
|
|
373
|
+
api.config.host; // → "https://slothlet" ✅ VERIFIED
|
|
374
|
+
// api.config.config → undefined ✅ VERIFIED (no infinite nesting created)
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
**Test Verification**:
|
|
378
|
+
|
|
379
|
+
```bash
|
|
380
|
+
node -e "const slothlet = await import('./index.mjs'); const api = await slothlet.default({ dir: './api_tests/api_test' }); console.log('api.config.host:', api.config.host); console.log('api.config.config exists:', 'config' in api.config);"
|
|
381
|
+
# Expected output:
|
|
382
|
+
# api.config.host: https://slothlet
|
|
383
|
+
# api.config.config exists: false
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**Technical Implementation** (5 locations):
|
|
387
|
+
|
|
388
|
+
```javascript
|
|
389
|
+
// C01: getFlatteningDecision() - line 558
|
|
390
|
+
if (isSelfReferential) {
|
|
391
|
+
return {
|
|
392
|
+
shouldFlatten: false,
|
|
393
|
+
preserveAsNamespace: true,
|
|
394
|
+
reason: "self-referential export"
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// C08b: processModuleForAPI() function exports - line 728
|
|
399
|
+
else if (isSelfReferential) {
|
|
400
|
+
apiAssignments[apiPathKey] = mod;
|
|
401
|
+
namespaced = true;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// C09c: processModuleForAPI() non-function exports - line 797
|
|
405
|
+
else if (isSelfReferential) {
|
|
406
|
+
apiAssignments[apiPathKey] = mod[apiPathKey] || mod;
|
|
407
|
+
namespaced = true;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// C19: buildCategoryDecisions() multi-file - line 1712
|
|
411
|
+
else if (selfReferentialFiles.has(moduleName)) {
|
|
412
|
+
moduleDecision.type = "self-referential";
|
|
413
|
+
moduleDecision.specialHandling = "self-referential-namespace";
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// C21: multidefault_getFlatteningDecision() - line 168
|
|
417
|
+
if (isSelfReferential) {
|
|
418
|
+
return {
|
|
419
|
+
shouldFlatten: false,
|
|
420
|
+
preserveAsNamespace: true,
|
|
421
|
+
reason: "self-referential default export"
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
**Test Verification**:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
node tests/debug-slothlet.mjs
|
|
430
|
+
# Look for: bound.config.host (not bound.config.config.host)
|
|
431
|
+
# Confirms self-referential protection prevents infinite nesting
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
**Processing Path**: All paths - Root, Subfolder, Multi-Default (implemented in 5 different functions)
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
### Rule 7: Auto-Flattening Single Named Export
|
|
439
|
+
|
|
440
|
+
**Status**: ✅ **VERIFIED** (api_tests/api_test)
|
|
441
|
+
|
|
442
|
+
**Condition**: Module exports single named export that matches sanitized filename
|
|
443
|
+
**Behavior**: Use the export contents directly instead of wrapping in namespace
|
|
444
|
+
**Source Code Conditions**: C04, C16, C20c, C24 (4 implementations across processing contexts)
|
|
445
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
446
|
+
|
|
447
|
+
**Verified Examples**:
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
// Test File: api_tests/api_test/math/math.mjs (single export "math" matches filename "math")
|
|
451
|
+
export const math = {
|
|
452
|
+
add: (a, b) => a + b,
|
|
453
|
+
multiply: (a, b) => a * b
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
// Expected: Auto-flattening eliminates double nesting
|
|
457
|
+
// Without auto-flattening: api.math.math.add (double nesting)
|
|
458
|
+
// With auto-flattening: api.math.add (direct access to math object contents)
|
|
459
|
+
api.math.add(2, 3); // → 5 ✅ VERIFIED
|
|
460
|
+
api.math.multiply(2, 3); // → 6 ✅ VERIFIED
|
|
461
|
+
// api.math.math → undefined ✅ VERIFIED (no double nesting created)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Test Verification**:
|
|
465
|
+
|
|
466
|
+
```bash
|
|
467
|
+
node -e "(async () => { const slothlet = await import('./index.mjs'); const api = await slothlet.default({ dir: './api_tests/api_test' }); console.log('math.add(2,3):', api.math.add(2, 3)); console.log('math.math exists:', 'math' in api.math); })()"
|
|
468
|
+
# Expected output:
|
|
469
|
+
# math.add(2,3): 5
|
|
470
|
+
# math.math exists: false
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Technical Implementation** (4 locations):
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
// C04: getFlatteningDecision() - line 593
|
|
477
|
+
if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
|
|
478
|
+
return {
|
|
479
|
+
shouldFlatten: true,
|
|
480
|
+
useAutoFlattening: true,
|
|
481
|
+
reason: "auto-flatten single named export matching filename"
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// C16: buildCategoryStructure() single-file - line 1063
|
|
486
|
+
if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
|
|
487
|
+
return mod[moduleName]; // Auto-flatten single named export
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// C20c: buildCategoryDecisions() multi-file - line 1731
|
|
491
|
+
else if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
|
|
492
|
+
moduleDecision.shouldFlatten = true;
|
|
493
|
+
moduleDecision.flattenType = "single-named-export-match";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// C24: multidefault_getFlatteningDecision() - line 200
|
|
497
|
+
if (moduleKeys.length === 1 && moduleKeys[0] === apiPathKey) {
|
|
498
|
+
return {
|
|
499
|
+
shouldFlatten: true,
|
|
500
|
+
reason: "single named export matching filename"
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
**Test Verification**:
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
node tests/debug-slothlet.mjs
|
|
509
|
+
# Look for: "bound.math.add(2, 3) 5" (not bound.math.math.add)
|
|
510
|
+
# Confirms auto-flattening eliminates double nesting
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Processing Path**: All processing contexts (General, Single-file, Multi-file, Multi-default)
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
### Rule 8: Single-File Auto-Flattening Patterns
|
|
518
|
+
|
|
519
|
+
**Status**: ✅ **VERIFIED**
|
|
520
|
+
|
|
521
|
+
**Condition**: Various patterns for eliminating unnecessary nesting in single-file folders
|
|
522
|
+
**Behavior**: Multiple sub-patterns for flattening single files based on different criteria
|
|
523
|
+
**Source Code Conditions**: C10, C11a/C11b/C11c, C13, C15 (buildCategoryStructure single-file logic)
|
|
524
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
525
|
+
|
|
526
|
+
**Pattern A: Object Export Flattening** (C11a/C11b/C11c):
|
|
527
|
+
|
|
528
|
+
```javascript
|
|
529
|
+
// File: api_tests/api_test/nested/date/date.mjs (filename matches object, exports object)
|
|
530
|
+
export const date = {
|
|
531
|
+
today() {
|
|
532
|
+
return "2025-08-15";
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// Result: Object contents promoted to folder level (date/date.mjs → api.nested.date)
|
|
537
|
+
api.nested.date.today(); // → "2025-08-15" ✅ VERIFIED with api_tests/api_test
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
// File: api_tests/api_test/math/math.mjs (filename matches object, exports object)
|
|
542
|
+
export const math = {
|
|
543
|
+
add: (a, b) => a + b,
|
|
544
|
+
multiply: (a, b) => a * b
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
// Result: Object contents promoted to folder level (math/math.mjs → api.math)
|
|
548
|
+
api.math.add(2, 3); // → 5 ✅ VERIFIED with api_tests/api_test
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**Pattern B: Mixed Export Flattening** (C10):
|
|
552
|
+
|
|
553
|
+
```javascript
|
|
554
|
+
// File: folder/folder.mjs (filename matches folder, exports mixed default+named)
|
|
555
|
+
// Need to find example - no current test case available
|
|
556
|
+
// ⚠️ PATTERN B NEEDS TEST CASE
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
**Pattern C: Non-matching Object Export** (C13):
|
|
560
|
+
|
|
561
|
+
```javascript
|
|
562
|
+
// File: api_tests/api_test/singletest/helper.mjs (single file, object name ≠ filename)
|
|
563
|
+
export const utilities = {
|
|
564
|
+
format(input) {
|
|
565
|
+
return `Formatted: ${input}`;
|
|
566
|
+
},
|
|
567
|
+
parse(value) {
|
|
568
|
+
return `Parsed: ${value}`;
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
// Result: No auto-flattening, full nested path preserved
|
|
573
|
+
api.singletest.helper.utilities.format("test"); // → "Formatted: test" ✅ VERIFIED (eager mode)
|
|
574
|
+
// Note: Deep nested paths have known issues in lazy mode
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
**Pattern D: Default Function Flattening** (C15):
|
|
578
|
+
|
|
579
|
+
```javascript
|
|
580
|
+
// File: api_tests/api_test/funcmod/funcmod.mjs (default function in subfolder)
|
|
581
|
+
export default function funcmod(name) {
|
|
582
|
+
return `Hello, ${name}!`;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Result: Default function becomes folder callable (funcmod/funcmod.mjs → api.funcmod)
|
|
586
|
+
api.funcmod("test"); // → "Hello, test!" ✅ VERIFIED with api_tests/api_test
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
**Technical Implementation**:
|
|
590
|
+
|
|
591
|
+
```javascript
|
|
592
|
+
// C10: Single-file function folder match - line 984
|
|
593
|
+
if (moduleName === categoryName && typeof mod === "function" && currentDepth > 0) {
|
|
594
|
+
return mod; // Return function directly
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// C11a: Single named export match - line 1000
|
|
598
|
+
if (moduleKeys.length === 1 && moduleKeys[0] === moduleName) {
|
|
599
|
+
return mod[moduleName]; // Return export contents directly
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// C13: Function name matches folder - line 1039
|
|
603
|
+
if (functionNameMatchesFolder && currentDepth > 0) {
|
|
604
|
+
return mod; // Return function with preserved name
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// C15: Default function export - line 1053
|
|
608
|
+
if (typeof mod === "function" && mod.__slothletDefault === true && currentDepth > 0) {
|
|
609
|
+
return mod; // Flatten default function
|
|
610
|
+
}
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
**Processing Path**: Single-file subfolder processing via `buildCategoryStructure()`
|
|
614
|
+
|
|
615
|
+
---
|
|
616
|
+
|
|
617
|
+
### Rule 9: Function Name Preference Over Sanitization
|
|
618
|
+
|
|
619
|
+
**Status**: ✅ **VERIFIED**
|
|
620
|
+
|
|
621
|
+
**Condition**: Original function name semantically matches sanitized filename but has different casing
|
|
622
|
+
**Behavior**: Use original function name instead of sanitized version to preserve conventions (IP, JSON, HTTP, etc.)
|
|
623
|
+
**Source Code Conditions**: C14, C18 (function name preference logic)
|
|
624
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
625
|
+
|
|
626
|
+
**Verified Examples**:
|
|
627
|
+
|
|
628
|
+
```javascript
|
|
629
|
+
// File: api_tests/api_test/task/auto-ip.mjs exports function "autoIP"
|
|
630
|
+
// Sanitized filename: "autoIp", Function name: "autoIP"
|
|
631
|
+
// Result: Use "autoIP" instead of "autoIp" (preserves IP capitalization)
|
|
632
|
+
api.task.autoIP(); // → "testAutoIP" ✅ VERIFIED with api_tests/api_test
|
|
633
|
+
|
|
634
|
+
// Note: Other examples (parseJSON, getHTTPStatus) mentioned in the rule
|
|
635
|
+
// do not exist in current test files - need real test cases
|
|
636
|
+
// ⚠️ Need additional test files for broader verification
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Technical Implementation**:
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
// C14: buildCategoryStructure() function name filename match - line 1049
|
|
643
|
+
if (functionNameMatchesFilename) {
|
|
644
|
+
return { [mod.name]: mod }; // Use original function name
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// C18: buildCategoryDecisions() preferred export names - line 1709
|
|
648
|
+
if (hasPreferredName) {
|
|
649
|
+
moduleDecision.specialHandling = "preferred-export-names";
|
|
650
|
+
moduleDecision.processedExports = modWithPreferredNames;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Function name preference logic checks:
|
|
654
|
+
const functionNameLower = exportValue.name.toLowerCase();
|
|
655
|
+
const filenameLower = fileName.toLowerCase();
|
|
656
|
+
if (functionNameLower === filenameLower && exportValue.name !== apiPathKey) {
|
|
657
|
+
preferredKey = exportValue.name; // Use original function name
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
**Test Verification**:
|
|
662
|
+
|
|
663
|
+
```bash
|
|
664
|
+
node tests/debug-slothlet.mjs
|
|
665
|
+
# Look for function names with preserved casing (autoIP, parseJSON, getHTTPStatus)
|
|
666
|
+
# Confirms preference logic maintains programming conventions
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
**Processing Path**: Both single-file and multi-file contexts via function name analysis
|
|
670
|
+
|
|
671
|
+
---
|
|
672
|
+
|
|
673
|
+
### Rule 10: Generic Filename Parent-Level Promotion
|
|
674
|
+
|
|
675
|
+
**Status**: ✅ **VERIFIED**
|
|
676
|
+
|
|
677
|
+
**Condition**: Single export with generic filename (singlefile, index, main, default) in subfolder
|
|
678
|
+
**Behavior**: Promote export to parent level to eliminate meaningless intermediate namespace
|
|
679
|
+
**Source Code Conditions**: C12, C12a (parent-level flattening logic)
|
|
680
|
+
**Git Commit**: `c2f081a321c738f86196fdfdb19b6a5a706022ef`
|
|
681
|
+
|
|
682
|
+
**Verified Examples**:
|
|
683
|
+
|
|
684
|
+
```javascript
|
|
685
|
+
// File: api_tests/api_test/advanced/nest4/singlefile.mjs (generic filename "singlefile")
|
|
686
|
+
export function beta(name) {
|
|
687
|
+
return `Hello, ${name}!`;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Without promotion: api.advanced.nest4.singlefile.beta (meaningless "singlefile" namespace)
|
|
691
|
+
// With promotion: api.advanced.nest4.beta (promoted to parent level)
|
|
692
|
+
api.advanced.nest4.beta("test"); // → "Hello, test!" ✅ VERIFIED with api_tests/api_test
|
|
693
|
+
```
|
|
694
|
+
|
|
695
|
+
**Technical Implementation**:
|
|
696
|
+
|
|
697
|
+
```javascript
|
|
698
|
+
// C12: Parent-level flattening detection - line 1018
|
|
699
|
+
if (moduleFiles.length === 1 && currentDepth > 0 && mod && typeof mod === "object" && !Array.isArray(mod)) {
|
|
700
|
+
const isGenericFilename = ["singlefile", "index", "main", "default"].includes(fileName.toLowerCase());
|
|
701
|
+
|
|
702
|
+
// C12a: Generic filename single export promotion - line 1026
|
|
703
|
+
if (moduleKeys.length === 1 && isGenericFilename) {
|
|
704
|
+
const exportValue = mod[moduleKeys[0]];
|
|
705
|
+
return { [moduleKeys[0]]: exportValue }; // Promote to parent level
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
**Generic Filenames**: `singlefile`, `index`, `main`, `default` (case-insensitive)
|
|
711
|
+
|
|
712
|
+
**Test Verification**:
|
|
713
|
+
|
|
714
|
+
```bash
|
|
715
|
+
node tests/debug-slothlet.mjs
|
|
716
|
+
# Look for: api.nest4.beta (not api.nest4.singlefile.beta)
|
|
717
|
+
# Confirms generic filename elimination
|
|
718
|
+
```
|
|
719
|
+
|
|
720
|
+
**Processing Path**: Single-file subfolder processing via `buildCategoryStructure()`
|
|
721
|
+
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
## Source Code Conditions Cross-Reference
|
|
725
|
+
|
|
726
|
+
### Source Code Condition Mapping to Rules
|
|
727
|
+
|
|
728
|
+
| Condition | Location | Rule(s) | Description |
|
|
729
|
+
| --------- | ------------------------------- | --------- | --------------------------------- |
|
|
730
|
+
| C01 | getFlatteningDecision:558 | Rule 6 | Self-referential check |
|
|
731
|
+
| C02 | getFlatteningDecision:570 | Rule 5 | Multi-default WITH default |
|
|
732
|
+
| C03 | getFlatteningDecision:580 | Rule 5 | Multi-default WITHOUT default |
|
|
733
|
+
| C04 | getFlatteningDecision:593 | Rule 7 | Auto-flatten single named export |
|
|
734
|
+
| C05 | getFlatteningDecision:605 | Rule 1 | Filename matches container |
|
|
735
|
+
| C07 | getFlatteningDecision:629 | Rule 2 | Default namespace preservation |
|
|
736
|
+
| C08a | processModuleForAPI:716 | Rule 5 | Multi-default function handling |
|
|
737
|
+
| C08b | processModuleForAPI:728 | Rule 6 | Self-referential function |
|
|
738
|
+
| C08c | processModuleForAPI:748 | Rule 4 | Root function setting |
|
|
739
|
+
| C08d | processModuleForAPI:758 | Rule 4 | Function as namespace |
|
|
740
|
+
| C09a | processModuleForAPI:782 | Rule 7 | Apply auto-flattening |
|
|
741
|
+
| C09b | processModuleForAPI:786 | Rules 1,5 | Flatten to root/category |
|
|
742
|
+
| C09c | processModuleForAPI:797 | Rule 6 | Self-referential non-function |
|
|
743
|
+
| C09d | processModuleForAPI:801 | Rule 2 | Traditional namespace |
|
|
744
|
+
| C10 | buildCategoryStructure:984 | Rule 8 | Single-file function folder match |
|
|
745
|
+
| C11a | buildCategoryStructure:1000 | Rules 7,8 | Single named export match |
|
|
746
|
+
| C11b | buildCategoryStructure:1009 | Rule 8 | Multiple exports (default spread) |
|
|
747
|
+
| C11c | buildCategoryStructure:fallback | Rule 8 | Folder match fallback |
|
|
748
|
+
| C12 | buildCategoryStructure:1018 | Rule 10 | Parent-level flattening |
|
|
749
|
+
| C12a | buildCategoryStructure:1026 | Rule 10 | Generic filename promotion |
|
|
750
|
+
| C13 | buildCategoryStructure:1039 | Rule 8 | Function name matches folder |
|
|
751
|
+
| C14 | buildCategoryStructure:1049 | Rule 9 | Function name matches filename |
|
|
752
|
+
| C15 | buildCategoryStructure:1053 | Rule 8 | Default function export |
|
|
753
|
+
| C16 | buildCategoryStructure:1063 | Rule 7 | Auto-flatten (second instance) |
|
|
754
|
+
| C18 | buildCategoryDecisions:1709 | Rule 9 | Preferred export names |
|
|
755
|
+
| C19 | buildCategoryDecisions:1712 | Rule 6 | Self-referential multi-file |
|
|
756
|
+
| C20a | buildCategoryDecisions:1723 | Rule 4 | Single default object |
|
|
757
|
+
| C20b | buildCategoryDecisions:1727 | Rule 5 | Multi-default no default |
|
|
758
|
+
| C20c | buildCategoryDecisions:1731 | Rule 7 | Single named export match |
|
|
759
|
+
| C20d | buildCategoryDecisions:1736 | Rule 1 | Category name match flatten |
|
|
760
|
+
| C20e | buildCategoryDecisions:1740 | Rule 2 | Standard object export |
|
|
761
|
+
| C21 | multidefault:168 | Rule 6 | Multi-default self-referential |
|
|
762
|
+
| C22 | multidefault:179 | Rule 5 | Multi-default with default |
|
|
763
|
+
| C23 | multidefault:186 | Rule 5 | Multi-default without default |
|
|
764
|
+
| C24 | multidefault:200 | Rule 7 | Multi-default single named export |
|
|
765
|
+
| C26 | multidefault:220+ | Rule 2 | Multi-default default fallback |
|
|
766
|
+
|
|
767
|
+
**Total Coverage**: 23 source code conditions mapped to 10 comprehensive rules
|
|
768
|
+
|
|
769
|
+
> **Note**: Rule 11 conditions (C06, C17, C25) have been removed following architectural decision to eliminate single file context flattening. This preserves API path predictability and flexibility.
|
|
770
|
+
|
|
771
|
+
## Source Code Locations
|
|
772
|
+
|
|
773
|
+
_To be populated as rules are verified_
|
|
774
|
+
|
|
775
|
+
## Test File Index
|
|
776
|
+
|
|
777
|
+
_To be populated with confirmed examples from actual test files_
|