@cldmv/slothlet
Version:
Slothlet: Modular API Loader for Node.js. Lazy mode dynamically loads API modules and submodules only when accessed, supporting both lazy and eager loading.
1,211 lines (899 loc) β’ 54.6 kB
Markdown
# @cldmv/slothlet
<div align="center">
<img src="https://github.com/CLDMV/slothlet/raw/HEAD/images/slothlet-logo-v1-horizontal-transparent.png" alt="Slothlet Logo" width="600">
</div>
**@cldmv/slothlet** is a sophisticated module loading framework that revolutionizes how you work with massive APIs in Node.js. Built for developers who demand smart, efficient module loading without compromising performance or developer experience.
Choose your loading strategy based on your needs: **lazy mode** loads modules on-demand for faster startup and lower memory usage, while **eager mode** loads everything upfront for maximum runtime performance and predictable behavior.
With our **copy-left materialization** in lazy mode, you get the best of both worlds: the memory efficiency of on-demand loading with near-eager performance on repeated calls. Once a module is materialized, it stays materializedβno re-processing overhead.
The name might suggest we're taking it easy, but don't be fooled. **Slothlet delivers speed where it counts**, with smart optimizations that make your APIs fly.
> _"slothlet is anything but slow."_
[![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] <!-- [![GitHub release]][github_release_url] -->[![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] <!-- [![Release date]][release_date_url] -->[![npm last update]][npm_last_update_url]
> [!NOTE]
> **π Production Ready Modes:**
>
> - **Eager Mode**: Fully stable and production-ready for immediate module loading
> - **Lazy Mode**: Production-ready with advanced copy-left materialization and 4.3x faster startup (1.1x slower function calls)
> [!CAUTION]
> **βοΈ Experimental Modes:**
>
> - **Worker, Fork, Child, VM modes**: In active development, not recommended for production use
>
> Please report issues and contribute feedback to help improve the experimental features.
[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
---
## β¨ What's New in v2.x
### π― **Complete Architectural Rewrite (v2.0)**
v2.0 represents a ground-up rewrite with enterprise-grade features:
- **Universal Module Support**: Load both ESM (`.mjs`) and CommonJS (`.cjs`) files seamlessly
- **AsyncLocalStorage Integration**: Advanced context isolation and live-binding system
- **4.3x Faster Startup**: Lazy mode achieves 564.17ΞΌs vs 2.45ms in eager mode
- **Copy-Left Materialization**: Once loaded, modules stay materialized for optimal performance
- **Zero Dependencies**: Pure Node.js implementation with no external dependencies
### ποΈ **Enhanced Architecture**
- **Modular Design**: Organized into `engine/`, `modes/`, `runtime/`, and `helpers/`
- **Live-Binding System**: Dynamic context and reference binding with runtime coordination
- **Smart Function Naming**: Preserves original capitalization (`autoIP`, `parseJSON`, `getHTTPStatus`)
- **Multi-Execution Environments**: Singleton, VM, worker, fork isolation modes (experimental)
### π§ **Advanced Sanitization Control** β NEW
- **Custom API Naming**: Control how filenames become API property names through sanitize options
- **Boundary Pattern Matching**: Use `**string**` patterns for precise transformations (`**url**` β `buildURLWithParams`)
- **Glob Pattern Support**: Apply rules with wildcards (`*json*`, `auto*`, `http*`) for flexible naming control
- **Case-Sensitive Rules**: Preserve important naming patterns (acronyms, technical terms, branding)
- **Mixed Rule Types**: Combine exact matches, globs, and boundary patterns for sophisticated naming strategies
### π **Performance Optimizations**
- **Startup**: Lazy mode 4.3x faster (564.17ΞΌs vs 2.45ms)
- **Function Calls**: Eager mode 1.1x faster (0.65ΞΌs vs 0.72ΞΌs) after materialization
- **Memory**: On-demand loading scales with actual usage
- **Predictability**: Consistent performance characteristics per mode
### π **Context Propagation (v2.3)** β NEW
- **EventEmitter Context Propagation**: Automatic context preservation across EventEmitter callbacks using AsyncResource patterns
- **Class Instance Context Propagation**: Automatic context preservation across class method calls with transparent wrapping
- **AsyncResource Integration**: Production-ready context management following Node.js best practices
- **Zero Configuration**: Works automatically with TCP servers, HTTP servers, and any EventEmitter-based patterns
---
## π Key Features
### π― **Dual Loading Strategies**
- **Eager Loading**: Immediate loading for maximum performance in production environments
- **Lazy Loading**: Copy-left materialization with look-ahead proxies (4.3x faster startup, 1.1x slower calls after materialization)
> [!IMPORTANT]
> **Function Call Patterns:**
>
> - **Lazy Mode**: ALL function calls must be awaited (`await api.math.add(2, 3)`) due to materialization process
> - **Eager Mode**: Functions behave as originally defined - sync functions are sync (`api.math.add(2, 3)`), async functions are async (`await api.async.process()`)
### β‘ Performance Excellence
- **π For comprehensive performance analysis, benchmarks, and recommendations, see [PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)**
### π§ **Smart API Management**
- **Callable Interface**: Use `slothlet(options)` for direct API creation
- **Smart Flattening**: Intelligent rules create clean APIs (`math/math.mjs` β `api.math`, `connection.mjs` β `api.connect()`)
- **Intelligent Naming**: Dash-separated names convert automatically (`root-math.mjs` β `api.rootMath`)
- **Function Name Preservation**: Maintains original capitalization (`auto-ip.mjs` with `autoIP` β `api.autoIP`)
- **Hybrid Exports**: Support for callable APIs with methods, default + named exports, and mixed patterns
> [!TIP]
> **π 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)**
> [!NOTE]
> **π For detailed technical documentation on API transformation rules:**
>
> - **[API-RULES.md](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES.md)** - Verified API transformation rules with examples and test cases
> - **[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
### π **Advanced Binding System**
- **Live Bindings**: Dynamic context and reference binding for runtime API mutation
- **AsyncLocalStorage**: Per-instance context isolation with seamless integration
- **Copy-Left Preservation**: Materialized functions stay materialized, preserving performance gains
- **Bubble-Up Updates**: Parent API synchronization ensures consistency across the API tree
- **Mixed Module Support**: Seamlessly blend ESM and CommonJS modules in the same API
### π **Developer Experience**
- **Standard Error Handling**: Clear JavaScript errors with plans for enhanced descriptive errors in v2.1.0
- **TypeScript-Friendly**: Comprehensive JSDoc annotations for excellent editor support with auto-generated declarations
- **Configurable Debug**: Detailed logging for development and troubleshooting via CLI flags or environment variables
- **Multiple Instances**: Parameter-based isolation for complex applications with instance ID management
- **Development Checks**: Built-in environment detection with silent production behavior
### π **Architecture & Compatibility**
- **ESM-First**: Built for modern JavaScript with full ES module support
- **Universal Loading**: CommonJS and ESM files work together seamlessly
- **Zero Dependencies**: Lightweight footprint with no external dependencies
- **Cross-Platform**: Works seamlessly across all Node.js environments
- **Extensible**: Modular architecture designed for future plugin system (in development)
---
## π¦ Installation
### Requirements
- **Node.js v16.4.0 or higher** (for stable AsyncLocalStorage support)
- **ESM support** (ES modules with `import`/`export`)
> [!IMPORTANT]
> **v2.x Breaking Change**: Slothlet v2.x requires AsyncLocalStorage for its comprehensive live-binding system, which was stabilized in Node.js v16.4.0+ (June 2021). If you need older Node.js versions, please use slothlet v1.x (which requires Node.js v12.20.0+ (November 2020) for ESM support, dynamic imports, and query string imports). Note that v1.x live-binding worked in ESM (including multiple APIs via query strings) but was not available for multiple API instances in CommonJS.
### Install
```bash
npm install @cldmv/slothlet
```
---
## π Quick Start
### ESM (ES Modules)
```javascript
import slothlet from "@cldmv/slothlet";
// Direct usage - eager mode by default (auto-detects callable interface)
const api = await slothlet({
dir: "./api",
context: { user: "alice" }
});
// Eager mode: Functions behave as originally defined
const result = api.math.add(2, 3); // Sync function - no await needed
const greeting = api("World"); // Instant if callable
// Original async functions still need await in eager mode
const asyncResult = await api.async.processData({ data: "async" });
// Access both ESM and CJS modules seamlessly
const esmResult = api.mathEsm.multiply(4, 5); // 20 (sync)
const cjsResult = await api.mathCjs.divide(10, 2); // 5 (if originally async)
```
### CommonJS (CJS)
```javascript
const slothlet = require("@cldmv/slothlet");
// Same usage pattern works with CommonJS
const api = await slothlet({
dir: "./api",
context: { env: "production" }
});
const result = api.math.multiply(4, 5); // 20
const mixedResult = await api.interop.processData({ data: "test" }); // CJS+ESM interop
```
### Lazy Loading Mode
```javascript
import slothlet from "@cldmv/slothlet";
// Lazy mode with copy-left materialization
const api = await slothlet({
mode: "lazy", // New preferred syntax
dir: "./api",
apiDepth: 3
});
// Or use legacy syntax (still supported)
const apiLegacy = await slothlet({
lazy: true, // Legacy syntax
dir: "./api",
apiDepth: 3
});
// First access: ~310ΞΌs (materialization overhead)
const result1 = await api.math.add(2, 3);
// Subsequent access: ~0.5ΞΌs (materialized function)
const result2 = await api.math.add(5, 7); // 700x faster than first call!
```
### Advanced Configuration
```javascript
import slothlet from "@cldmv/slothlet";
const api = await slothlet({
dir: "./api",
mode: "eager", // New: Loading strategy (lazy/eager)
engine: "singleton", // New: Execution environment
api_mode: "auto", // API structure behavior
apiDepth: Infinity, // Directory traversal depth
debug: false, // Enable verbose logging
context: {
// Injected into live-binding
user: "alice",
env: "production",
config: { timeout: 5000 }
},
reference: {
// Merged into API root
version: "2.0.0",
helpers: {
/* ... */
}
},
sanitize: {
// π§ NEW: Control API property naming
lowerFirst: false, // Keep first character casing
rules: {
leave: ["parseJSON", "autoIP"], // Preserve exact names
leaveInsensitive: ["*xml*"], // Case-insensitive preservation
upper: ["**url**", "api", "http*"], // Force uppercase (including boundary patterns)
lower: ["id", "uuid", "*id"] // Force lowercase
}
}
});
```
### Sanitize Options Examples
Transform module filenames into clean, professional API property names:
```javascript
// Without sanitize options (default behavior)
const api = await slothlet({ dir: "./api" });
// Files: build-url-with-params.mjs, parse-json-data.mjs, auto-ip.mjs
// Result: api.buildUrlWithParams, api.parseJsonData, api.autoIp
// With sanitize options (custom naming control)
const api = await slothlet({
dir: "./api",
sanitize: {
lowerFirst: false,
rules: {
leave: ["parseJSON"], // Exact match preservation
upper: ["**url**", "ip", "http*"], // Boundary + glob patterns
leaveInsensitive: ["*xml*"] // Case-insensitive globs
}
}
});
// Result: api.buildURLWithParams, api.parseJSON, api.autoIP
```
**Sanitize Pattern Types:**
- **Exact Match**: `"parseJSON"` - Matches exact string only
- **Glob Patterns**: `"*json*"`, `"auto*"`, `"http*"` - Wildcard matching
- **Boundary Patterns**: `"**url**"` - Only matches when surrounded by word boundaries
- **Case Control**: `leaveInsensitive` for case-insensitive matching
### Multiple Instances
In v2.x, each call to `slothlet(options)` automatically creates a new isolated instance with its own context and configuration:
#### ESM (ES Modules)
```javascript
import slothlet from "@cldmv/slothlet";
// Each call creates a new isolated instance automatically
const api1 = await slothlet({ dir: "./api1", context: { tenant: "alice" } });
const api2 = await slothlet({ dir: "./api2", context: { tenant: "bob" } });
// Instances are completely isolated
console.log(api1.context.tenant); // "alice"
console.log(api2.context.tenant); // "bob"
```
#### CommonJS (CJS)
```javascript
const slothlet = require("@cldmv/slothlet");
// Each call creates a new isolated instance automatically
const api1 = await slothlet({ dir: "./api1", context: { tenant: "alice" } });
const api2 = await slothlet({ dir: "./api2", context: { tenant: "bob" } });
// Instances are completely isolated with their own AsyncLocalStorage contexts
console.log(api1.context.tenant); // "alice"
console.log(api2.context.tenant); // "bob"
```
> [!NOTE]
> **v2.x Simplification**: Unlike v1.x which required query string parameters or `withInstanceId()` methods, v2.x automatically creates isolated instances with each `slothlet()` call, leveraging AsyncLocalStorage for complete context separation.
---
## π API Reference
### Core Methods
#### `slothlet(options)` β `Promise<object>`
Creates and loads an API instance with the specified configuration.
**Parameters:**
| Param | Type | Description |
| ------- | -------- | --------------------- |
| options | `object` | Configuration options |
**Returns:** `Promise<object>` - The bound API object
**Options:**
| Option | Type | Default | Description |
| ----------- | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dir` | `string` | `"api"` | Directory to load API modules from. Can be absolute or relative path. If relative, resolved from process.cwd(). |
| `lazy` | `boolean` | `false` | **Legacy** loading strategy - `true` for lazy loading (on-demand), `false` for eager loading (immediate). Use `mode` option instead. |
| `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. |
| `engine` | `string` | `"singleton"` | **New** execution environment mode - `"singleton"`, `"vm"`, `"worker"`, or `"fork"` |
| `apiDepth` | `number` | `Infinity` | Directory traversal depth control - `0` for root only, `Infinity` for all levels |
| `debug` | `boolean` | `false` | Enable verbose logging. Can also be set via `--slothletdebug` command line flag or `SLOTHLET_DEBUG=true` environment variable |
| `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) |
| `context` | `object` | `{}` | Context data object injected into live-binding `context` reference. Available to all loaded modules via `import { context } from "@cldmv/slothlet/runtime"` |
| `reference` | `object` | `{}` | Reference object merged into the API root level. Properties not conflicting with loaded modules are added directly to the API |
| `sanitize` | `object` | `{}` | **π§ NEW**: Control how filenames become API property names. Supports exact matches, glob patterns (`*json*`), and boundary patterns (`**url**`). Configure `lowerFirst` and `rules` for `leave`, `leaveInsensitive`, `upper`, and `lower` transformations |
#### β¨ New Option Format (v2.6.0+)
The option structure has been improved for better clarity:
```javascript
// β
New recommended syntax
const api = await slothlet({
mode: "lazy", // Loading strategy: "lazy" | "eager"
engine: "singleton", // Execution environment: "singleton" | "vm" | "worker" | "fork"
dir: "./api"
});
// β
Legacy syntax (still fully supported)
const api = await slothlet({
lazy: true, // Boolean loading strategy
mode: "singleton", // Execution environment (legacy placement)
dir: "./api"
});
// β
Mixed usage (mode takes precedence)
const api = await slothlet({
lazy: false, // Will be overridden
mode: "lazy", // Takes precedence - results in lazy loading
engine: "singleton"
});
```
**Benefits of the new syntax:**
- **Clearer separation**: `mode` for loading strategy, `engine` for execution environment
- **Better discoverability**: String values are more self-documenting than boolean flags
- **Future-proof**: Easier to extend with additional loading strategies
- **Backward compatible**: All existing code continues to work unchanged
#### `slothlet.getApi()` β `object`
Returns the raw API object (Proxy or plain object).
**Returns:** `function | object` - The raw API object or function
#### `slothlet.getBoundApi()` β `object`
Returns the bound API object with context and reference.
**Returns:** `function | object` - The bound API object or function with live bindings and context
#### `slothlet.isLoaded()` β `boolean`
Returns true if the API is loaded.
**Returns:** `boolean` - Whether the API has been loaded
#### `slothlet.shutdown()` β `Promise<void>`
Gracefully shuts down the API and cleans up resources.
**Returns:** `Promise<void>` - Resolves when shutdown is complete
> [!NOTE]
> **π 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)**
### Live Bindings
Access live-bound references in your API modules:
```javascript
// Create API with reference functions
const api = await slothlet({
dir: "./api",
reference: {
md5: (str) => crypto.createHash("md5").update(str).digest("hex"),
version: "2.0.0",
utils: { format: (msg) => `[LOG] ${msg}` }
}
});
```
```javascript
// In your API modules (ESM)
import { self, context, reference } from "@cldmv/slothlet/runtime";
export function myFunction() {
console.log(context.user); // Access live context
return self.otherModule.helper(); // Access other API modules
// Reference functions are available directly on self
const hash = self.md5("hello world"); // Access reference function
console.log(self.version); // Access reference data
}
// Mixed module example (ESM accessing CJS)
export function processData(data) {
// Call a CJS module from ESM
const processed = self.cjsModule.process(data);
// Use reference utilities directly
const logged = self.utils.format(`Processed: ${processed}`);
return self.md5(logged); // Hash the result
}
```
```javascript
// In your CJS modules
const { self, context, reference } = require("@cldmv/slothlet/runtime");
function cjsFunction(data) {
console.log(context.env); // Access live context
// Reference functions available directly on self
const hash = self.md5(data); // Direct access to reference function
return self.esmModule.transform(hash); // Access ESM modules from CJS
}
module.exports = { cjsFunction };
```
### EventEmitter Context Propagation
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.
```javascript
// api/tcp-server.mjs - Your API module
import { self, context } from "@cldmv/slothlet/runtime";
import net from "node:net";
export function createTcpServer() {
const server = net.createServer();
// Connection handler maintains full context automatically
server.on("connection", (socket) => {
console.log(`User: ${context.user}`); // β
Context preserved
console.log(`API keys: ${Object.keys(self).length}`); // β
Full API access
// Socket data handler also maintains context automatically
socket.on("data", (data) => {
console.log(`Session: ${context.session}`); // β
Context preserved
console.log(`Processing for: ${context.user}`); // β
Context preserved
// Full API access in nested event handlers
const processed = self.dataProcessor.handle(data.toString());
socket.write(processed);
});
socket.on("error", (err) => {
// Error handlers also maintain context
self.logger.error(`Error for user ${context.user}: ${err.message}`);
});
});
return server;
}
export function startServer(port = 3000) {
const server = createTcpServer();
server.listen(port);
return server;
}
```
```javascript
// Usage in your application
import slothlet from "@cldmv/slothlet";
const api = await slothlet({
dir: "./api",
context: { user: "alice", session: "tcp-session" }
});
// Start the server - all event handlers will have full context
const server = api.startServer(8080);
console.log("TCP server started with context preservation");
```
**Key Benefits:**
- β
**Automatic**: No configuration needed - works transparently in all API modules
- β
**Complete Context**: Full `context` object and `self` API access in all event handlers
- β
**Nested Events**: Works with any depth of EventEmitter nesting (server β socket β custom emitters)
- β
**Universal Support**: All EventEmitter methods (`on`, `once`, `addListener`) are automatically context-aware
- β
**Production Ready**: Uses Node.js AsyncResource patterns for reliable context propagation
- β
**Zero Overhead**: Only wraps listeners when context is active, minimal performance impact
> [!TIP]
> **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.
### Class Instance Context Propagation
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.
```javascript
// api/data-processor.mjs - Your API module
import { self, context } from "@cldmv/slothlet/runtime";
class DataProcessor {
constructor(config) {
this.config = config;
}
process(data) {
// Context automatically available in all methods
console.log(`Processing for user: ${context.user}`); // β
Context preserved
console.log(`Request ID: ${context.requestId}`); // β
Context preserved
// Full API access in class methods
const validated = self.validator.check(data);
return this.transform(validated);
}
transform(data) {
// Context preserved in nested method calls
console.log(`Transforming for: ${context.user}`); // β
Context preserved
// Call other API modules from class methods
return self.utils.format(data);
}
}
export function createProcessor(config) {
// Return class instance - slothlet automatically wraps it
return new DataProcessor(config);
}
```
```javascript
// Usage in your application
import slothlet from "@cldmv/slothlet";
const api = await slothlet({
dir: "./api",
context: { user: "alice", requestId: "req-123" }
});
// Create processor instance - all methods will have full context
const processor = api.createProcessor({ format: "json" });
// All method calls maintain context automatically
const result = processor.process({ data: "test" });
console.log("Processing completed with context preservation");
```
**Key Benefits:**
- β
**Automatic**: Class instances returned from API functions are automatically context-aware
- β
**Transparent**: No code changes needed - works with existing class patterns
- β
**Complete Context**: Full `context` object and `self` API access in all class methods
- β
**Nested Methods**: Context preserved across method chains and internal calls
- β
**Constructor Support**: Context preserved for both function calls and `new` constructor usage
- β
**Performance Optimized**: Method wrapping is cached to avoid overhead on repeated calls
> [!TIP]
> **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.
### API Mode Configuration
The `api_mode` option controls how slothlet handles root-level default function exports:
#### Auto-Detection (Recommended)
```javascript
const api = await slothlet({
api_mode: "auto" // Default - automatically detects structure
});
// If you have a root-level function export:
// root-function.mjs: export default function(name) { return `Hello, ${name}!` }
// Result: api("World") works AND api.otherModule.method() works
// If you only have object exports:
// Result: api.math.add() works, api("World") doesn't exist
```
#### Explicit Function Mode
```javascript
const api = await slothlet({
api_mode: "function" // Force callable interface
});
// Always creates callable API even without root default export
// Useful when you know you have root functions
const result = api("World"); // Calls root default function
const math = api.math.add(2, 3); // Also access other modules
```
#### Explicit Object Mode
```javascript
const api = await slothlet({
api_mode: "object" // Force object-only interface
});
// Always creates object interface even with root default export
// api("World") won't work, but api.rootFunction("World") will
const result = api.rootFunction("World"); // Access via property
const math = api.math.add(2, 3); // Normal module access
```
---
## π Module Structure & Examples
Slothlet supports sophisticated module organization patterns with seamless ESM/CJS interoperability:
### Root-Level Modules
```text
root-math.mjs β api.rootMath (dash-to-camelCase)
rootstring.mjs β api.rootstring
config.mjs β api.config
```
### Filename-Folder Matching Modules
```text
math/math.mjs β api.math (filename matches folder)
string/string.mjs β api.string (filename matches folder)
util/util.cjs β api.util (CJS support with filename matching)
```
### Multi-File Modules
```text
multi/
βββ alpha.mjs β api.multi.alpha
βββ beta.mjs β api.multi.beta
βββ gamma.cjs β api.multi.gamma (mixed ESM/CJS)
```
### Function-Based Modules
```text
funcmod/funcmod.mjs β api.funcmod() (callable function)
multi_func/
βββ alpha.mjs β api.multi_func.alpha()
βββ beta.cjs β api.multi_func.beta() (CJS callable)
```
### Mixed ESM/CJS Modules
```text
interop/
βββ esm-module.mjs β api.interop.esmModule
βββ cjs-module.cjs β api.interop.cjsModule
βββ mixed.mjs β api.interop.mixed (calls both ESM and CJS)
```
### Hybrid Export Patterns
```text
exportDefault/exportDefault.mjs β api.exportDefault() (callable with methods)
objectDefaultMethod/ β api.objectDefaultMethod() (object with default)
```
### Nested Structure
```text
nested/
βββ date/
βββ date.mjs β api.nested.date
βββ util.cjs β api.nested.dateUtil
advanced/
βββ selfObject/ β api.advanced.selfObject
βββ nest*/ β Various nesting examples
```
### Utility Modules
```text
util/
βββ controller.mjs β api.util.controller
βββ extract.cjs β api.util.extract (CJS utility)
βββ url/
βββ parser.mjs β api.util.url.parser
βββ builder.cjs β api.util.url.builder (mixed)
```
### Smart Function Naming Examples
```text
task/auto-ip.mjs (exports autoIP) β api.task.autoIP (preserves function name)
util/parseJSON.mjs β api.util.parseJSON (preserves JSON casing)
api/getHTTPStatus.mjs β api.api.getHTTPStatus (preserves HTTP casing)
```
## ποΈ API Flattening Rules
Slothlet uses intelligent flattening rules to create clean, intuitive API structures. Understanding these rules helps you organize your modules for the best developer experience:
### 1. **Filename-Folder Matching** (Single Named Export)
**When:** A file exports a single named export that matches the sanitized filename
**Why:** Avoids redundant nesting (`api.math.math.add()` β `api.math.add()`)
**Reasoning:** When file purpose matches folder purpose, eliminate the duplicate layer
```text
math/math.mjs (exports { math: {...} }) β api.math (flattened)
string/string.mjs (exports { string: {...} }) β api.string (flattened)
util/util.mjs (exports { util: {...} }) β api.util (flattened)
```
### 2. **No Default Export + Only Named Exports** β NEW
**When:** A file has **no default export** and **only named exports**
**Why:** The file acts as a pure function collection, not a module with a main export
**Reasoning:** If there's no "main thing" (default export), treat all functions as equals at the root level
```text
connection.mjs (exports { connect, disconnect, isConnected })
β api.connect(), api.disconnect(), api.isConnected()
Because: No default export = no main "connection" object needed
app.mjs (exports { getAllApps, getCurrentApp, setApp })
β api.getAllApps(), api.getCurrentApp(), api.setApp()
Because: No default export = these are standalone utility functions
state.mjs (exports { cloneState, emitLog, reset, update })
β api.cloneState(), api.emitLog(), api.reset(), api.update()
Because: No default export = treat as individual state utilities
```
### 3. **Has Default Export** (Namespace Preservation)
**When:** A file has a default export (with or without named exports)
**Why:** The default export indicates there's a "main thing" that should be the namespace
**Reasoning:** Default export signals intentional module structure that should be preserved
```text
config.mjs (exports default + named exports) β api.config.*
Because: Default export indicates a main config object with methods
input.mjs (exports default + named exports) β api.input.*
Because: Default export indicates a main input handler with utilities
volume.mjs (exports default + named exports) β api.volume.*
Because: Default export indicates a main volume controller with methods
```
### 4. **Root-Level Special Cases**
**When:** Files are at the root directory level (not in subfolders)
**Why:** Prevents accidental API pollution and maintains clear root structure
**Reasoning:** Root files are explicitly placed there and should maintain their intended naming
```text
root-math.mjs β api.rootMath (namespace preserved)
Because: Explicitly named "root-math" = keep as intended namespace
rootstring.mjs β api.rootstring (namespace preserved)
Because: Root-level placement = developer wants this specific API structure
config.mjs β api.config (namespace preserved)
Because: Root config file = keep as clear config namespace, don't flatten
```
### 5. **Self-Referential Prevention**
**When:** A file would create circular/redundant nesting (`api.config.config`)
**Why:** Prevents infinite nesting and maintains clean API structure
**Reasoning:** When file/folder names match, assume they represent the same logical concept
```text
config/config.mjs β api.config (prevented: api.config.config.config...)
Because: config.mjs in config/ folder = same concept, use folder name only
```
### π― Flattening Decision Tree
```mermaid
flowchart TD
FILE[Module File] --> ROOT{Root Level?}
ROOT -->|Yes| PRESERVE[Preserve Namespace<br/>api.rootMath, api.config]
ROOT -->|No| SELFREFER{Self-Referential?}
SELFREFER -->|Yes| NAMESPACE[Use Namespace<br/>api.config]
SELFREFER -->|No| HASDEFAULT{Has Default Export?}
HASDEFAULT -->|Yes| NAMESPACE
HASDEFAULT -->|No| NAMEDONLY{Only Named Exports?}
NAMEDONLY -->|Yes| FLATTEN[Flatten All Named Exports<br/>api.connect(), api.disconnect()]
NAMEDONLY -->|No| SINGLENAMED{Single Named Export<br/>Matching Filename?}
SINGLENAMED -->|Yes| FLATTENSINGLE[Flatten Single Export<br/>api.math]
SINGLENAMED -->|No| NAMESPACE
style FLATTEN fill:#e1f5fe
style FLATTENSINGLE fill:#e8f5e8
style NAMESPACE fill:#fff3e0
style PRESERVE fill:#fce4ec
```
### π Benefits of Smart Flattening
- **Cleaner APIs**: `api.connect()` instead of `api.connection.connect()`
_Why it matters:_ Reduces typing, improves readability, and matches how you'd naturally call connection functions
- **Intuitive Structure**: File organization matches API usage patterns
_Why it matters:_ Files with only utility functions flatten (no main export = no namespace needed), while files with main exports preserve their intended structure
- **Flexible Organization**: Mix flattened and nested patterns as needed
_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
- **Developer Intent Respected**: Export structure signals your architectural intentions
_Why it matters:_ Default exports = "this is a main thing with methods", named exports only = "these are utility functions"
- **Backward Compatibility**: Existing APIs continue to work as expected
_Why it matters:_ The rules are additive - existing filename-matching and default export patterns still work exactly the same
## π How Slothlet Works: Loading Modes Explained
```mermaid
flowchart TD
MODULEFOLDERS --> SLOTHLET
SLOTHLET --> CHOOSEMODE
CHOOSEMODE --> LAZY
CHOOSEMODE --> EAGER
subgraph EAGER ["β‘ Eager Mode"]
direction TB
EAGER0 ~~~ EAGER1
EAGER2 ~~~ EAGER3
EAGER0@{ shape: braces, label: "π₯ All modules loaded immediately" }
EAGER1@{ shape: braces, label: "β
API methods available right away" }
EAGER2@{ shape: braces, label: "π Function calls behave as originally defined" }
EAGER3@{ shape: braces, label: "π Sync stays sync: api.math.add(2,3)<br/>π Async stays async: await api.async.process()" }
end
subgraph LAZY ["π€ Lazy Mode"]
direction TB
LAZY0 ~~~ LAZY1
LAZY2 ~~~ LAZY3
LAZY4 ~~~ LAZY5
LAZY0@{ shape: braces, label: "π¦ Modules not loaded yet" }
LAZY1@{ shape: braces, label: "π API methods are placeholders/proxies" }
LAZY2@{ shape: braces, label: "π First call triggers materialization" }
LAZY3@{ shape: braces, label: "β³ All calls must be awaited<br/>await api.math.add(2,3)" }
LAZY4@{ shape: braces, label: "πΎ Module stays loaded after materialization<br/>Copy-left materialization" }
LAZY5@{ shape: braces, label: "π Subsequent calls nearly as fast as eager mode" }
end
subgraph EAGERCALL ["β‘ Eager Mode Calls"]
direction TB
end
subgraph LAZYCALL ["π€ Lazy Mode Calls"]
direction TB
LAZYCALL0 --> LAZYCALL2
LAZYCALL0@{ shape: rounded, label: "π First call" }
LAZYCALL1@{ shape: rounded, label: "π Sequential calls" }
LAZYCALL2@{ shape: rounded, label: "π§© Materialize" }
end
EAGER --> READYTOUSE
LAZY --> READYTOUSE
READYTOUSE --> CALL
CALL -.-> EAGERCALL
CALL -.-> LAZYCALL
EAGERCALL --> MATERIALIZEDFUNCTION
LAZYCALL1 --> MATERIALIZEDFUNCTION
LAZYCALL2 --> MATERIALIZEDFUNCTION
READYTOUSE@{ shape: rounded, label: "π― Ready to Use" }
MATERIALIZEDFUNCTION@{ shape: rounded, label: "β
Materialized method/property" }
CALL@{ shape: trap-b, label: "π Call" }
%% Notes as unattached nodes with braces shape
subgraph ALWAYS ["β¨ Extras Always On"]
direction TB
ALWAYS0 ~~~ ALWAYS1
ALWAYS1 ~~~ ALWAYS2
ALWAYS0@{ shape: rounded, label: "π Live Bindings ALS<br/>Per-instance context isolation" }
ALWAYS1@{ shape: rounded, label: "π·οΈ Smart Naming & Flattening<br/>Multiple rules for clean APIs" }
ALWAYS2@{ shape: rounded, label: "π Mixed Module Support<br/>Seamlessly mix .mjs and .cjs" }
end
MODULEFOLDERS@{ shape: st-rect, label: "π Modules Folder<br/>.mjs and/or .cjs files<br/>math.mjs, string.cjs, async.mjs" }
SLOTHLET@{ shape: rounded, label: "π§ Call slothlet(options)" }
CHOOSEMODE@{ shape: diamond, label: "Choose Mode<br/>in options" }
style EAGER0 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style EAGER1 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style EAGER2 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style EAGER3 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style LAZY0 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style LAZY1 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style LAZY2 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style LAZY3 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style LAZY4 stroke:#9BC66B,color:#9BC66B,opacity:0.5
style LAZY5 stroke:#9BC66B,color:#9BC66B,opacity:0.5
%% Slothlet brand colors - #9BC66B primary on dark theme
style MODULEFOLDERS fill:#1a1a1a,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
style SLOTHLET fill:#1a1a1a,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
style CHOOSEMODE fill:#1a1a1a,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
style READYTOUSE fill:#1a1a1a,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
style CALL fill:#1a1a1a,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
style MATERIALIZEDFUNCTION fill:#1a1a1a,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
%% Eager mode - primary green
style EAGER fill:#0d1a0d,stroke:#9BC66B,stroke-width:3px,color:#9BC66B,opacity:0.5
style EAGERCALL fill:#0d1a0d,stroke:#9BC66B,stroke-width:2px,color:#9BC66B,opacity:0.5
%% Lazy mode - lighter green tint
style LAZY fill:#0d1a0d,stroke:#B8D982,stroke-width:3px,color:#B8D982,opacity:0.5
style LAZYCALL fill:#0d1a0d,stroke:#B8D982,stroke-width:2px,color:#B8D982,opacity:0.5
style LAZYCALL0 fill:#1a1a1a,stroke:#B8D982,stroke-width:2px,color:#B8D982,opacity:0.5
style LAZYCALL1 fill:#1a1a1a,stroke:#B8D982,stroke-width:2px,color:#B8D982,opacity:0.5
style LAZYCALL2 fill:#1a1a1a,stroke:#B8D982,stroke-width:2px,color:#B8D982,opacity:0.5
%% Always available - accent green
style ALWAYS fill:#0d1a0d,stroke:#7FA94F,stroke-width:3px,color:#7FA94F,opacity:0.5
style ALWAYS0 fill:#1a1a1a,stroke:#7FA94F,stroke-width:1px,color:#7FA94F,opacity:0.5
style ALWAYS1 fill:#1a1a1a,stroke:#7FA94F,stroke-width:1px,color:#7FA94F,opacity:0.5
style ALWAYS2 fill:#1a1a1a,stroke:#7FA94F,stroke-width:1px,color:#7FA94F,opacity:0.5
%% Arrow styling
linkStyle default stroke:#9BC66B,stroke-width:3px,opacity:0.5
linkStyle 4,5,6,7,8,18,19 stroke-width:0px
```
---
## π Performance Modes
### Eager Mode (Default - Production Ready)
```javascript
const api = await slothlet({ dir: "./api" }); // lazy: false by default
// Functions behave as originally defined - no await needed for sync functions
const result = api.math.add(2, 3); // Sync function - direct call
const greeting = api("World"); // Instant if callable
// Async functions still need await (as originally defined)
const asyncResult = await api.async.processData({ data: "test" }); // Original async function
// ESM+CJS works seamlessly with native behavior
const mixed = api.interop.process({ data: "test" }); // Sync or async as defined
```
**Benefits:**
- β
Fastest function calls (0.36ΞΌs average)
- β
Predictable performance
- β
No materialization delays
- β
Functions behave exactly as originally defined (sync stays sync, async stays async)
- β
Optimal for production environments
### Lazy Mode with Copy-Left Materialization (Production Ready)
```javascript
const api = await slothlet({ lazy: true, dir: "./api" });
// ALL function calls must be awaited in lazy mode (due to materialization)
const result1 = await api.math.add(2, 3); // First access: ~310ΞΌs (materialization)
const result2 = await api.math.add(5, 7); // Subsequent: ~0.5ΞΌs (materialized)
// Even originally sync functions need await in lazy mode
const greeting = await api("World"); // Callable interface also needs await
const syncResult = await api.string.format("Hello"); // Originally sync, but needs await
```
**Benefits:**
- β
4.3x faster startup (564.17ΞΌs vs 2.45ms)
- β
Memory efficient (loads only what you use)
- β
Copy-left optimization (once loaded, stays loaded)
- β
Optimal for startup-sensitive applications
- β οΈ All function calls require await (regardless of original sync/async nature)
**Performance Summary:**
> [!TIP]
> **Choose your loading strategy based on your needs:**
>
> - **Startup**: Lazy wins (4.3x faster - 564.17ΞΌs vs 2.45ms)
> - **Function calls**: Eager wins (1.1x faster - 0.65ΞΌs vs 0.72ΞΌs)
> - **Memory**: Lazy wins (loads only what you use)
> - **Predictability**: Eager wins (no materialization delays)
---
## π‘ Error Handling
> [!NOTE]
> **Current Error Behavior**: Slothlet currently uses standard JavaScript error handling. Enhanced error handling with module suggestions is planned for v2.1.0 but not yet implemented.
**Current behavior:**
```javascript
try {
console.log(api.nonexistent); // Returns: undefined
await api.nonexistent.method(); // Throws: "Cannot read properties of undefined (reading 'method')"
} catch (error) {
console.error(error.message); // Standard JavaScript error message
}
```
**Planned Enhanced Error Features (v2.1.0):**
> [!TIP]
> **Coming Soon**: Enhanced error handling with descriptive messages and module suggestions:
>
> ```javascript
> try {
> await api.nonexistent.method();
> } catch (error) {
> console.error(error.message);
> // Planned: "Module 'nonexistent' not found in './api'. Available modules: math, string, util. Did you mean 'util'?"
> }
> ```
>
> **Planned Features:**
>
> - π **Module discovery**: Show available modules and suggest alternatives
> - π **Context information**: Include directory path and configuration details
> - π― **Actionable suggestions**: Provide specific guidance for resolution
> - π **Development mode**: Additional debugging information when debug flag is enabled
---
## π§ Production vs Development Modes
### Production Ready β
- **Eager Mode**: Stable, battle-tested, maximum performance
- **Lazy Mode**: Production-ready with copy-left optimization
- **Singleton Mode**: Default mode for standard applications
- **Mixed Module Loading**: ESM/CJS interoperability fully supported
### Development Features π οΈ
- **Development Check**: `devcheck.mjs` for environment validation
- **Debug Mode**: Comprehensive logging via `--slothletdebug` flag or `SLOTHLET_DEBUG=true`
- **Performance Monitoring**: Built-in timing and performance analysis
- **Source Detection**: Automatic `src/` vs `dist/` mode detection
### Experimental β οΈ
> [!WARNING]
> The following modes are in active development and not recommended for production use:
>
> - **Worker Mode**: Thread isolation (in development)
> - **Fork Mode**: Process isolation (in development)
> - **Child Mode**: Child process execution (in development)
> - **VM Mode**: Virtual machine context (in development)
>
> The experimental modes are located in `src/lib/engine/` and should not be used in production environments.
---
## π Migration from v1.x
### Key Changes
1. **Import paths**: `@cldmv/slothlet` instead of specific file paths
2. **Configuration**: New options (`api_mode`, `context`, `reference`)
3. **Function names**: Enhanced preservation of original capitalization
4. **Module structure**: Mixed ESM/CJS support
5. **Live bindings**: New runtime system with AsyncLocalStorage
### Migration Steps
```javascript
// v1.3.x - API creation (same pattern as v2.x)
import slothlet from "@cldmv/slothlet";
const api = await slothlet({
dir: "./api",
lazy: true
});
// v1.3.x - Multiple instances required query strings or withInstanceId()
const api1 = await slothlet({ dir: "./api?instanceId=alice" });
const api2 = slothlet.withInstanceId("bob");
const bobApi = await api2({ dir: "./api" });
// v2.0 - Same API creation, but automatic instance isolation
import slothlet from "@cldmv/slothlet";
const api = await slothlet({
dir: "./api",
lazy: true,
context: { user: "alice" }, // New: context injection
api_mode: "auto" // New: API mode control
});
// v2.0 - Multiple instances automatically isolated (no query strings needed)
const api1 = await slothlet({ dir: "./api", context: { tenant: "alice" } });
const api2 = await slothlet({ dir: "./api", context: { tenant: "bob" } });
```
### Performance Improvements
- **Architectural optimizations** with copy-left materialization and AsyncLocalStorage integration
- **Zero dependencies** - pure Node.js implementation reduces overhead
- **Enhanced materialization** with copy-left optimization in lazy mode
- **Modular design** improves maintainability and potential optimization opportunities
---
## π€ Contributing
We welcome contributions! The experimental modes in particular need development and testing. Please:
1. **Review the code** in `src/lib/engine/` for experimental features
2. **Report issues** with detailed reproduction steps
3. **Submit pull requests** with comprehensive tests
4. **Provide feedback** on API design and performance
5. **Documentation improvements** are always appreciated
See [CONTRIBUTING.md](https://github.com/CLDMV/slothlet/blob/HEAD/CONTRIBUTING.md) for detailed contribution guidelines.
[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url]
---
## π Performance Analysis
For comprehensive performance benchmarks, analysis, and recommendations:
**π [See PERFORMANCE.md](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)**
Key highlights:
- Detailed startup vs runtime performance comparison
- Memory usage analysis by loading mode
- Materialization cost breakdown by module type
- Real-world performance recommendations
[![CodeFactor]][codefactor_url] [![npms.io score]][npms_url]
[![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url]
---
## π Documentation
- **[API Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/docs/API.md)** - Complete API reference with examples
- **[Performance Analysis](https://github.com/CLDMV/slothlet/blob/HEAD/PERFORMANCE.md)** - Detailed benchmarks and recommendations
- **[Contributing Guide](https://github.com/CLDMV/slothlet/blob/HEAD/CONTRIBUTING.md)** - How to contribute to the project
- **[Security Policy](https://github.com/CLDMV/slothlet/blob/HEAD/SECURITY.md)** - Security guidelines and reporting
- **[Test Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/api_tests)** - Comprehensive test module examples
### π§ Technical Documentation
- **[API Rules](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES.md)** - Systematically verified API transformation rules with real example