@cldmv/slothlet 2.7.1 → 2.9.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 +1 -1
- package/README.md +253 -1475
- package/dist/lib/helpers/als-eventemitter.mjs +4 -5
- package/dist/lib/helpers/api_builder/add_api.mjs +237 -0
- package/dist/lib/helpers/api_builder/analysis.mjs +522 -0
- package/dist/lib/helpers/api_builder/construction.mjs +457 -0
- package/dist/lib/helpers/api_builder/decisions.mjs +737 -0
- package/dist/lib/helpers/api_builder.mjs +16 -1567
- package/dist/lib/helpers/utilities.mjs +121 -0
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +44 -17
- package/dist/lib/runtime/runtime-livebindings.mjs +18 -3
- package/dist/lib/runtime/runtime.mjs +3 -3
- package/dist/slothlet.mjs +197 -547
- package/docs/API-RULES-CONDITIONS.md +508 -0
- package/{API-RULES.md → docs/API-RULES.md} +127 -72
- package/index.cjs +2 -1
- package/index.mjs +2 -1
- package/package.json +11 -9
- package/types/dist/lib/helpers/als-eventemitter.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder/add_api.d.mts +60 -0
- package/types/dist/lib/helpers/api_builder/add_api.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder/analysis.d.mts +189 -0
- package/types/dist/lib/helpers/api_builder/analysis.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder/construction.d.mts +107 -0
- package/types/dist/lib/helpers/api_builder/construction.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder/decisions.d.mts +213 -0
- package/types/dist/lib/helpers/api_builder/decisions.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder.d.mts +5 -448
- package/types/dist/lib/helpers/api_builder.d.mts.map +1 -1
- package/types/dist/lib/helpers/utilities.d.mts +120 -0
- package/types/dist/lib/helpers/utilities.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +7 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +8 -0
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +23 -13
- package/types/dist/slothlet.d.mts.map +1 -1
- package/types/index.d.mts +0 -1
- package/API-RULES-CONDITIONS.md +0 -367
package/README.md
CHANGED
|
@@ -33,1438 +33,249 @@ The name might suggest we're taking it easy, but don't be fooled. **Slothlet del
|
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
|
-
## ✨ What's New
|
|
36
|
+
## ✨ What's New
|
|
37
37
|
|
|
38
|
-
###
|
|
38
|
+
### Latest: v2.9 (December 30, 2025)
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
- **Per-Request Context Isolation** - New `api.run()` and `api.scope()` methods for isolated context execution ([Documentation](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md#per-request-context-isolation))
|
|
41
|
+
- API Builder Modularization - Improved maintainability and code organization
|
|
42
|
+
- [View Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.9.md)
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
- **Dual Runtime System**: Choose AsyncLocalStorage or live-bindings for context isolation
|
|
44
|
-
- **4.3x Faster Startup**: Lazy mode achieves 564.17μs vs 2.45ms in eager mode
|
|
45
|
-
- **Copy-Left Materialization**: Once loaded, modules stay materialized for optimal performance
|
|
46
|
-
- **Zero Dependencies**: Pure Node.js implementation with no external dependencies
|
|
44
|
+
### Recent Releases
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
- **v2.8** - NPM security fixes and package workflow updates ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.8.md))
|
|
47
|
+
- **v2.7** - Security updates ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.7.md))
|
|
48
|
+
- **v2.6** - Hook System with 4 interceptor types ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.6.md))
|
|
49
|
+
- **v2.5** - Architectural consolidation and API consistency ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.5.md))
|
|
50
|
+
- **v2.4** - Multi-default export handling ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.4.md))
|
|
51
|
+
- **v2.3** - EventEmitter & Class Context Propagation ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.3.md))
|
|
52
|
+
- **v2.2** - Case preservation options ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.2.md))
|
|
53
|
+
- **v2.1** - Advanced sanitization patterns ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.1.md))
|
|
54
|
+
- **v2.0** - Complete Architectural Rewrite ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.0.md))
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
- **Live-Binding System**: Dynamic context and reference binding with runtime coordination
|
|
52
|
-
- **Smart Function Naming**: Preserves original capitalization (`autoIP`, `parseJSON`, `getHTTPStatus`)
|
|
53
|
-
- **Multi-Execution Environments**: Singleton, VM, worker, fork isolation modes (experimental)
|
|
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
|
-
|
|
63
|
-
### 📊 **Performance Optimizations**
|
|
64
|
-
|
|
65
|
-
- **Startup**: Lazy mode 4.3x faster (564.17μs vs 2.45ms)
|
|
66
|
-
- **Function Calls**: Eager mode 1.1x faster (0.65μs vs 0.72μs) after materialization
|
|
67
|
-
- **Memory**: On-demand loading scales with actual usage
|
|
68
|
-
- **Predictability**: Consistent performance characteristics per mode
|
|
69
|
-
|
|
70
|
-
### 🔄 **Context Propagation (v2.3)** ⭐ NEW
|
|
71
|
-
|
|
72
|
-
- **EventEmitter Context Propagation**: Automatic context preservation across EventEmitter callbacks using AsyncResource patterns
|
|
73
|
-
- **Class Instance Context Propagation**: Automatic context preservation across class method calls with transparent wrapping
|
|
74
|
-
- **AsyncResource Integration**: Production-ready context management following Node.js best practices
|
|
75
|
-
- **Zero Configuration**: Works automatically with TCP servers, HTTP servers, and any EventEmitter-based patterns
|
|
76
|
-
|
|
77
|
-
### 🎣 **Hook System (v2.6.4)** ⭐ NEW
|
|
78
|
-
|
|
79
|
-
- **3-Hook Types**: `before` (modify args or cancel), `after` (transform results), `always` (observe final result)
|
|
80
|
-
- **Cross-Mode Compatibility**: Works seamlessly across all 4 combinations (eager/lazy × async/live)
|
|
81
|
-
- **Pattern Matching**: Target specific functions or use wildcards (`math.*`, `*.add`, `**`)
|
|
82
|
-
- **Priority Control**: Order hook execution with numeric priorities
|
|
83
|
-
- **Runtime Control**: Enable/disable hooks at runtime, globally or by pattern
|
|
84
|
-
- **Short-Circuit Support**: Cancel execution and return custom values from `before` hooks
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## 🚀 Key Features
|
|
89
|
-
|
|
90
|
-
### 🎯 **Dual Loading Strategies**
|
|
91
|
-
|
|
92
|
-
- **Eager Loading**: Immediate loading for maximum performance in production environments
|
|
93
|
-
- **Lazy Loading**: Copy-left materialization with look-ahead proxies (4.3x faster startup, 1.1x slower calls after materialization)
|
|
94
|
-
|
|
95
|
-
> [!IMPORTANT]
|
|
96
|
-
> **Function Call Patterns:**
|
|
97
|
-
>
|
|
98
|
-
> - **Lazy Mode**: ALL function calls must be awaited (`await api.math.add(2, 3)`) due to materialization process
|
|
99
|
-
> - **Eager Mode**: Functions behave as originally defined - sync functions are sync (`api.math.add(2, 3)`), async functions are async (`await api.async.process()`)
|
|
100
|
-
|
|
101
|
-
### ⚡ Performance Excellence
|
|
102
|
-
|
|
103
|
-
- **📊 For comprehensive performance analysis, benchmarks, and recommendations, see [PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)**
|
|
104
|
-
|
|
105
|
-
### 🔧 **Smart API Management**
|
|
106
|
-
|
|
107
|
-
- **Callable Interface**: Use `slothlet(options)` for direct API creation
|
|
108
|
-
- **Smart Flattening**: Intelligent rules create clean APIs (`math/math.mjs` → `api.math`, `connection.mjs` → `api.connect()`)
|
|
109
|
-
- **Intelligent Naming**: Dash-separated names convert automatically (`root-math.mjs` → `api.rootMath`)
|
|
110
|
-
- **Function Name Preservation**: Maintains original capitalization (`auto-ip.mjs` with `autoIP` → `api.autoIP`)
|
|
111
|
-
- **Hybrid Exports**: Support for callable APIs with methods, default + named exports, and mixed patterns
|
|
112
|
-
|
|
113
|
-
> [!TIP]
|
|
114
|
-
> **📁 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)**
|
|
115
|
-
|
|
116
|
-
> [!NOTE]
|
|
117
|
-
> **🔍 For detailed technical documentation on API transformation rules:**
|
|
118
|
-
>
|
|
119
|
-
> - **[API-RULES.md](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES.md)** - Verified API transformation rules with examples and test cases
|
|
120
|
-
> - **[API-RULES-CONDITIONS.md](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES-CONDITIONS.md)** - Complete technical reference of all conditional logic that controls API generation
|
|
121
|
-
|
|
122
|
-
### 🔗 **Advanced Binding System**
|
|
123
|
-
|
|
124
|
-
- **Live Bindings**: Dynamic context and reference binding for runtime API mutation
|
|
125
|
-
- **Context Isolation**: Dual runtime options for per-instance context isolation with seamless integration
|
|
126
|
-
- **Copy-Left Preservation**: Materialized functions stay materialized, preserving performance gains
|
|
127
|
-
- **Bubble-Up Updates**: Parent API synchronization ensures consistency across the API tree
|
|
128
|
-
- **Mixed Module Support**: Seamlessly blend ESM and CommonJS modules in the same API
|
|
129
|
-
|
|
130
|
-
### 🛠 **Developer Experience**
|
|
131
|
-
|
|
132
|
-
- **Enhanced Error Handling**: Clear JavaScript errors with module suggestions and descriptive errors (planned for v3.0.0)
|
|
133
|
-
- **TypeScript-Friendly**: Comprehensive JSDoc annotations for excellent editor support with auto-generated declarations
|
|
134
|
-
- **Configurable Debug**: Detailed logging for development and troubleshooting via CLI flags or environment variables
|
|
135
|
-
- **Multiple Instances**: Parameter-based isolation for complex applications with instance ID management
|
|
136
|
-
- **Development Checks**: Built-in environment detection with silent production behavior
|
|
137
|
-
|
|
138
|
-
### 🏗 **Architecture & Compatibility**
|
|
139
|
-
|
|
140
|
-
- **ESM-First**: Built for modern JavaScript with full ES module support
|
|
141
|
-
- **Universal Loading**: CommonJS and ESM files work together seamlessly
|
|
142
|
-
- **Zero Dependencies**: Lightweight footprint with no external dependencies
|
|
143
|
-
- **Cross-Platform**: Works seamlessly across all Node.js environments
|
|
144
|
-
- **Extensible**: Modular architecture with flexible API composition patterns
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## 📦 Installation
|
|
149
|
-
|
|
150
|
-
### Requirements
|
|
151
|
-
|
|
152
|
-
- **Node.js v12.20.0 or higher** (for ESM support with `import`/`export`)
|
|
153
|
-
- **Node.js v16.4.0 or higher** (recommended for AsyncLocalStorage runtime - `runtime: "async"`)
|
|
154
|
-
|
|
155
|
-
> [!IMPORTANT]
|
|
156
|
-
> **v2.x Runtime Options**: Slothlet v2.x supports two runtime systems:
|
|
157
|
-
>
|
|
158
|
-
> - **AsyncLocalStorage Runtime** (`runtime: "async"`) - Default, requires Node.js v16.4.0+ for context isolation
|
|
159
|
-
> - **Live Bindings Runtime** (`runtime: "live"`) - Advanced system, works on Node.js v12.20.0+ without AsyncLocalStorage
|
|
160
|
-
>
|
|
161
|
-
> Both runtimes provide full live-binding capabilities with `self`, `context`, and `reference` support across ESM and CommonJS modules. Use `runtime: "live"` for older Node.js versions or advanced binding scenarios.
|
|
162
|
-
|
|
163
|
-
### Install
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
npm install @cldmv/slothlet
|
|
167
|
-
```
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## 🚀 Quick Start
|
|
172
|
-
|
|
173
|
-
### ESM (ES Modules)
|
|
174
|
-
|
|
175
|
-
```javascript
|
|
176
|
-
import slothlet from "@cldmv/slothlet";
|
|
177
|
-
|
|
178
|
-
// Direct usage - eager mode by default (auto-detects callable interface)
|
|
179
|
-
const api = await slothlet({
|
|
180
|
-
dir: "./api",
|
|
181
|
-
context: { user: "alice" }
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
// Eager mode: Functions behave as originally defined
|
|
185
|
-
const result = api.math.add(2, 3); // Sync function - no await needed
|
|
186
|
-
const greeting = api("World"); // Instant if callable
|
|
187
|
-
|
|
188
|
-
// Original async functions still need await in eager mode
|
|
189
|
-
const asyncResult = await api.async.processData({ data: "async" });
|
|
190
|
-
|
|
191
|
-
// Access both ESM and CJS modules seamlessly
|
|
192
|
-
const esmResult = api.mathEsm.multiply(4, 5); // 20 (sync)
|
|
193
|
-
const cjsResult = await api.mathCjs.divide(10, 2); // 5 (if originally async)
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
### CommonJS (CJS)
|
|
197
|
-
|
|
198
|
-
```javascript
|
|
199
|
-
const slothlet = require("@cldmv/slothlet");
|
|
200
|
-
|
|
201
|
-
// Same usage pattern works with CommonJS
|
|
202
|
-
const api = await slothlet({
|
|
203
|
-
dir: "./api",
|
|
204
|
-
context: { env: "production" }
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
const result = api.math.multiply(4, 5); // 20
|
|
208
|
-
const mixedResult = await api.interop.processData({ data: "test" }); // CJS+ESM interop
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Runtime Selection
|
|
212
|
-
|
|
213
|
-
```javascript
|
|
214
|
-
// AsyncLocalStorage runtime (default) - requires Node.js v16.4.0+
|
|
215
|
-
const apiAsync = await slothlet({
|
|
216
|
-
dir: "./api",
|
|
217
|
-
runtime: "async", // or "asynclocalstorage"
|
|
218
|
-
context: { user: "alice" }
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
// Live bindings runtime - works on Node.js v12.20.0+
|
|
222
|
-
const apiLive = await slothlet({
|
|
223
|
-
dir: "./api",
|
|
224
|
-
runtime: "live", // or "livebindings"
|
|
225
|
-
context: { user: "bob" }
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
// Both provide identical live-binding capabilities
|
|
229
|
-
import { self, context, reference } from "@cldmv/slothlet/runtime";
|
|
230
|
-
// context.user is available in your modules regardless of runtime choice
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
### Lazy Loading Mode
|
|
234
|
-
|
|
235
|
-
```javascript
|
|
236
|
-
import slothlet from "@cldmv/slothlet";
|
|
237
|
-
|
|
238
|
-
// Lazy mode with copy-left materialization
|
|
239
|
-
const api = await slothlet({
|
|
240
|
-
mode: "lazy", // New preferred syntax
|
|
241
|
-
dir: "./api",
|
|
242
|
-
apiDepth: 3
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// Or use legacy syntax (still supported)
|
|
246
|
-
const apiLegacy = await slothlet({
|
|
247
|
-
lazy: true, // Legacy syntax
|
|
248
|
-
dir: "./api",
|
|
249
|
-
apiDepth: 3
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// First access: ~310μs (materialization overhead)
|
|
253
|
-
const result1 = await api.math.add(2, 3);
|
|
254
|
-
|
|
255
|
-
// Subsequent access: ~0.5μs (materialized function)
|
|
256
|
-
const result2 = await api.math.add(5, 7); // 700x faster than first call!
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
### Advanced Configuration
|
|
260
|
-
|
|
261
|
-
```javascript
|
|
262
|
-
import slothlet from "@cldmv/slothlet";
|
|
263
|
-
|
|
264
|
-
const api = await slothlet({
|
|
265
|
-
dir: "./api",
|
|
266
|
-
mode: "eager", // New: Loading strategy (lazy/eager)
|
|
267
|
-
engine: "singleton", // New: Execution environment
|
|
268
|
-
api_mode: "auto", // API structure behavior
|
|
269
|
-
apiDepth: Infinity, // Directory traversal depth
|
|
270
|
-
debug: false, // Enable verbose logging
|
|
271
|
-
context: {
|
|
272
|
-
// Injected into live-binding
|
|
273
|
-
user: "alice",
|
|
274
|
-
env: "production",
|
|
275
|
-
config: { timeout: 5000 }
|
|
276
|
-
},
|
|
277
|
-
reference: {
|
|
278
|
-
// Merged into API root
|
|
279
|
-
version: "2.0.0",
|
|
280
|
-
helpers: {
|
|
281
|
-
/* ... */
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
sanitize: {
|
|
285
|
-
// 🔧 NEW: Control API property naming
|
|
286
|
-
lowerFirst: false, // Keep first character casing
|
|
287
|
-
rules: {
|
|
288
|
-
leave: ["parseJSON", "autoIP"], // Preserve exact names
|
|
289
|
-
leaveInsensitive: ["*xml*"], // Case-insensitive preservation
|
|
290
|
-
upper: ["**url**", "api", "http*"], // Force uppercase (including boundary patterns)
|
|
291
|
-
lower: ["id", "uuid", "*id"] // Force lowercase
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### Sanitize Options Examples
|
|
298
|
-
|
|
299
|
-
Transform module filenames into clean, professional API property names with sophisticated control:
|
|
300
|
-
|
|
301
|
-
```javascript
|
|
302
|
-
// Without sanitize options (default behavior)
|
|
303
|
-
const api = await slothlet({ dir: "./api" });
|
|
304
|
-
// Files: build-url-with-params.mjs, parse-json-data.mjs, auto-ip.mjs
|
|
305
|
-
// Result: api.buildUrlWithParams, api.parseJsonData, api.autoIp
|
|
306
|
-
|
|
307
|
-
// With sanitize options (custom naming control)
|
|
308
|
-
const api = await slothlet({
|
|
309
|
-
dir: "./api",
|
|
310
|
-
sanitize: {
|
|
311
|
-
lowerFirst: false, // Keep first character casing
|
|
312
|
-
preserveAllUpper: true, // Preserve identifiers like "COMMON_APPS"
|
|
313
|
-
preserveAllLower: false, // Transform identifiers like "common_apps"
|
|
314
|
-
rules: {
|
|
315
|
-
leave: ["parseJSON"], // Exact match preservation (case-sensitive)
|
|
316
|
-
leaveInsensitive: ["*xml*"], // Case-insensitive preservation
|
|
317
|
-
upper: ["**url**", "ip", "http*"], // Force uppercase transformations
|
|
318
|
-
lower: ["id", "*id"] // Force lowercase transformations
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
});
|
|
322
|
-
// Result: api.buildURLWithParams, api.parseJSON, api.autoIP
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
**Comprehensive Sanitize Configuration:**
|
|
326
|
-
|
|
327
|
-
```javascript
|
|
328
|
-
const api = await slothlet({
|
|
329
|
-
dir: "./api",
|
|
330
|
-
sanitize: {
|
|
331
|
-
// Basic Options
|
|
332
|
-
lowerFirst: true, // Default: lowercase first character for camelCase
|
|
333
|
-
preserveAllUpper: true, // Keep "COMMON_APPS" unchanged
|
|
334
|
-
preserveAllLower: false, // Transform "common_apps" → "commonApps"
|
|
335
|
-
|
|
336
|
-
rules: {
|
|
337
|
-
// Preserve exact matches (case-sensitive)
|
|
338
|
-
leave: ["parseJSON", "autoIP", "getHTTPStatus"],
|
|
339
|
-
|
|
340
|
-
// Preserve patterns (case-insensitive)
|
|
341
|
-
leaveInsensitive: ["*xml*", "*html*"],
|
|
342
|
-
|
|
343
|
-
// Force uppercase transformations
|
|
344
|
-
upper: [
|
|
345
|
-
"api", // Exact: "api" → "API"
|
|
346
|
-
"http*", // Glob: "httpGet" → "HTTPGet"
|
|
347
|
-
"**url**", // Boundary: "buildUrlPath" → "buildURLPath"
|
|
348
|
-
"**json**" // Boundary: "parseJsonData" → "parseJSONData"
|
|
349
|
-
],
|
|
350
|
-
|
|
351
|
-
// Force lowercase transformations
|
|
352
|
-
lower: [
|
|
353
|
-
"id", // Exact: "ID" → "id"
|
|
354
|
-
"*id", // Glob: "userId" → "userid"
|
|
355
|
-
"uuid*" // Glob: "UUIDGenerator" → "uuidGenerator"
|
|
356
|
-
]
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
```
|
|
361
|
-
|
|
362
|
-
**Sanitize Pattern Types:**
|
|
363
|
-
|
|
364
|
-
- **Exact Match**: `"parseJSON"` - Matches exact string only
|
|
365
|
-
- **Glob Patterns**:
|
|
366
|
-
- `"*json*"` - Matches anywhere in string (`parseJsonData`)
|
|
367
|
-
- `"auto*"` - Matches at start (`autoGenerateId`)
|
|
368
|
-
- `"*id"` - Matches at end (`userId`)
|
|
369
|
-
- **Boundary Patterns**:
|
|
370
|
-
- `"**url**"` - Only matches when surrounded by characters (`buildUrlPath` ✓, `url` ✗)
|
|
371
|
-
- `"**json**"` - Matches `parseJsonData` but not standalone `json`
|
|
372
|
-
- **Case Control**:
|
|
373
|
-
- `leave` - Case-sensitive exact preservation
|
|
374
|
-
- `leaveInsensitive` - Case-insensitive preservation
|
|
375
|
-
- `preserveAllUpper`/`preserveAllLower` - Automatic case detection
|
|
376
|
-
|
|
377
|
-
**Advanced Pattern Examples:**
|
|
378
|
-
|
|
379
|
-
```javascript
|
|
380
|
-
// File transformations with patterns:
|
|
381
|
-
sanitizePathName("build-url-with-params", {
|
|
382
|
-
rules: { upper: ["**url**"] }
|
|
383
|
-
}); // → "buildURLWithParams"
|
|
384
|
-
|
|
385
|
-
sanitizePathName("parse-json-data", {
|
|
386
|
-
rules: { upper: ["**json**"] }
|
|
387
|
-
}); // → "parseJSONData"
|
|
388
|
-
|
|
389
|
-
sanitizePathName("get-http-status", {
|
|
390
|
-
rules: { upper: ["http*"] }
|
|
391
|
-
}); // → "getHTTPStatus"
|
|
392
|
-
|
|
393
|
-
sanitizePathName("validate-user-id", {
|
|
394
|
-
rules: { lower: ["*id"] }
|
|
395
|
-
}); // → "validateUserid"
|
|
396
|
-
|
|
397
|
-
sanitizePathName("XML_PARSER", {
|
|
398
|
-
preserveAllUpper: true
|
|
399
|
-
}); // → "XML_PARSER" (preserved)
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
### Multiple Instances
|
|
403
|
-
|
|
404
|
-
In v2.x, each call to `slothlet(options)` automatically creates a new isolated instance with its own context and configuration:
|
|
405
|
-
|
|
406
|
-
#### ESM (ES Modules)
|
|
407
|
-
|
|
408
|
-
```javascript
|
|
409
|
-
import slothlet from "@cldmv/slothlet";
|
|
410
|
-
|
|
411
|
-
// Each call creates a new isolated instance automatically
|
|
412
|
-
const api1 = await slothlet({ dir: "./api1", context: { tenant: "alice" } });
|
|
413
|
-
const api2 = await slothlet({ dir: "./api2", context: { tenant: "bob" } });
|
|
414
|
-
|
|
415
|
-
// Instances are completely isolated
|
|
416
|
-
console.log(api1.context.tenant); // "alice"
|
|
417
|
-
console.log(api2.context.tenant); // "bob"
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
#### CommonJS (CJS)
|
|
421
|
-
|
|
422
|
-
```javascript
|
|
423
|
-
const slothlet = require("@cldmv/slothlet");
|
|
424
|
-
|
|
425
|
-
// Each call creates a new isolated instance automatically
|
|
426
|
-
const api1 = await slothlet({ dir: "./api1", context: { tenant: "alice" } });
|
|
427
|
-
const api2 = await slothlet({ dir: "./api2", context: { tenant: "bob" } });
|
|
428
|
-
|
|
429
|
-
// Instances are completely isolated with their own AsyncLocalStorage contexts
|
|
430
|
-
console.log(api1.context.tenant); // "alice"
|
|
431
|
-
console.log(api2.context.tenant); // "bob"
|
|
432
|
-
```
|
|
433
|
-
|
|
434
|
-
> [!NOTE]
|
|
435
|
-
> **v2.x Simplification**: Unlike v1.x which required query string parameters or `withInstanceId()` methods, v2.x automatically creates isolated instances with each `slothlet()` call, using your chosen runtime system (`async` or `live`) for complete context separation.
|
|
436
|
-
|
|
437
|
-
---
|
|
438
|
-
|
|
439
|
-
## 📚 API Reference
|
|
440
|
-
|
|
441
|
-
### Core Methods
|
|
442
|
-
|
|
443
|
-
#### `slothlet(options)` ⇒ `Promise<object>`
|
|
444
|
-
|
|
445
|
-
Creates and loads an API instance with the specified configuration.
|
|
446
|
-
|
|
447
|
-
**Parameters:**
|
|
448
|
-
|
|
449
|
-
| Param | Type | Description |
|
|
450
|
-
| ------- | -------- | --------------------- |
|
|
451
|
-
| options | `object` | Configuration options |
|
|
452
|
-
|
|
453
|
-
**Returns:** `Promise<object>` - The bound API object
|
|
454
|
-
|
|
455
|
-
**Options:**
|
|
456
|
-
|
|
457
|
-
| Option | Type | Default | Description |
|
|
458
|
-
| ----------- | --------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
459
|
-
| `dir` | `string` | `"api"` | Directory to load API modules from. Can be absolute or relative path. If relative, resolved from process.cwd(). |
|
|
460
|
-
| `lazy` | `boolean` | `false` | **Legacy** loading strategy - `true` for lazy loading (on-demand), `false` for eager loading (immediate). Use `mode` option instead. |
|
|
461
|
-
| `mode` | `string` | - | **New** loading mode - `"lazy"` for on-demand loading, `"eager"` for immediate loading. Takes precedence over `lazy` option. Also supports execution modes for backward compatibility. |
|
|
462
|
-
| `engine` | `string` | `"singleton"` | **New** execution environment mode - `"singleton"`, `"vm"`, `"worker"`, or `"fork"` |
|
|
463
|
-
| `runtime` | `string` | `"async"` | Runtime binding system: `"async"` for AsyncLocalStorage-based context isolation (default, requires Node.js v16.4.0+), `"live"` for advanced live-binding system (works on Node.js v12.20.0+). Both provide full live-binding capabilities. |
|
|
464
|
-
| `apiDepth` | `number` | `Infinity` | Directory traversal depth control - `0` for root only, `Infinity` for all levels |
|
|
465
|
-
| `debug` | `boolean` | `false` | Enable verbose logging. Can also be set via `--slothletdebug` command line flag or `SLOTHLET_DEBUG=true` environment variable |
|
|
466
|
-
| `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) |
|
|
467
|
-
| `context` | `object` | `{}` | Context data object injected into live-binding `context` reference. Available to all loaded modules via `import { context } from "@cldmv/slothlet/runtime"` |
|
|
468
|
-
| `reference` | `object` | `{}` | Reference object merged into the API root level. Properties not conflicting with loaded modules are added directly to the API |
|
|
469
|
-
| `sanitize` | `object` | `{}` | **🔧 NEW**: Advanced filename-to-API transformation control. Options: `lowerFirst` (boolean), `preserveAllUpper` (boolean), `preserveAllLower` (boolean), `rules` object with `leave` (exact case-sensitive), `leaveInsensitive` (case-insensitive), `upper`/`lower` arrays. Supports exact matches, glob patterns (`*json*`, `http*`), and boundary patterns (`**url**` for surrounded matches only) |
|
|
470
|
-
|
|
471
|
-
#### ✨ Current Option Format
|
|
472
|
-
|
|
473
|
-
The option structure has been improved for better clarity:
|
|
474
|
-
|
|
475
|
-
```javascript
|
|
476
|
-
// ✅ New recommended syntax
|
|
477
|
-
const api = await slothlet({
|
|
478
|
-
mode: "lazy", // Loading strategy: "lazy" | "eager"
|
|
479
|
-
engine: "singleton", // Execution environment: "singleton" | "vm" | "worker" | "fork"
|
|
480
|
-
dir: "./api"
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
// ✅ Legacy syntax (still fully supported)
|
|
484
|
-
const api = await slothlet({
|
|
485
|
-
lazy: true, // Boolean loading strategy
|
|
486
|
-
mode: "singleton", // Execution environment (legacy placement)
|
|
487
|
-
dir: "./api"
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
// ✅ Mixed usage (mode takes precedence)
|
|
491
|
-
const api = await slothlet({
|
|
492
|
-
lazy: false, // Will be overridden
|
|
493
|
-
mode: "lazy", // Takes precedence - results in lazy loading
|
|
494
|
-
engine: "singleton"
|
|
495
|
-
});
|
|
496
|
-
```
|
|
497
|
-
|
|
498
|
-
**Benefits of the new syntax:**
|
|
499
|
-
|
|
500
|
-
- **Clearer separation**: `mode` for loading strategy, `engine` for execution environment
|
|
501
|
-
- **Better discoverability**: String values are more self-documenting than boolean flags
|
|
502
|
-
- **Future-proof**: Easier to extend with additional loading strategies
|
|
503
|
-
- **Backward compatible**: All existing code continues to work unchanged
|
|
504
|
-
|
|
505
|
-
#### `slothlet.getApi()` ⇒ `object`
|
|
506
|
-
|
|
507
|
-
Returns the raw API object (Proxy or plain object).
|
|
508
|
-
|
|
509
|
-
**Returns:** `function | object` - The raw API object or function
|
|
510
|
-
|
|
511
|
-
#### `slothlet.getBoundApi()` ⇒ `object`
|
|
512
|
-
|
|
513
|
-
Returns the bound API object with context and reference.
|
|
514
|
-
|
|
515
|
-
**Returns:** `function | object` - The bound API object or function with live bindings and context
|
|
516
|
-
|
|
517
|
-
#### `slothlet.isLoaded()` ⇒ `boolean`
|
|
518
|
-
|
|
519
|
-
Returns true if the API is loaded.
|
|
520
|
-
|
|
521
|
-
**Returns:** `boolean` - Whether the API has been loaded
|
|
522
|
-
|
|
523
|
-
#### `slothlet.shutdown()` ⇒ `Promise<void>`
|
|
524
|
-
|
|
525
|
-
Gracefully shuts down the API and performs comprehensive resource cleanup to prevent hanging processes.
|
|
526
|
-
|
|
527
|
-
**Cleanup includes:**
|
|
528
|
-
- Hook manager state and registered hooks
|
|
529
|
-
- AsyncLocalStorage context and bindings
|
|
530
|
-
- EventEmitter listeners and AsyncResource instances (including third-party libraries)
|
|
531
|
-
- Instance data and runtime coordination
|
|
532
|
-
|
|
533
|
-
**Returns:** `Promise<void>` - Resolves when shutdown is complete
|
|
534
|
-
|
|
535
|
-
> [!IMPORTANT]
|
|
536
|
-
> **🛡️ Process Cleanup**: The shutdown method now performs comprehensive cleanup of all EventEmitter listeners created after slothlet loads, including those from third-party libraries like pg-pool. This prevents hanging AsyncResource instances that could prevent your Node.js process from exiting cleanly.
|
|
537
|
-
|
|
538
|
-
> [!NOTE]
|
|
539
|
-
> **📚 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)**
|
|
540
|
-
|
|
541
|
-
### Live Bindings
|
|
542
|
-
|
|
543
|
-
Access live-bound references in your API modules:
|
|
544
|
-
|
|
545
|
-
```javascript
|
|
546
|
-
// Create API with reference functions
|
|
547
|
-
const api = await slothlet({
|
|
548
|
-
dir: "./api",
|
|
549
|
-
reference: {
|
|
550
|
-
md5: (str) => crypto.createHash("md5").update(str).digest("hex"),
|
|
551
|
-
version: "2.0.0",
|
|
552
|
-
utils: { format: (msg) => `[LOG] ${msg}` }
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
```javascript
|
|
558
|
-
// In your API modules (ESM)
|
|
559
|
-
import { self, context, reference } from "@cldmv/slothlet/runtime";
|
|
560
|
-
|
|
561
|
-
export function myFunction() {
|
|
562
|
-
console.log(context.user); // Access live context
|
|
563
|
-
return self.otherModule.helper(); // Access other API modules
|
|
564
|
-
|
|
565
|
-
// Reference functions are available directly on self
|
|
566
|
-
const hash = self.md5("hello world"); // Access reference function
|
|
567
|
-
console.log(self.version); // Access reference data
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// Mixed module example (ESM accessing CJS)
|
|
571
|
-
export function processData(data) {
|
|
572
|
-
// Call a CJS module from ESM
|
|
573
|
-
const processed = self.cjsModule.process(data);
|
|
574
|
-
|
|
575
|
-
// Use reference utilities directly
|
|
576
|
-
const logged = self.utils.format(`Processed: ${processed}`);
|
|
577
|
-
return self.md5(logged); // Hash the result
|
|
578
|
-
}
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
```javascript
|
|
582
|
-
// In your CJS modules
|
|
583
|
-
const { self, context, reference } = require("@cldmv/slothlet/runtime");
|
|
584
|
-
|
|
585
|
-
function cjsFunction(data) {
|
|
586
|
-
console.log(context.env); // Access live context
|
|
587
|
-
|
|
588
|
-
// Reference functions available directly on self
|
|
589
|
-
const hash = self.md5(data); // Direct access to reference function
|
|
590
|
-
|
|
591
|
-
return self.esmModule.transform(hash); // Access ESM modules from CJS
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
module.exports = { cjsFunction };
|
|
595
|
-
```
|
|
596
|
-
|
|
597
|
-
### EventEmitter Context Propagation
|
|
598
|
-
|
|
599
|
-
Slothlet automatically preserves AsyncLocalStorage context across all EventEmitter callbacks using Node.js AsyncResource patterns. This ensures your API modules maintain full context access in event handlers without any configuration.
|
|
600
|
-
|
|
601
|
-
```javascript
|
|
602
|
-
// api/tcp-server.mjs - Your API module
|
|
603
|
-
import { self, context } from "@cldmv/slothlet/runtime";
|
|
604
|
-
import net from "node:net";
|
|
605
|
-
|
|
606
|
-
export function createTcpServer() {
|
|
607
|
-
const server = net.createServer();
|
|
608
|
-
|
|
609
|
-
// Connection handler maintains full context automatically
|
|
610
|
-
server.on("connection", (socket) => {
|
|
611
|
-
console.log(`User: ${context.user}`); // ✅ Context preserved
|
|
612
|
-
console.log(`API keys: ${Object.keys(self).length}`); // ✅ Full API access
|
|
613
|
-
|
|
614
|
-
// Socket data handler also maintains context automatically
|
|
615
|
-
socket.on("data", (data) => {
|
|
616
|
-
console.log(`Session: ${context.session}`); // ✅ Context preserved
|
|
617
|
-
console.log(`Processing for: ${context.user}`); // ✅ Context preserved
|
|
618
|
-
|
|
619
|
-
// Full API access in nested event handlers
|
|
620
|
-
const processed = self.dataProcessor.handle(data.toString());
|
|
621
|
-
socket.write(processed);
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
socket.on("error", (err) => {
|
|
625
|
-
// Error handlers also maintain context
|
|
626
|
-
self.logger.error(`Error for user ${context.user}: ${err.message}`);
|
|
627
|
-
});
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
return server;
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
export function startServer(port = 3000) {
|
|
634
|
-
const server = createTcpServer();
|
|
635
|
-
server.listen(port);
|
|
636
|
-
return server;
|
|
637
|
-
}
|
|
638
|
-
```
|
|
639
|
-
|
|
640
|
-
```javascript
|
|
641
|
-
// Usage in your application
|
|
642
|
-
import slothlet from "@cldmv/slothlet";
|
|
643
|
-
|
|
644
|
-
const api = await slothlet({
|
|
645
|
-
dir: "./api",
|
|
646
|
-
context: { user: "alice", session: "tcp-session" }
|
|
647
|
-
});
|
|
648
|
-
|
|
649
|
-
// Start the server - all event handlers will have full context
|
|
650
|
-
const server = api.startServer(8080);
|
|
651
|
-
console.log("TCP server started with context preservation");
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
**Key Benefits:**
|
|
655
|
-
|
|
656
|
-
- ✅ **Automatic**: No configuration needed - works transparently in all API modules
|
|
657
|
-
- ✅ **Complete Context**: Full `context` object and `self` API access in all event handlers
|
|
658
|
-
- ✅ **Nested Events**: Works with any depth of EventEmitter nesting (server → socket → custom emitters)
|
|
659
|
-
- ✅ **Universal Support**: All EventEmitter methods (`on`, `once`, `addListener`) are automatically context-aware
|
|
660
|
-
- ✅ **Production Ready**: Uses Node.js AsyncResource patterns for reliable context propagation
|
|
661
|
-
- ✅ **Clean Shutdown**: Automatically cleans up all AsyncResource instances during shutdown to prevent hanging processes
|
|
662
|
-
- ✅ **Zero Overhead**: Only wraps listeners when context is active, minimal performance impact
|
|
663
|
-
|
|
664
|
-
> [!TIP]
|
|
665
|
-
> **Automatic Context Propagation**: EventEmitter context propagation works automatically in both lazy and eager modes. TCP servers, HTTP servers, custom EventEmitters, and any other event-driven patterns in your API modules will maintain full slothlet context and API access without any code changes.
|
|
666
|
-
|
|
667
|
-
### Class Instance Context Propagation
|
|
668
|
-
|
|
669
|
-
Slothlet automatically preserves AsyncLocalStorage context across all class instance method calls. When your API functions return class instances, slothlet wraps them transparently to ensure all method calls maintain full context access.
|
|
670
|
-
|
|
671
|
-
```javascript
|
|
672
|
-
// api/data-processor.mjs - Your API module
|
|
673
|
-
import { self, context } from "@cldmv/slothlet/runtime";
|
|
674
|
-
|
|
675
|
-
class DataProcessor {
|
|
676
|
-
constructor(config) {
|
|
677
|
-
this.config = config;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
process(data) {
|
|
681
|
-
// Context automatically available in all methods
|
|
682
|
-
console.log(`Processing for user: ${context.user}`); // ✅ Context preserved
|
|
683
|
-
console.log(`Request ID: ${context.requestId}`); // ✅ Context preserved
|
|
684
|
-
|
|
685
|
-
// Full API access in class methods
|
|
686
|
-
const validated = self.validator.check(data);
|
|
687
|
-
return this.transform(validated);
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
transform(data) {
|
|
691
|
-
// Context preserved in nested method calls
|
|
692
|
-
console.log(`Transforming for: ${context.user}`); // ✅ Context preserved
|
|
693
|
-
|
|
694
|
-
// Call other API modules from class methods
|
|
695
|
-
return self.utils.format(data);
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
export function createProcessor(config) {
|
|
700
|
-
// Return class instance - slothlet automatically wraps it
|
|
701
|
-
return new DataProcessor(config);
|
|
702
|
-
}
|
|
703
|
-
```
|
|
704
|
-
|
|
705
|
-
```javascript
|
|
706
|
-
// Usage in your application
|
|
707
|
-
import slothlet from "@cldmv/slothlet";
|
|
708
|
-
|
|
709
|
-
const api = await slothlet({
|
|
710
|
-
dir: "./api",
|
|
711
|
-
context: { user: "alice", requestId: "req-123" }
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
// Create processor instance - all methods will have full context
|
|
715
|
-
const processor = api.createProcessor({ format: "json" });
|
|
716
|
-
|
|
717
|
-
// All method calls maintain context automatically
|
|
718
|
-
const result = processor.process({ data: "test" });
|
|
719
|
-
console.log("Processing completed with context preservation");
|
|
720
|
-
```
|
|
721
|
-
|
|
722
|
-
**Key Benefits:**
|
|
723
|
-
|
|
724
|
-
- ✅ **Automatic**: Class instances returned from API functions are automatically context-aware
|
|
725
|
-
- ✅ **Transparent**: No code changes needed - works with existing class patterns
|
|
726
|
-
- ✅ **Complete Context**: Full `context` object and `self` API access in all class methods
|
|
727
|
-
- ✅ **Nested Methods**: Context preserved across method chains and internal calls
|
|
728
|
-
- ✅ **Constructor Support**: Context preserved for both function calls and `new` constructor usage
|
|
729
|
-
- ✅ **Performance Optimized**: Method wrapping is cached to avoid overhead on repeated calls
|
|
730
|
-
|
|
731
|
-
> [!TIP]
|
|
732
|
-
> **Universal Class Support**: Any class instance returned from your API functions automatically maintains slothlet context. This includes database models, service classes, utility classes, and any other object-oriented patterns in your codebase.
|
|
733
|
-
|
|
734
|
-
### Hook System
|
|
735
|
-
|
|
736
|
-
Slothlet provides a powerful hook system for intercepting, modifying, and observing API function calls. Hooks work seamlessly across all loading modes (eager/lazy) and runtime types (async/live).
|
|
737
|
-
|
|
738
|
-
#### Hook Configuration
|
|
739
|
-
|
|
740
|
-
Hooks can be configured when creating a slothlet instance:
|
|
741
|
-
|
|
742
|
-
```javascript
|
|
743
|
-
// Enable hooks (simple boolean)
|
|
744
|
-
const api = await slothlet({
|
|
745
|
-
dir: "./api",
|
|
746
|
-
hooks: true // Enable all hooks with default pattern "**"
|
|
747
|
-
});
|
|
748
|
-
|
|
749
|
-
// Enable with custom pattern
|
|
750
|
-
const api = await slothlet({
|
|
751
|
-
dir: "./api",
|
|
752
|
-
hooks: "database.*" // Only enable for database functions
|
|
753
|
-
});
|
|
754
|
-
|
|
755
|
-
// Full configuration object
|
|
756
|
-
const api = await slothlet({
|
|
757
|
-
dir: "./api",
|
|
758
|
-
hooks: {
|
|
759
|
-
enabled: true,
|
|
760
|
-
pattern: "**", // Default pattern for filtering
|
|
761
|
-
suppressErrors: false // Control error throwing behavior
|
|
762
|
-
}
|
|
763
|
-
});
|
|
764
|
-
```
|
|
765
|
-
|
|
766
|
-
**Configuration Options:**
|
|
767
|
-
|
|
768
|
-
- **`enabled`** (boolean): Enable or disable hook execution
|
|
769
|
-
- **`pattern`** (string): Default pattern for filtering which functions hooks apply to
|
|
770
|
-
- **`suppressErrors`** (boolean): Control error throwing behavior
|
|
771
|
-
- `false` (default): Errors are sent to error hooks, THEN thrown (normal behavior)
|
|
772
|
-
- `true`: Errors are sent to error hooks, BUT NOT thrown (returns `undefined`)
|
|
773
|
-
|
|
774
|
-
**Error Suppression Behavior:**
|
|
775
|
-
|
|
776
|
-
Error hooks **ALWAYS receive errors** regardless of this setting. The `suppressErrors` option only controls whether errors are thrown after error hooks execute.
|
|
777
|
-
|
|
778
|
-
> [!IMPORTANT]
|
|
779
|
-
> **Hooks Must Be Enabled**: Error hooks (and all hooks) only execute when `hooks.enabled: true`. If hooks are disabled, errors are thrown normally without any hook execution.
|
|
780
|
-
|
|
781
|
-
When `suppressErrors: true`, errors are caught and sent to error hooks, but not thrown:
|
|
782
|
-
|
|
783
|
-
```javascript
|
|
784
|
-
const api = await slothlet({
|
|
785
|
-
dir: "./api",
|
|
786
|
-
hooks: {
|
|
787
|
-
enabled: true,
|
|
788
|
-
pattern: "**",
|
|
789
|
-
suppressErrors: true // Suppress all errors
|
|
790
|
-
}
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
// Register error hook to monitor failures
|
|
794
|
-
api.hooks.on(
|
|
795
|
-
"error-monitor",
|
|
796
|
-
"error",
|
|
797
|
-
({ path, error, source }) => {
|
|
798
|
-
console.error(`Error in ${path}:`, error.message);
|
|
799
|
-
// Log to monitoring service without crashing
|
|
800
|
-
},
|
|
801
|
-
{ pattern: "**" }
|
|
802
|
-
);
|
|
803
|
-
|
|
804
|
-
// Function errors won't crash the application
|
|
805
|
-
const result = await api.riskyOperation();
|
|
806
|
-
if (result === undefined) {
|
|
807
|
-
// Function failed but didn't throw
|
|
808
|
-
console.log("Operation failed gracefully");
|
|
809
|
-
}
|
|
810
|
-
```
|
|
811
|
-
|
|
812
|
-
**Error Flow:**
|
|
813
|
-
|
|
814
|
-
1. Error occurs (in before hook, function, or after hook)
|
|
815
|
-
2. Error hooks execute and receive the error
|
|
816
|
-
3. **If `suppressErrors: false`** → Error is thrown (crashes if uncaught)
|
|
817
|
-
4. **If `suppressErrors: true`** → Error is NOT thrown, function returns `undefined`
|
|
818
|
-
|
|
819
|
-
**What Gets Suppressed (when `suppressErrors: true`):**
|
|
820
|
-
|
|
821
|
-
- ✅ Before hook errors → Sent to error hooks, NOT thrown
|
|
822
|
-
- ✅ Function execution errors → Sent to error hooks, NOT thrown
|
|
823
|
-
- ✅ After hook errors → Sent to error hooks, NOT thrown
|
|
824
|
-
- ✅ Always hook errors → Sent to error hooks, never thrown (regardless of setting)
|
|
825
|
-
|
|
826
|
-
> [!TIP]
|
|
827
|
-
> **Use Case**: Enable `suppressErrors: true` for resilient systems where you want to monitor failures without crashing. Perfect for background workers, batch processors, or systems with comprehensive error monitoring.
|
|
828
|
-
|
|
829
|
-
> [!CAUTION]
|
|
830
|
-
> **Critical Operations**: For validation or authorization hooks where errors MUST stop execution, use `suppressErrors: false` (default) to ensure errors propagate normally.
|
|
831
|
-
|
|
832
|
-
#### Hook Types
|
|
833
|
-
|
|
834
|
-
**Four hook types with distinct responsibilities:**
|
|
835
|
-
|
|
836
|
-
- **`before`**: Intercept before function execution
|
|
837
|
-
- Modify arguments passed to functions
|
|
838
|
-
- Cancel execution and return custom values (short-circuit)
|
|
839
|
-
- Execute validation or logging before function runs
|
|
840
|
-
- **`after`**: Transform results after successful execution
|
|
841
|
-
- Transform function return values
|
|
842
|
-
- Only runs if function executes (skipped on short-circuit)
|
|
843
|
-
- Chain multiple transformations in priority order
|
|
844
|
-
- **`always`**: Observe final result with full execution context
|
|
845
|
-
- Always executes after function completes
|
|
846
|
-
- Runs even when `before` hooks cancel execution or errors occur
|
|
847
|
-
- Receives complete context: `{ path, result, hasError, errors }`
|
|
848
|
-
- Cannot modify result (read-only observation)
|
|
849
|
-
- Perfect for unified logging of both success and error scenarios
|
|
850
|
-
- **`error`**: Monitor and handle errors
|
|
851
|
-
- Receives detailed error context with source tracking
|
|
852
|
-
- Error source types: 'before', 'function', 'after', 'always', 'unknown'
|
|
853
|
-
- Includes error type, hook ID, hook tag, timestamp, and stack trace
|
|
854
|
-
- Perfect for error monitoring, logging, and alerting
|
|
855
|
-
|
|
856
|
-
#### Basic Usage
|
|
857
|
-
|
|
858
|
-
```javascript
|
|
859
|
-
import slothlet from "@cldmv/slothlet";
|
|
860
|
-
|
|
861
|
-
const api = await slothlet({
|
|
862
|
-
dir: "./api",
|
|
863
|
-
hooks: true // Enable hooks
|
|
864
|
-
});
|
|
865
|
-
|
|
866
|
-
// Before hook: Modify arguments
|
|
867
|
-
api.hooks.on(
|
|
868
|
-
"validate-input",
|
|
869
|
-
"before",
|
|
870
|
-
({ path, args }) => {
|
|
871
|
-
console.log(`Calling ${path} with args:`, args);
|
|
872
|
-
// Return modified args or original
|
|
873
|
-
return [args[0] * 2, args[1] * 2];
|
|
874
|
-
},
|
|
875
|
-
{ pattern: "math.add", priority: 100 }
|
|
876
|
-
);
|
|
877
|
-
|
|
878
|
-
// After hook: Transform result
|
|
879
|
-
api.hooks.on(
|
|
880
|
-
"format-output",
|
|
881
|
-
"after",
|
|
882
|
-
({ path, result }) => {
|
|
883
|
-
console.log(`${path} returned:`, result);
|
|
884
|
-
// Return transformed result
|
|
885
|
-
return result * 10;
|
|
886
|
-
},
|
|
887
|
-
{ pattern: "math.*", priority: 100 }
|
|
888
|
-
);
|
|
889
|
-
|
|
890
|
-
// Always hook: Observe final result with error context
|
|
891
|
-
api.hooks.on(
|
|
892
|
-
"log-execution",
|
|
893
|
-
"always",
|
|
894
|
-
({ path, result, hasError, errors }) => {
|
|
895
|
-
if (hasError) {
|
|
896
|
-
console.log(`${path} failed with ${errors.length} error(s):`, errors);
|
|
897
|
-
} else {
|
|
898
|
-
console.log(`${path} succeeded with result:`, result);
|
|
899
|
-
}
|
|
900
|
-
// Return value ignored - read-only observer
|
|
901
|
-
},
|
|
902
|
-
{ pattern: "**" } // All functions
|
|
903
|
-
);
|
|
904
|
-
|
|
905
|
-
// Call function - hooks execute automatically
|
|
906
|
-
const result = await api.math.add(2, 3);
|
|
907
|
-
// Logs: "Calling math.add with args: [2, 3]"
|
|
908
|
-
// Logs: "math.add returned: 10" (4+6)
|
|
909
|
-
// Logs: "Final result for math.add: 100" (10*10)
|
|
910
|
-
// result === 100
|
|
911
|
-
```
|
|
912
|
-
|
|
913
|
-
#### Short-Circuit Execution
|
|
914
|
-
|
|
915
|
-
`before` hooks can cancel function execution and return custom values:
|
|
916
|
-
|
|
917
|
-
```javascript
|
|
918
|
-
// Caching hook example
|
|
919
|
-
const cache = new Map();
|
|
920
|
-
|
|
921
|
-
api.hooks.on(
|
|
922
|
-
"cache-check",
|
|
923
|
-
"before",
|
|
924
|
-
({ path, args }) => {
|
|
925
|
-
const key = JSON.stringify({ path, args });
|
|
926
|
-
if (cache.has(key)) {
|
|
927
|
-
console.log(`Cache hit for ${path}`);
|
|
928
|
-
return cache.get(key); // Short-circuit: return cached value
|
|
929
|
-
}
|
|
930
|
-
// Return undefined to continue to function
|
|
931
|
-
},
|
|
932
|
-
{ pattern: "**", priority: 1000 } // High priority
|
|
933
|
-
);
|
|
934
|
-
|
|
935
|
-
api.hooks.on(
|
|
936
|
-
"cache-store",
|
|
937
|
-
"after",
|
|
938
|
-
({ path, args, result }) => {
|
|
939
|
-
const key = JSON.stringify({ path, args });
|
|
940
|
-
cache.set(key, result);
|
|
941
|
-
return result; // Pass through
|
|
942
|
-
},
|
|
943
|
-
{ pattern: "**", priority: 100 }
|
|
944
|
-
);
|
|
945
|
-
|
|
946
|
-
// First call - executes function and caches
|
|
947
|
-
await api.math.add(2, 3); // Computes and stores
|
|
948
|
-
|
|
949
|
-
// Second call - returns cached value (function not executed)
|
|
950
|
-
await api.math.add(2, 3); // Cache hit! No computation
|
|
951
|
-
```
|
|
952
|
-
|
|
953
|
-
#### Pattern Matching
|
|
954
|
-
|
|
955
|
-
Hooks support flexible pattern matching:
|
|
956
|
-
|
|
957
|
-
```javascript
|
|
958
|
-
// Exact match
|
|
959
|
-
api.hooks.on("hook1", "before", handler, { pattern: "math.add" });
|
|
960
|
-
|
|
961
|
-
// Wildcard: all functions in namespace
|
|
962
|
-
api.hooks.on("hook2", "before", handler, { pattern: "math.*" });
|
|
963
|
-
|
|
964
|
-
// Wildcard: specific function in all namespaces
|
|
965
|
-
api.hooks.on("hook3", "before", handler, { pattern: "*.add" });
|
|
966
|
-
|
|
967
|
-
// Global: all functions
|
|
968
|
-
api.hooks.on("hook4", "before", handler, { pattern: "**" });
|
|
969
|
-
```
|
|
970
|
-
|
|
971
|
-
#### Priority and Chaining
|
|
972
|
-
|
|
973
|
-
Multiple hooks execute in priority order (highest first):
|
|
974
|
-
|
|
975
|
-
```javascript
|
|
976
|
-
// High priority - runs first
|
|
977
|
-
api.hooks.on(
|
|
978
|
-
"validate",
|
|
979
|
-
"before",
|
|
980
|
-
({ args }) => {
|
|
981
|
-
if (args[0] < 0) throw new Error("Negative numbers not allowed");
|
|
982
|
-
return args;
|
|
983
|
-
},
|
|
984
|
-
{ pattern: "math.*", priority: 1000 }
|
|
985
|
-
);
|
|
986
|
-
|
|
987
|
-
// Medium priority - runs second
|
|
988
|
-
api.hooks.on("double", "before", ({ args }) => [args[0] * 2, args[1] * 2], { pattern: "math.*", priority: 500 });
|
|
989
|
-
|
|
990
|
-
// Low priority - runs last
|
|
991
|
-
api.hooks.on(
|
|
992
|
-
"log",
|
|
993
|
-
"before",
|
|
994
|
-
({ path, args }) => {
|
|
995
|
-
console.log(`Final args for ${path}:`, args);
|
|
996
|
-
return args;
|
|
997
|
-
},
|
|
998
|
-
{ pattern: "math.*", priority: 100 }
|
|
999
|
-
);
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
#### Runtime Control
|
|
1003
|
-
|
|
1004
|
-
Enable and disable hooks at runtime:
|
|
1005
|
-
|
|
1006
|
-
```javascript
|
|
1007
|
-
const api = await slothlet({ dir: "./api", hooks: true });
|
|
1008
|
-
|
|
1009
|
-
// Add hooks
|
|
1010
|
-
api.hooks.on("test", "before", handler, { pattern: "math.*" });
|
|
1011
|
-
|
|
1012
|
-
// Disable all hooks
|
|
1013
|
-
api.hooks.disable();
|
|
1014
|
-
await api.math.add(2, 3); // No hooks execute
|
|
56
|
+
---
|
|
1015
57
|
|
|
1016
|
-
|
|
1017
|
-
api.hooks.enable();
|
|
1018
|
-
await api.math.add(2, 3); // Hooks execute
|
|
58
|
+
## 🚀 Key Features
|
|
1019
59
|
|
|
1020
|
-
|
|
1021
|
-
api.hooks.disable();
|
|
1022
|
-
api.hooks.enable("math.*"); // Only math.* pattern enabled
|
|
1023
|
-
await api.math.add(2, 3); // math.* hooks execute
|
|
1024
|
-
await api.other.func(); // No hooks execute
|
|
1025
|
-
```
|
|
60
|
+
### 🎯 **Dual Loading Strategies**
|
|
1026
61
|
|
|
1027
|
-
|
|
62
|
+
- **Eager Loading**: Immediate loading for maximum performance in production environments
|
|
63
|
+
- **Lazy Loading**: Copy-left materialization with look-ahead proxies (4.3x faster startup, 1.4x faster calls after materialization)
|
|
1028
64
|
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
65
|
+
> [!IMPORTANT]
|
|
66
|
+
> **Function Call Patterns:**
|
|
67
|
+
>
|
|
68
|
+
> - **Lazy Mode**: ALL function calls must be awaited (`await api.math.add(2, 3)`) due to materialization process
|
|
69
|
+
> - **Eager Mode**: Functions behave as originally defined - sync functions are sync (`api.math.add(2, 3)`), async functions are async (`await api.async.process()`)
|
|
1034
70
|
|
|
1035
|
-
|
|
1036
|
-
const id = api.hooks.on("temp", "before", handler, { pattern: "math.*" });
|
|
1037
|
-
api.hooks.off(id);
|
|
71
|
+
### ⚡ Performance Excellence
|
|
1038
72
|
|
|
1039
|
-
|
|
1040
|
-
|
|
73
|
+
- **2.9x faster startup** in lazy mode (4.89ms vs 14.29ms)
|
|
74
|
+
- **1.1x faster function calls** in eager mode (0.90μs vs 0.99μs)
|
|
75
|
+
- **Copy-left materialization**: Once loaded, modules stay materialized
|
|
76
|
+
- **Zero dependencies**: Pure Node.js implementation
|
|
1041
77
|
|
|
1042
|
-
|
|
1043
|
-
api.hooks.clear("before"); // Remove all before hooks
|
|
1044
|
-
api.hooks.clear(); // Remove all hooks
|
|
1045
|
-
```
|
|
78
|
+
📊 **For comprehensive performance analysis, benchmarks, and recommendations, see [docs/PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/master/docs/PERFORMANCE.md)**
|
|
1046
79
|
|
|
1047
|
-
|
|
80
|
+
### 🎣 **Hook System**
|
|
1048
81
|
|
|
1049
|
-
|
|
82
|
+
Powerful function interceptor system with 4 hook types:
|
|
1050
83
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
({ path, error, source }) => {
|
|
1056
|
-
console.error(`Error in ${path}:`, error.message);
|
|
1057
|
-
console.error(`Source: ${source.type}`); // 'before', 'after', 'always', 'function', 'unknown'
|
|
84
|
+
- **`before`** - Modify arguments or cancel execution
|
|
85
|
+
- **`after`** - Transform return values
|
|
86
|
+
- **`always`** - Observe final results (read-only)
|
|
87
|
+
- **`error`** - Monitor and handle errors with detailed source tracking
|
|
1058
88
|
|
|
1059
|
-
|
|
1060
|
-
console.error("Error occurred in function execution");
|
|
1061
|
-
} else if (["before", "after", "always"].includes(source.type)) {
|
|
1062
|
-
console.error(`Error occurred in ${source.type} hook:`);
|
|
1063
|
-
console.error(` Hook ID: ${source.hookId}`);
|
|
1064
|
-
console.error(` Hook Tag: ${source.hookTag}`);
|
|
1065
|
-
}
|
|
89
|
+
Pattern matching, priority control, runtime enable/disable, and short-circuit support included.
|
|
1066
90
|
|
|
1067
|
-
|
|
1068
|
-
console.error(`Stack trace:\n${source.stack}`);
|
|
91
|
+
🎣 **For complete hook system documentation, see [docs/HOOKS.md](https://github.com/CLDMV/slothlet/blob/master/docs/HOOKS.md)**
|
|
1069
92
|
|
|
1070
|
-
|
|
1071
|
-
// Error is re-thrown after all error hooks execute
|
|
1072
|
-
},
|
|
1073
|
-
{ pattern: "**" }
|
|
1074
|
-
);
|
|
93
|
+
### 🔄 **Context Propagation**
|
|
1075
94
|
|
|
1076
|
-
|
|
1077
|
-
await api.validateData({ invalid: true });
|
|
1078
|
-
} catch (error) {
|
|
1079
|
-
// Error hooks executed before this catch block
|
|
1080
|
-
console.log("Caught error:", error);
|
|
1081
|
-
}
|
|
1082
|
-
```
|
|
95
|
+
Automatic context preservation across all asynchronous boundaries:
|
|
1083
96
|
|
|
1084
|
-
|
|
97
|
+
- **EventEmitter propagation**: Context maintained across all event callbacks
|
|
98
|
+
- **Class instance propagation**: Context preserved in class method calls
|
|
99
|
+
- **Zero configuration**: Works automatically with TCP servers, HTTP servers, and custom EventEmitters
|
|
1085
100
|
|
|
1086
|
-
|
|
101
|
+
🔄 **For context propagation details, see [docs/CONTEXT-PROPAGATION.md](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md)**
|
|
1087
102
|
|
|
1088
|
-
**
|
|
103
|
+
### 🔧 **Smart API Management**
|
|
1089
104
|
|
|
1090
|
-
-
|
|
1091
|
-
-
|
|
1092
|
-
-
|
|
1093
|
-
-
|
|
1094
|
-
- `"unknown"`: Error source could not be determined
|
|
105
|
+
- **Intelligent Flattening**: Clean APIs with automatic structure optimization (`math/math.mjs` → `api.math`)
|
|
106
|
+
- **Smart Naming**: Preserves original capitalization (`auto-ip.mjs` with `autoIP` → `api.autoIP`)
|
|
107
|
+
- **Advanced Sanitization**: Custom naming rules with glob and boundary patterns
|
|
108
|
+
- **Hybrid Exports**: Support for callable APIs with methods, default + named exports
|
|
1095
109
|
|
|
1096
|
-
**
|
|
110
|
+
🏗️ **For module structure examples, see [docs/MODULE-STRUCTURE.md](https://github.com/CLDMV/slothlet/blob/master/docs/MODULE-STRUCTURE.md)**
|
|
111
|
+
📐 **For API flattening rules, see [docs/API-FLATTENING.md](https://github.com/CLDMV/slothlet/blob/master/docs/API-FLATTENING.md)**
|
|
1097
112
|
|
|
1098
|
-
|
|
1099
|
-
- `source.hookId`: Hook identifier (for hook errors)
|
|
1100
|
-
- `source.hookTag`: Hook tag/name (for hook errors)
|
|
1101
|
-
- `source.timestamp`: ISO timestamp when error occurred
|
|
1102
|
-
- `source.stack`: Full stack trace
|
|
113
|
+
### 🔗 **Advanced Binding System**
|
|
1103
114
|
|
|
1104
|
-
**
|
|
115
|
+
- **Live Bindings**: Dynamic context and reference binding for runtime API mutation
|
|
116
|
+
- **Context Isolation**: Dual runtime options (AsyncLocalStorage or live-bindings)
|
|
117
|
+
- **Mixed Module Support**: Seamlessly blend ESM and CommonJS modules
|
|
118
|
+
- **Copy-Left Preservation**: Materialized functions stay materialized
|
|
1105
119
|
|
|
1106
|
-
|
|
1107
|
-
const errorStats = {
|
|
1108
|
-
function: 0,
|
|
1109
|
-
before: 0,
|
|
1110
|
-
after: 0,
|
|
1111
|
-
always: 0,
|
|
1112
|
-
byHook: {}
|
|
1113
|
-
};
|
|
120
|
+
### 🛠 **Developer Experience**
|
|
1114
121
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
// Track error source statistics
|
|
1120
|
-
errorStats[source.type]++;
|
|
1121
|
-
|
|
1122
|
-
if (source.hookId) {
|
|
1123
|
-
if (!errorStats.byHook[source.hookTag]) {
|
|
1124
|
-
errorStats.byHook[source.hookTag] = 0;
|
|
1125
|
-
}
|
|
1126
|
-
errorStats.byHook[source.hookTag]++;
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
// Log detailed error info
|
|
1130
|
-
console.error(`[${source.timestamp}] Error in ${path}:`);
|
|
1131
|
-
console.error(` Type: ${source.type}`);
|
|
1132
|
-
console.error(` Message: ${error.message}`);
|
|
1133
|
-
|
|
1134
|
-
if (source.type === "function") {
|
|
1135
|
-
// Function-level error - might be a bug in implementation
|
|
1136
|
-
console.error(" Action: Review function implementation");
|
|
1137
|
-
} else {
|
|
1138
|
-
// Hook-level error - might be a bug in hook logic
|
|
1139
|
-
console.error(` Action: Review ${source.hookTag} hook (${source.type})`);
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
// Send to monitoring service
|
|
1143
|
-
sendToMonitoring({
|
|
1144
|
-
timestamp: source.timestamp,
|
|
1145
|
-
path,
|
|
1146
|
-
errorType: source.type,
|
|
1147
|
-
hookId: source.hookId,
|
|
1148
|
-
hookTag: source.hookTag,
|
|
1149
|
-
message: error.message,
|
|
1150
|
-
stack: source.stack
|
|
1151
|
-
});
|
|
1152
|
-
},
|
|
1153
|
-
{ pattern: "**" }
|
|
1154
|
-
);
|
|
122
|
+
- **TypeScript-Friendly**: Comprehensive JSDoc annotations with auto-generated declarations
|
|
123
|
+
- **Configurable Debug**: Detailed logging via CLI flags or environment variables
|
|
124
|
+
- **Multiple Instances**: Parameter-based isolation for complex applications
|
|
125
|
+
- **Development Checks**: Built-in environment detection with silent production behavior
|
|
1155
126
|
|
|
1156
|
-
|
|
1157
|
-
console.log("Error Statistics:", errorStats);
|
|
1158
|
-
// {
|
|
1159
|
-
// function: 5,
|
|
1160
|
-
// before: 2,
|
|
1161
|
-
// after: 1,
|
|
1162
|
-
// always: 0,
|
|
1163
|
-
// byHook: {
|
|
1164
|
-
// "validate-input": 2,
|
|
1165
|
-
// "format-output": 1
|
|
1166
|
-
// }
|
|
1167
|
-
// }
|
|
1168
|
-
```
|
|
127
|
+
---
|
|
1169
128
|
|
|
1170
|
-
|
|
129
|
+
## 📦 Installation
|
|
1171
130
|
|
|
1172
|
-
|
|
1173
|
-
- Errors from `always` hooks are caught and logged but do NOT crash execution
|
|
1174
|
-
- Error hooks themselves do not receive errors from other error hooks (no recursion)
|
|
1175
|
-
- The `_hookSourceReported` flag prevents double-reporting of errors
|
|
131
|
+
### Requirements
|
|
1176
132
|
|
|
1177
|
-
|
|
133
|
+
- **Node.js v16.4.0 or higher** (required for AsyncLocalStorage support)
|
|
1178
134
|
|
|
1179
|
-
|
|
135
|
+
### Install
|
|
1180
136
|
|
|
1181
|
-
```
|
|
1182
|
-
|
|
1183
|
-
const api1 = await slothlet({ dir: "./api", lazy: false, runtime: "async", hooks: true });
|
|
1184
|
-
|
|
1185
|
-
// Eager + Live Bindings
|
|
1186
|
-
const api2 = await slothlet({ dir: "./api", lazy: false, runtime: "live", hooks: true });
|
|
1187
|
-
|
|
1188
|
-
// Lazy + AsyncLocalStorage
|
|
1189
|
-
const api3 = await slothlet({ dir: "./api", lazy: true, runtime: "async", hooks: true });
|
|
1190
|
-
|
|
1191
|
-
// Lazy + Live Bindings
|
|
1192
|
-
const api4 = await slothlet({ dir: "./api", lazy: true, runtime: "live", hooks: true });
|
|
1193
|
-
|
|
1194
|
-
// Same hook code works with all configurations
|
|
1195
|
-
[api1, api2, api3, api4].forEach((api) => {
|
|
1196
|
-
api.hooks.on(
|
|
1197
|
-
"universal",
|
|
1198
|
-
"before",
|
|
1199
|
-
({ args }) => {
|
|
1200
|
-
return [args[0] * 10, args[1] * 10];
|
|
1201
|
-
},
|
|
1202
|
-
{ pattern: "math.add" }
|
|
1203
|
-
);
|
|
1204
|
-
});
|
|
137
|
+
```bash
|
|
138
|
+
npm install @cldmv/slothlet
|
|
1205
139
|
```
|
|
1206
140
|
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
- ✅ **Universal**: Works across all 4 mode/runtime combinations
|
|
1210
|
-
- ✅ **Flexible**: Pattern matching with wildcards and priorities
|
|
1211
|
-
- ✅ **Powerful**: Modify args, transform results, observe execution
|
|
1212
|
-
- ✅ **Composable**: Chain multiple hooks with priority control
|
|
1213
|
-
- ✅ **Dynamic**: Enable/disable at runtime globally or by pattern
|
|
1214
|
-
- ✅ **Observable**: Separate hook types for different responsibilities
|
|
1215
|
-
|
|
1216
|
-
### API Mode Configuration
|
|
141
|
+
---
|
|
1217
142
|
|
|
1218
|
-
|
|
143
|
+
## 🚀 Quick Start
|
|
1219
144
|
|
|
1220
|
-
|
|
145
|
+
### ESM (ES Modules)
|
|
1221
146
|
|
|
1222
147
|
```javascript
|
|
148
|
+
import slothlet from "@cldmv/slothlet";
|
|
149
|
+
|
|
150
|
+
// Direct usage - eager mode by default
|
|
1223
151
|
const api = await slothlet({
|
|
1224
|
-
|
|
152
|
+
dir: "./api",
|
|
153
|
+
context: { user: "alice" }
|
|
1225
154
|
});
|
|
1226
155
|
|
|
1227
|
-
//
|
|
1228
|
-
|
|
1229
|
-
|
|
156
|
+
// Eager mode: Functions behave as originally defined
|
|
157
|
+
const result = api.math.add(2, 3); // Sync function - no await needed
|
|
158
|
+
const asyncResult = await api.async.processData({ data: "async" });
|
|
1230
159
|
|
|
1231
|
-
//
|
|
1232
|
-
|
|
160
|
+
// Access both ESM and CJS modules seamlessly
|
|
161
|
+
const esmResult = api.mathEsm.multiply(4, 5);
|
|
162
|
+
const cjsResult = api.mathCjs.divide(10, 2);
|
|
1233
163
|
```
|
|
1234
164
|
|
|
1235
|
-
|
|
165
|
+
### CommonJS (CJS)
|
|
1236
166
|
|
|
1237
167
|
```javascript
|
|
168
|
+
const slothlet = require("@cldmv/slothlet");
|
|
169
|
+
|
|
170
|
+
// Same usage pattern works with CommonJS
|
|
1238
171
|
const api = await slothlet({
|
|
1239
|
-
|
|
172
|
+
dir: "./api",
|
|
173
|
+
context: { env: "production" }
|
|
1240
174
|
});
|
|
1241
175
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
const result = api("World"); // Calls root default function
|
|
1245
|
-
const math = api.math.add(2, 3); // Also access other modules
|
|
176
|
+
const result = api.math.multiply(4, 5);
|
|
177
|
+
const mixedResult = await api.interop.processData({ data: "test" });
|
|
1246
178
|
```
|
|
1247
179
|
|
|
1248
|
-
|
|
180
|
+
### Lazy Loading Mode
|
|
1249
181
|
|
|
1250
182
|
```javascript
|
|
183
|
+
import slothlet from "@cldmv/slothlet";
|
|
184
|
+
|
|
185
|
+
// Lazy mode with copy-left materialization
|
|
1251
186
|
const api = await slothlet({
|
|
1252
|
-
|
|
187
|
+
mode: "lazy", // Preferred syntax
|
|
188
|
+
dir: "./api",
|
|
189
|
+
apiDepth: 3
|
|
1253
190
|
});
|
|
1254
191
|
|
|
1255
|
-
//
|
|
1256
|
-
|
|
1257
|
-
const result = api.rootFunction("World"); // Access via property
|
|
1258
|
-
const math = api.math.add(2, 3); // Normal module access
|
|
1259
|
-
```
|
|
1260
|
-
|
|
1261
|
-
---
|
|
1262
|
-
|
|
1263
|
-
## 🏗 Module Structure & Examples
|
|
1264
|
-
|
|
1265
|
-
Slothlet supports sophisticated module organization patterns with seamless ESM/CJS interoperability:
|
|
1266
|
-
|
|
1267
|
-
### Root-Level Modules
|
|
1268
|
-
|
|
1269
|
-
```text
|
|
1270
|
-
root-math.mjs → api.rootMath (dash-to-camelCase)
|
|
1271
|
-
rootstring.mjs → api.rootstring
|
|
1272
|
-
config.mjs → api.config
|
|
1273
|
-
```
|
|
1274
|
-
|
|
1275
|
-
### Filename-Folder Matching Modules
|
|
1276
|
-
|
|
1277
|
-
```text
|
|
1278
|
-
math/math.mjs → api.math (filename matches folder)
|
|
1279
|
-
string/string.mjs → api.string (filename matches folder)
|
|
1280
|
-
util/util.cjs → api.util (CJS support with filename matching)
|
|
1281
|
-
```
|
|
1282
|
-
|
|
1283
|
-
### Multi-File Modules
|
|
1284
|
-
|
|
1285
|
-
```text
|
|
1286
|
-
multi/
|
|
1287
|
-
├── alpha.mjs → api.multi.alpha
|
|
1288
|
-
├── beta.mjs → api.multi.beta
|
|
1289
|
-
└── gamma.cjs → api.multi.gamma (mixed ESM/CJS)
|
|
1290
|
-
```
|
|
1291
|
-
|
|
1292
|
-
### Function-Based Modules
|
|
1293
|
-
|
|
1294
|
-
```text
|
|
1295
|
-
funcmod/funcmod.mjs → api.funcmod() (callable function)
|
|
1296
|
-
multi_func/
|
|
1297
|
-
├── alpha.mjs → api.multi_func.alpha()
|
|
1298
|
-
└── beta.cjs → api.multi_func.beta() (CJS callable)
|
|
1299
|
-
```
|
|
1300
|
-
|
|
1301
|
-
### Mixed ESM/CJS Modules
|
|
1302
|
-
|
|
1303
|
-
```text
|
|
1304
|
-
interop/
|
|
1305
|
-
├── esm-module.mjs → api.interop.esmModule
|
|
1306
|
-
├── cjs-module.cjs → api.interop.cjsModule
|
|
1307
|
-
└── mixed.mjs → api.interop.mixed (calls both ESM and CJS)
|
|
1308
|
-
```
|
|
1309
|
-
|
|
1310
|
-
### Hybrid Export Patterns
|
|
1311
|
-
|
|
1312
|
-
```text
|
|
1313
|
-
exportDefault/exportDefault.mjs → api.exportDefault() (callable with methods)
|
|
1314
|
-
objectDefaultMethod/ → api.objectDefaultMethod() (object with default)
|
|
1315
|
-
```
|
|
1316
|
-
|
|
1317
|
-
### Nested Structure
|
|
1318
|
-
|
|
1319
|
-
```text
|
|
1320
|
-
nested/
|
|
1321
|
-
└── date/
|
|
1322
|
-
├── date.mjs → api.nested.date
|
|
1323
|
-
└── util.cjs → api.nested.dateUtil
|
|
1324
|
-
advanced/
|
|
1325
|
-
├── selfObject/ → api.advanced.selfObject
|
|
1326
|
-
└── nest*/ → Various nesting examples
|
|
1327
|
-
```
|
|
1328
|
-
|
|
1329
|
-
### Utility Modules
|
|
1330
|
-
|
|
1331
|
-
```text
|
|
1332
|
-
util/
|
|
1333
|
-
├── controller.mjs → api.util.controller
|
|
1334
|
-
├── extract.cjs → api.util.extract (CJS utility)
|
|
1335
|
-
└── url/
|
|
1336
|
-
├── parser.mjs → api.util.url.parser
|
|
1337
|
-
└── builder.cjs → api.util.url.builder (mixed)
|
|
1338
|
-
```
|
|
1339
|
-
|
|
1340
|
-
### Smart Function Naming Examples
|
|
1341
|
-
|
|
1342
|
-
```text
|
|
1343
|
-
task/auto-ip.mjs (exports autoIP) → api.task.autoIP (preserves function name)
|
|
1344
|
-
util/parseJSON.mjs → api.util.parseJSON (preserves JSON casing)
|
|
1345
|
-
api/getHTTPStatus.mjs → api.api.getHTTPStatus (preserves HTTP casing)
|
|
1346
|
-
```
|
|
1347
|
-
|
|
1348
|
-
## 🏗️ API Flattening Rules
|
|
1349
|
-
|
|
1350
|
-
Slothlet uses intelligent flattening rules to create clean, intuitive API structures. Understanding these rules helps you organize your modules for the best developer experience:
|
|
1351
|
-
|
|
1352
|
-
### 1. **Filename-Folder Matching** (Single Named Export)
|
|
1353
|
-
|
|
1354
|
-
**When:** A file exports a single named export that matches the sanitized filename
|
|
1355
|
-
**Why:** Avoids redundant nesting (`api.math.math.add()` → `api.math.add()`)
|
|
1356
|
-
**Reasoning:** When file purpose matches folder purpose, eliminate the duplicate layer
|
|
1357
|
-
|
|
1358
|
-
```text
|
|
1359
|
-
math/math.mjs (exports { math: {...} }) → api.math (flattened)
|
|
1360
|
-
string/string.mjs (exports { string: {...} }) → api.string (flattened)
|
|
1361
|
-
util/util.mjs (exports { util: {...} }) → api.util (flattened)
|
|
1362
|
-
```
|
|
1363
|
-
|
|
1364
|
-
### 2. **No Default Export + Only Named Exports** ⭐ NEW
|
|
1365
|
-
|
|
1366
|
-
**When:** A file has **no default export** and **only named exports**
|
|
1367
|
-
**Why:** The file acts as a pure function collection, not a module with a main export
|
|
1368
|
-
**Reasoning:** If there's no "main thing" (default export), treat all functions as equals at the root level
|
|
1369
|
-
|
|
1370
|
-
```text
|
|
1371
|
-
connection.mjs (exports { connect, disconnect, isConnected })
|
|
1372
|
-
→ api.connect(), api.disconnect(), api.isConnected()
|
|
1373
|
-
Because: No default export = no main "connection" object needed
|
|
1374
|
-
|
|
1375
|
-
app.mjs (exports { getAllApps, getCurrentApp, setApp })
|
|
1376
|
-
→ api.getAllApps(), api.getCurrentApp(), api.setApp()
|
|
1377
|
-
Because: No default export = these are standalone utility functions
|
|
1378
|
-
|
|
1379
|
-
state.mjs (exports { cloneState, emitLog, reset, update })
|
|
1380
|
-
→ api.cloneState(), api.emitLog(), api.reset(), api.update()
|
|
1381
|
-
Because: No default export = treat as individual state utilities
|
|
1382
|
-
```
|
|
1383
|
-
|
|
1384
|
-
### 3. **Has Default Export** (Namespace Preservation)
|
|
1385
|
-
|
|
1386
|
-
**When:** A file has a default export (with or without named exports)
|
|
1387
|
-
**Why:** The default export indicates there's a "main thing" that should be the namespace
|
|
1388
|
-
**Reasoning:** Default export signals intentional module structure that should be preserved
|
|
1389
|
-
|
|
1390
|
-
```text
|
|
1391
|
-
config.mjs (exports default + named exports) → api.config.*
|
|
1392
|
-
Because: Default export indicates a main config object with methods
|
|
1393
|
-
|
|
1394
|
-
input.mjs (exports default + named exports) → api.input.*
|
|
1395
|
-
Because: Default export indicates a main input handler with utilities
|
|
192
|
+
// First access: materialization overhead (~1.45ms average)
|
|
193
|
+
const result1 = await api.math.add(2, 3);
|
|
1396
194
|
|
|
1397
|
-
|
|
1398
|
-
|
|
195
|
+
// Subsequent access: materialized function (near-eager performance)
|
|
196
|
+
const result2 = await api.math.add(5, 7);
|
|
1399
197
|
```
|
|
1400
198
|
|
|
1401
|
-
###
|
|
199
|
+
### Hook System Example
|
|
1402
200
|
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
**Reasoning:** Root files are explicitly placed there and should maintain their intended naming
|
|
201
|
+
```javascript
|
|
202
|
+
import slothlet from "@cldmv/slothlet";
|
|
1406
203
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
204
|
+
const api = await slothlet({
|
|
205
|
+
dir: "./api",
|
|
206
|
+
hooks: true // Enable hooks
|
|
207
|
+
});
|
|
1410
208
|
|
|
1411
|
-
|
|
1412
|
-
|
|
209
|
+
// Before hook: Modify arguments
|
|
210
|
+
api.hooks.on(
|
|
211
|
+
"validate",
|
|
212
|
+
"before",
|
|
213
|
+
({ path, args }) => {
|
|
214
|
+
console.log(`Calling ${path} with args:`, args);
|
|
215
|
+
return [args[0] * 2, args[1] * 2];
|
|
216
|
+
},
|
|
217
|
+
{ pattern: "math.add", priority: 100 }
|
|
218
|
+
);
|
|
1413
219
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
220
|
+
// After hook: Transform result
|
|
221
|
+
api.hooks.on(
|
|
222
|
+
"format",
|
|
223
|
+
"after",
|
|
224
|
+
({ path, result }) => {
|
|
225
|
+
console.log(`${path} returned:`, result);
|
|
226
|
+
return result * 10;
|
|
227
|
+
},
|
|
228
|
+
{ pattern: "math.*", priority: 100 }
|
|
229
|
+
);
|
|
1417
230
|
|
|
1418
|
-
|
|
231
|
+
// Always hook: Observe final result
|
|
232
|
+
api.hooks.on(
|
|
233
|
+
"observe",
|
|
234
|
+
"always",
|
|
235
|
+
({ path, result, hasError }) => {
|
|
236
|
+
console.log(hasError ? `${path} failed` : `${path} succeeded`);
|
|
237
|
+
},
|
|
238
|
+
{ pattern: "**" }
|
|
239
|
+
);
|
|
1419
240
|
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
241
|
+
// Error hook: Monitor errors with source tracking
|
|
242
|
+
api.hooks.on(
|
|
243
|
+
"error-logger",
|
|
244
|
+
"error",
|
|
245
|
+
({ path, error, source }) => {
|
|
246
|
+
console.error(`Error in ${path}:`, error.message);
|
|
247
|
+
console.error(`Source: ${source.type}`); // 'before', 'after', 'function', 'always'
|
|
248
|
+
},
|
|
249
|
+
{ pattern: "**" }
|
|
250
|
+
);
|
|
1423
251
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
Because: config.mjs in config/ folder = same concept, use folder name only
|
|
252
|
+
// Call function - hooks execute automatically
|
|
253
|
+
const result = await api.math.add(2, 3);
|
|
1427
254
|
```
|
|
1428
255
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
```mermaid
|
|
1432
|
-
flowchart TD
|
|
1433
|
-
FILE[Module File] --> ROOT{Root Level?}
|
|
1434
|
-
|
|
1435
|
-
ROOT -->|Yes| PRESERVE[Preserve Namespace<br/>api.rootMath, api.config]
|
|
1436
|
-
|
|
1437
|
-
ROOT -->|No| SELFREFER{Self-Referential?}
|
|
1438
|
-
SELFREFER -->|Yes| NAMESPACE[Use Namespace<br/>api.config]
|
|
1439
|
-
|
|
1440
|
-
SELFREFER -->|No| HASDEFAULT{Has Default Export?}
|
|
1441
|
-
HASDEFAULT -->|Yes| NAMESPACE
|
|
1442
|
-
|
|
1443
|
-
HASDEFAULT -->|No| NAMEDONLY{Only Named Exports?}
|
|
1444
|
-
NAMEDONLY -->|Yes| FLATTEN["Flatten All Named Exports<br/>api.connect, api.disconnect"]
|
|
1445
|
-
|
|
1446
|
-
NAMEDONLY -->|No| SINGLENAMED{Single Named Export<br/>Matching Filename?}
|
|
1447
|
-
SINGLENAMED -->|Yes| FLATTENSINGLE[Flatten Single Export<br/>api.math]
|
|
1448
|
-
SINGLENAMED -->|No| NAMESPACE
|
|
1449
|
-
|
|
1450
|
-
style FLATTEN fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000
|
|
1451
|
-
style FLATTENSINGLE fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px,color:#000
|
|
1452
|
-
style NAMESPACE fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
|
|
1453
|
-
style PRESERVE fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000
|
|
1454
|
-
```
|
|
256
|
+
---
|
|
1455
257
|
|
|
1456
|
-
|
|
258
|
+
## 📚 Configuration Options
|
|
259
|
+
|
|
260
|
+
| Option | Type | Default | Description |
|
|
261
|
+
| ------------------- | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
262
|
+
| `dir` | `string` | `"api"` | Directory to load API modules from (absolute or relative path) |
|
|
263
|
+
| `mode` | `string` | `"eager"` | **New** loading mode - `"lazy"` for on-demand loading, `"eager"` for immediate loading |
|
|
264
|
+
| `lazy` | `boolean` | `false` | **Legacy** loading strategy (use `mode` instead) |
|
|
265
|
+
| `engine` | `string` | `"singleton"` | Execution environment: `"singleton"`, `"vm"`, `"worker"`, or `"fork"` (experimental modes) |
|
|
266
|
+
| `runtime` | `string` | `"async"` | Runtime binding system: `"async"` for AsyncLocalStorage (requires Node.js v16.4.0+), `"live"` for live-bindings (works on Node.js v12.20.0+) |
|
|
267
|
+
| `apiDepth` | `number` | `Infinity` | Directory traversal depth - `0` for root only, `Infinity` for all levels |
|
|
268
|
+
| `debug` | `boolean` | `false` | Enable verbose logging (also via `--slothletdebug` flag or `SLOTHLET_DEBUG=true` env var) |
|
|
269
|
+
| `api_mode` | `string` | `"auto"` | API structure behavior: `"auto"` (detect), `"function"` (force callable), `"object"` (force object) |
|
|
270
|
+
| `allowApiOverwrite` | `boolean` | `true` | Allow `addApi()` to overwrite existing endpoints (`false` = prevent overwrites with warning) |
|
|
271
|
+
| `context` | `object` | `{}` | Context data injected into live-binding (available via `import { context } from "@cldmv/slothlet/runtime"`) |
|
|
272
|
+
| `reference` | `object` | `{}` | Reference object merged into API root level |
|
|
273
|
+
| `sanitize` | `object` | `{}` | Advanced filename-to-API transformation control with `lowerFirst`, `preserveAllUpper`, `preserveAllLower`, and `rules` (supports exact matches, glob patterns `*json*`, and boundary patterns `**url**`) |
|
|
274
|
+
| `hooks` | `mixed` | `false` | Enable hook system: `true` (enable all), `"pattern"` (enable with pattern), or object with `enabled`, `pattern`, `suppressErrors` options |
|
|
275
|
+
|
|
276
|
+
**For complete API documentation with detailed parameter descriptions and examples, see [docs/generated/API.md](https://github.com/CLDMV/slothlet/blob/master/docs/generated/API.md)**
|
|
1457
277
|
|
|
1458
|
-
|
|
1459
|
-
_Why it matters:_ Reduces typing, improves readability, and matches how you'd naturally call connection functions
|
|
1460
|
-
- **Intuitive Structure**: File organization matches API usage patterns
|
|
1461
|
-
_Why it matters:_ Files with only utility functions flatten (no main export = no namespace needed), while files with main exports preserve their intended structure
|
|
1462
|
-
- **Flexible Organization**: Mix flattened and nested patterns as needed
|
|
1463
|
-
_Why it matters:_ You can organize files by purpose (`connection.mjs` for utilities, `config.mjs` for main objects) and slothlet automatically creates the right API structure
|
|
1464
|
-
- **Developer Intent Respected**: Export structure signals your architectural intentions
|
|
1465
|
-
_Why it matters:_ Default exports = "this is a main thing with methods", named exports only = "these are utility functions"
|
|
1466
|
-
- **Backward Compatibility**: Existing APIs continue to work as expected
|
|
1467
|
-
_Why it matters:_ The rules are additive - existing filename-matching and default export patterns still work exactly the same
|
|
278
|
+
---
|
|
1468
279
|
|
|
1469
280
|
## 🔀 How Slothlet Works: Loading Modes Explained
|
|
1470
281
|
|
|
@@ -1592,59 +403,104 @@ flowchart TD
|
|
|
1592
403
|
|
|
1593
404
|
### Eager Mode (Default - Production Ready)
|
|
1594
405
|
|
|
406
|
+
**Best for:** Production environments, maximum runtime performance, predictable behavior
|
|
407
|
+
|
|
1595
408
|
```javascript
|
|
1596
409
|
const api = await slothlet({ dir: "./api" }); // lazy: false by default
|
|
1597
410
|
|
|
1598
|
-
// Functions behave as originally defined
|
|
1599
|
-
const result = api.math.add(2, 3); // Sync
|
|
1600
|
-
const
|
|
1601
|
-
|
|
1602
|
-
// Async functions still need await (as originally defined)
|
|
1603
|
-
const asyncResult = await api.async.processData({ data: "test" }); // Original async function
|
|
1604
|
-
|
|
1605
|
-
// ESM+CJS works seamlessly with native behavior
|
|
1606
|
-
const mixed = api.interop.process({ data: "test" }); // Sync or async as defined
|
|
411
|
+
// Functions behave as originally defined
|
|
412
|
+
const result = api.math.add(2, 3); // Sync - no await needed
|
|
413
|
+
const asyncResult = await api.async.processData({ data: "test" }); // Async needs await
|
|
1607
414
|
```
|
|
1608
415
|
|
|
1609
416
|
**Benefits:**
|
|
1610
417
|
|
|
1611
|
-
- ✅ Fastest function calls (0.
|
|
1612
|
-
- ✅ Predictable performance
|
|
1613
|
-
- ✅
|
|
1614
|
-
- ✅ Functions behave exactly as originally defined (sync stays sync, async stays async)
|
|
1615
|
-
- ✅ Optimal for production environments
|
|
418
|
+
- ✅ Fastest function calls (0.90μs average)
|
|
419
|
+
- ✅ Predictable performance (no materialization delays)
|
|
420
|
+
- ✅ Functions behave exactly as originally defined
|
|
1616
421
|
|
|
1617
422
|
### Lazy Mode with Copy-Left Materialization (Production Ready)
|
|
1618
423
|
|
|
1619
|
-
|
|
1620
|
-
const api = await slothlet({ lazy: true, dir: "./api" });
|
|
424
|
+
**Best for:** Startup-sensitive applications, memory efficiency, loading only what you use
|
|
1621
425
|
|
|
1622
|
-
|
|
1623
|
-
const
|
|
1624
|
-
const result2 = await api.math.add(5, 7); // Subsequent: ~0.5μs (materialized)
|
|
426
|
+
```javascript
|
|
427
|
+
const api = await slothlet({ mode: "lazy", dir: "./api" });
|
|
1625
428
|
|
|
1626
|
-
//
|
|
1627
|
-
const
|
|
1628
|
-
const
|
|
429
|
+
// ALL calls must be awaited (materialization process)
|
|
430
|
+
const result1 = await api.math.add(2, 3); // First: ~371μs (materialization)
|
|
431
|
+
const result2 = await api.math.add(5, 7); // Subsequent: 0.99μs (materialized)
|
|
1629
432
|
```
|
|
1630
433
|
|
|
1631
434
|
**Benefits:**
|
|
1632
435
|
|
|
1633
|
-
- ✅
|
|
436
|
+
- ✅ 2.9x faster startup (4.89ms vs 14.29ms)
|
|
437
|
+
- ✅ Near-equal function call performance (0.99μs vs 0.90μs eager)
|
|
1634
438
|
- ✅ Memory efficient (loads only what you use)
|
|
1635
439
|
- ✅ Copy-left optimization (once loaded, stays loaded)
|
|
1636
|
-
- ✅ Optimal for startup-sensitive applications
|
|
1637
|
-
- ⚠️ All function calls require await (regardless of original sync/async nature)
|
|
1638
|
-
|
|
1639
|
-
**Performance Summary:**
|
|
1640
440
|
|
|
1641
441
|
> [!TIP]
|
|
1642
|
-
> **Choose your
|
|
442
|
+
> **Choose your strategy:**
|
|
1643
443
|
>
|
|
1644
|
-
> - **Startup
|
|
1645
|
-
> - **
|
|
1646
|
-
> - **
|
|
1647
|
-
> - **
|
|
444
|
+
> - **Startup-sensitive?** → Lazy mode (2.9x faster startup)
|
|
445
|
+
> - **Call-intensive?** → Eager mode (1.1x faster calls)
|
|
446
|
+
> - **Need predictability?** → Eager mode (no materialization delays)
|
|
447
|
+
> - **Large API, use subset?** → Lazy mode (memory efficient)
|
|
448
|
+
|
|
449
|
+
---
|
|
450
|
+
|
|
451
|
+
## 📊 Performance Analysis
|
|
452
|
+
|
|
453
|
+
For comprehensive performance benchmarks, analysis, and recommendations:
|
|
454
|
+
|
|
455
|
+
**📈 [See docs/PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/master/docs/PERFORMANCE.md)**
|
|
456
|
+
|
|
457
|
+
Key highlights:
|
|
458
|
+
|
|
459
|
+
- Detailed startup vs runtime performance comparison
|
|
460
|
+
- Memory usage analysis by loading mode
|
|
461
|
+
- Materialization cost breakdown by module type
|
|
462
|
+
- Real-world performance recommendations
|
|
463
|
+
|
|
464
|
+
[![CodeFactor]][codefactor_url] [![npms.io score]][npms_url]
|
|
465
|
+
|
|
466
|
+
[![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url]
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
## 📚 Documentation
|
|
471
|
+
|
|
472
|
+
### Core Documentation
|
|
473
|
+
|
|
474
|
+
- **[API Documentation](https://github.com/CLDMV/slothlet/blob/master/docs/generated/API.md)** - Complete API reference with examples and detailed parameter descriptions
|
|
475
|
+
- **[Performance Analysis](https://github.com/CLDMV/slothlet/blob/master/docs/PERFORMANCE.md)** - Detailed benchmarks and recommendations
|
|
476
|
+
- **[Contributing Guide](CONTRIBUTING.md)** - How to contribute to the project
|
|
477
|
+
- **[Security Policy](SECURITY.md)** - Security guidelines and reporting
|
|
478
|
+
- **[Test Documentation](api_tests)** - Comprehensive test module examples
|
|
479
|
+
|
|
480
|
+
### Technical Guides
|
|
481
|
+
|
|
482
|
+
- **[Hook System](https://github.com/CLDMV/slothlet/blob/master/docs/HOOKS.md)** - Complete hook system documentation with 4 hook types, pattern matching, and examples
|
|
483
|
+
- **[Context Propagation](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md)** - EventEmitter and class instance context preservation
|
|
484
|
+
- **[Module Structure](https://github.com/CLDMV/slothlet/blob/master/docs/MODULE-STRUCTURE.md)** - Comprehensive module organization patterns and examples
|
|
485
|
+
- **[API Flattening](https://github.com/CLDMV/slothlet/blob/master/docs/API-FLATTENING.md)** - The 5 flattening rules with decision tree and benefits
|
|
486
|
+
|
|
487
|
+
### API Rules & Transformation
|
|
488
|
+
|
|
489
|
+
- **[API Rules](https://github.com/CLDMV/slothlet/blob/master/docs/API-RULES.md)** - Systematically verified API transformation rules with real examples and test cases
|
|
490
|
+
- **[API Rules Conditions](https://github.com/CLDMV/slothlet/blob/master/docs/API-RULES-CONDITIONS.md)** - Complete technical reference of all 26 conditional statements that control API generation
|
|
491
|
+
|
|
492
|
+
### Changelog
|
|
493
|
+
|
|
494
|
+
- **[v2.9](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.9.md)** - Per-Request Context Isolation & API Builder Modularization (December 30, 2025)
|
|
495
|
+
- **[v2.8](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.8.md)** - NPM security fixes and package workflow updates (December 26, 2025)
|
|
496
|
+
- **[v2.7](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.7.md)** - Hook System with 4 interceptor types (December 20, 2025)
|
|
497
|
+
- **[v2.6](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.6.md)** - Mode/Engine options and deep nested path fixes (November 10, 2025)
|
|
498
|
+
- **[v2.5](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.5.md)** - Architectural consolidation and API consistency (October 20, 2025)
|
|
499
|
+
- **[v2.4](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.4.md)** - Multi-default export handling with file-based naming (October 18, 2025)
|
|
500
|
+
- **[v2.3](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.3.md)** - EventEmitter & Class Context Propagation (October 16, 2025)
|
|
501
|
+
- **[v2.2](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.2.md)** - Case preservation options (preserveAllUpper/preserveAllLower) (October 14, 2025)
|
|
502
|
+
- **[v2.1](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.1.md)** - Advanced sanitization with boundary patterns (October 12, 2025)
|
|
503
|
+
- **[v2.0](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.0.md)** - Complete Architectural Rewrite (September 9, 2025)
|
|
1648
504
|
|
|
1649
505
|
---
|
|
1650
506
|
|
|
@@ -1664,27 +520,6 @@ try {
|
|
|
1664
520
|
}
|
|
1665
521
|
```
|
|
1666
522
|
|
|
1667
|
-
**Planned Enhanced Error Features (v3.0.0):**
|
|
1668
|
-
|
|
1669
|
-
> [!TIP]
|
|
1670
|
-
> **Coming Soon**: Enhanced error handling with descriptive messages and module suggestions:
|
|
1671
|
-
>
|
|
1672
|
-
> ```javascript
|
|
1673
|
-
> try {
|
|
1674
|
-
> await api.nonexistent.method();
|
|
1675
|
-
> } catch (error) {
|
|
1676
|
-
> console.error(error.message);
|
|
1677
|
-
> // Planned: "Module 'nonexistent' not found in './api'. Available modules: math, string, util. Did you mean 'util'?"
|
|
1678
|
-
> }
|
|
1679
|
-
> ```
|
|
1680
|
-
>
|
|
1681
|
-
> **Planned Features:**
|
|
1682
|
-
>
|
|
1683
|
-
> - 🔍 **Module discovery**: Show available modules and suggest alternatives
|
|
1684
|
-
> - 📍 **Context information**: Include directory path and configuration details
|
|
1685
|
-
> - 🎯 **Actionable suggestions**: Provide specific guidance for resolution
|
|
1686
|
-
> - 🚀 **Development mode**: Additional debugging information when debug flag is enabled
|
|
1687
|
-
|
|
1688
523
|
---
|
|
1689
524
|
|
|
1690
525
|
## 🔧 Production vs Development Modes
|
|
@@ -1712,8 +547,6 @@ try {
|
|
|
1712
547
|
> - **Fork Mode**: Process isolation (in development)
|
|
1713
548
|
> - **Child Mode**: Child process execution (in development)
|
|
1714
549
|
> - **VM Mode**: Virtual machine context (in development)
|
|
1715
|
-
>
|
|
1716
|
-
> The experimental modes are located in `src/lib/engine/` and should not be used in production environments.
|
|
1717
550
|
|
|
1718
551
|
---
|
|
1719
552
|
|
|
@@ -1722,47 +555,26 @@ try {
|
|
|
1722
555
|
### Key Changes
|
|
1723
556
|
|
|
1724
557
|
1. **Import paths**: `@cldmv/slothlet` instead of specific file paths
|
|
1725
|
-
2. **Configuration**: New options (`api_mode`, `context`, `reference`)
|
|
558
|
+
2. **Configuration**: New options (`api_mode`, `context`, `reference`, `hooks`)
|
|
1726
559
|
3. **Function names**: Enhanced preservation of original capitalization
|
|
1727
560
|
4. **Module structure**: Mixed ESM/CJS support
|
|
1728
561
|
5. **Live bindings**: Dual runtime system with AsyncLocalStorage and live-bindings options
|
|
562
|
+
6. **Automatic instances**: No more query strings or `withInstanceId()` methods
|
|
1729
563
|
|
|
1730
564
|
### Migration Steps
|
|
1731
565
|
|
|
1732
566
|
```javascript
|
|
1733
|
-
// v1.3.x - API creation (same pattern as v2.x)
|
|
1734
|
-
import slothlet from "@cldmv/slothlet";
|
|
1735
|
-
const api = await slothlet({
|
|
1736
|
-
dir: "./api",
|
|
1737
|
-
lazy: true
|
|
1738
|
-
});
|
|
1739
|
-
|
|
1740
567
|
// v1.3.x - Multiple instances required query strings or withInstanceId()
|
|
1741
568
|
const api1 = await slothlet({ dir: "./api?instanceId=alice" });
|
|
1742
569
|
const api2 = slothlet.withInstanceId("bob");
|
|
1743
570
|
const bobApi = await api2({ dir: "./api" });
|
|
1744
571
|
|
|
1745
|
-
// v2.
|
|
1746
|
-
import slothlet from "@cldmv/slothlet";
|
|
1747
|
-
const api = await slothlet({
|
|
1748
|
-
dir: "./api",
|
|
1749
|
-
lazy: true,
|
|
1750
|
-
context: { user: "alice" }, // New: context injection
|
|
1751
|
-
api_mode: "auto" // New: API mode control
|
|
1752
|
-
});
|
|
1753
|
-
|
|
1754
|
-
// v2.0 - Multiple instances automatically isolated (no query strings needed)
|
|
572
|
+
// v2.x - Automatic instance isolation (no query strings needed)
|
|
1755
573
|
const api1 = await slothlet({ dir: "./api", context: { tenant: "alice" } });
|
|
1756
574
|
const api2 = await slothlet({ dir: "./api", context: { tenant: "bob" } });
|
|
575
|
+
// Instances completely isolated with their own contexts
|
|
1757
576
|
```
|
|
1758
577
|
|
|
1759
|
-
### Performance Improvements
|
|
1760
|
-
|
|
1761
|
-
- **Architectural optimizations** with copy-left materialization and AsyncLocalStorage integration
|
|
1762
|
-
- **Zero dependencies** - pure Node.js implementation reduces overhead
|
|
1763
|
-
- **Enhanced materialization** with copy-left optimization in lazy mode
|
|
1764
|
-
- **Modular design** improves maintainability and potential optimization opportunities
|
|
1765
|
-
|
|
1766
578
|
---
|
|
1767
579
|
|
|
1768
580
|
## 🤝 Contributing
|
|
@@ -1775,46 +587,12 @@ We welcome contributions! The experimental modes in particular need development
|
|
|
1775
587
|
4. **Provide feedback** on API design and performance
|
|
1776
588
|
5. **Documentation improvements** are always appreciated
|
|
1777
589
|
|
|
1778
|
-
See [CONTRIBUTING.md](
|
|
590
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
|
|
1779
591
|
|
|
1780
592
|
[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
|
|
1781
593
|
|
|
1782
594
|
---
|
|
1783
595
|
|
|
1784
|
-
## 📊 Performance Analysis
|
|
1785
|
-
|
|
1786
|
-
For comprehensive performance benchmarks, analysis, and recommendations:
|
|
1787
|
-
|
|
1788
|
-
**📈 [See PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)**
|
|
1789
|
-
|
|
1790
|
-
Key highlights:
|
|
1791
|
-
|
|
1792
|
-
- Detailed startup vs runtime performance comparison
|
|
1793
|
-
- Memory usage analysis by loading mode
|
|
1794
|
-
- Materialization cost breakdown by module type
|
|
1795
|
-
- Real-world performance recommendations
|
|
1796
|
-
|
|
1797
|
-
[![CodeFactor]][codefactor_url] [![npms.io score]][npms_url]
|
|
1798
|
-
|
|
1799
|
-
[![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url]
|
|
1800
|
-
|
|
1801
|
-
---
|
|
1802
|
-
|
|
1803
|
-
## 📚 Documentation
|
|
1804
|
-
|
|
1805
|
-
- **[API Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/docs/API.md)** - Complete API reference with examples
|
|
1806
|
-
- **[Performance Analysis](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)** - Detailed benchmarks and recommendations
|
|
1807
|
-
- **[Contributing Guide](https://github.com/CLDMV/slothlet/blob/HEAD/CONTRIBUTING.md)** - How to contribute to the project
|
|
1808
|
-
- **[Security Policy](https://github.com/CLDMV/slothlet/blob/HEAD/SECURITY.md)** - Security guidelines and reporting
|
|
1809
|
-
- **[Test Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/api_tests)** - Comprehensive test module examples
|
|
1810
|
-
|
|
1811
|
-
### 🔧 Technical Documentation
|
|
1812
|
-
|
|
1813
|
-
- **[API Rules](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES.md)** - Systematically verified API transformation rules with real examples and test cases
|
|
1814
|
-
- **[API Rules Conditions](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES-CONDITIONS.md)** - Complete technical reference of all 26 conditional statements that control API generation
|
|
1815
|
-
|
|
1816
|
-
---
|
|
1817
|
-
|
|
1818
596
|
## 🔗 Links
|
|
1819
597
|
|
|
1820
598
|
- **npm**: [@cldmv/slothlet](https://www.npmjs.com/package/@cldmv/slothlet)
|