Skip to content

CLDMV/slothlet

@cldmv/slothlet

Slothlet Logo

@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.

🎉 Welcome to the future of module loading with Slothlet v3!

Where sophisticated architecture meets blazing performance - slothlet is anything but slow.

npm version npm downloads GitHub downloads Last commit npm last update

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 2.2x faster startup (function calls within 6% of eager - essentially equal)

Contributors Sponsor shinrai


🎉 Introducing Slothlet v3.0

Important

Slothlet v3.0 is a major release - the biggest since v2.0.

v3 rebuilds Slothlet from the inside out with a Unified Wrapper architecture that delivers consistent, inspectable, hook-intercepted API proxies across every loading mode. On top of this foundation comes a redesigned hook system with three-phase subset ordering, per-request context isolation improvements, a full internationalization layer, background materialization with progress tracking, granular API mutation controls, collision modes for runtime API management, and lifecycle events for every stage of the module lifecycle.

Every feature has been hardened with a comprehensive test suite - over 5,300 tests across eager, lazy, CJS, ESM, TypeScript, and mixed module scenarios.

What's New in v3.0

  • 🏗️ Unified Wrapper - single consistent proxy layer for all modes; console.log(api.math) now shows real contents
  • 🎣 Redesigned Hook System - new hook: config key, api.slothlet.hook.* access path, three-phase subset ordering (before → primary → after)
  • 🌍 Full i18n - all error and debug messages are translated and available in 9 languages: English, Spanish, French, German, Portuguese, Italian, Japanese, Chinese (Simplified), and Korean
  • 💤 Background Materialization - backgroundMaterialize: true pre-loads lazy modules without blocking; api.slothlet.materialize.wait() to await completion
  • Lifecycle Events - subscribe to impl:created, impl:changed, impl:removed, and materialized:complete via api.slothlet.lifecycle.on/off()
  • 🔀 Collision Modes - replace allowApiOverwrite with typed modes: merge, skip, overwrite, throw - independently configurable for initial load vs runtime add()
  • 🔒 Mutation Controls - granular per-operation enable/disable for add, remove, and reload
  • 🧹 Sanitization Improvements - runtime api.slothlet.sanitize() method; helper export renamed to sanitizePropertyName
  • 🔄 Improved Context Isolation - .run() and .scope() use a child-instance model; full isolation between Slothlet instances

📋 See the full v3.0 changelog


✨ What's New

Latest: v3.1.0 (March 2026)

  • Environment Snapshotapi.slothlet.env exposes a frozen copy of process.env captured at initialization time; every module accesses it via self.slothlet.env
  • env.include Allowlist — restrict the snapshot to specific keys with env: { include: ["NODE_ENV", "PORT"] }; empty array falls back to full snapshot
  • Reload Immunity — snapshot is captured once on first load() and never replaced on subsequent api.slothlet.reload() calls (including partial api.slothlet.api.reload() calls)
  • View full v3.1.0 Changelog

Recent Releases

  • v3.0.1 (March 2026) — Resolver fix for user index.mjs mis-classified as internal; CI slothlet-dev stripping hardening; respawn race fix; resilient build:dist script (Changelog)
  • v3.0.0 (February 2026) — Unified Wrapper architecture, redesigned hook system, full i18n, background materialization, lifecycle events, collision modes, mutation controls (Changelog)
  • v2.11.0 — AddApi Special File Pattern (Rule 11), smart flattening enhancements (Changelog)
  • v2.10.0 — Function metadata tagging and introspection capabilities (Changelog)
  • v2.9 — Per-Request Context Isolation (Changelog)

📚 For complete version history and detailed release notes, see docs/changelog/ folder.


🚀 Key Features

🎯 Dual Loading Strategies

  • Eager Loading: Immediate loading for maximum performance in production environments
  • Lazy Loading: Copy-left materialization with look-ahead proxies (2.2x faster startup, function calls equal to eager 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

  • Startup Performance: 2.2x faster startup in lazy mode (15.41ms vs 34.28ms)
  • Runtime Performance: Function calls essentially equal between modes (9.99μs lazy vs 9.46μs eager - within 6% measurement noise)
  • Copy-left materialization: Once loaded, modules stay materialized - no re-processing overhead
  • Zero dependencies: Pure Node.js implementation
  • Memory efficiency: Lazy mode loads modules on-demand, eager mode optimizes for predictable behavior

Mode Selection Guide:

  • Eager Mode: Best for production environments with maximum runtime performance and predictable behavior
  • Lazy Mode: Best for development and applications with large APIs where startup time matters

📊 For comprehensive performance benchmarks and analysis, see docs/PERFORMANCE.md

🎣 Hook System (redesigned in v3)

Powerful function interceptor system with 4 hook types and three-phase subset ordering:

  • before - Modify arguments or cancel execution (must be synchronous)
  • after - Transform return values
  • always - Observe final results (read-only; fires even on short-circuit)
  • error - Monitor and handle errors with detailed source tracking

Each hook type supports three ordered execution subsets: "before""primary" (default) → "after". Pattern matching, priority control, runtime enable/disable, and short-circuit support included.

🎣 For complete hook system documentation, see docs/HOOKS.md

🌍 Full Internationalization (new in v3)

All error messages and debug output are translated. Supported languages: English · Spanish · French · German · Portuguese · Italian · Japanese · Chinese (Simplified) · Korean

Configure via i18n: { language: "es" } in your slothlet config.

🔄 Context Propagation

Automatic context preservation across all asynchronous boundaries:

  • Per-request isolation: api.slothlet.context.run(ctx, fn) and api.slothlet.context.scope(ctx)
  • EventEmitter propagation: Context maintained across all event callbacks
  • Class instance propagation: Context preserved in class method calls
  • Zero configuration: Works automatically with TCP servers, HTTP servers, and custom EventEmitters

🔄 For context propagation details, see docs/CONTEXT-PROPAGATION.md

🔧 Smart API Management

  • Intelligent Flattening: Clean APIs with automatic structure optimization (math/math.mjsapi.math)
  • Smart Naming: Preserves original capitalization (auto-ip.mjs with autoIPapi.autoIP)
  • Advanced Sanitization: Custom naming rules with glob and boundary patterns; api.slothlet.sanitize() at runtime
  • Hybrid Exports: Support for callable APIs with methods, default + named exports

🏗️ For module structure examples, see docs/MODULE-STRUCTURE.md 📐 For API flattening rules, see docs/API-RULES/API-FLATTENING.md

🔗 Runtime & Context System

  • Context Isolation: Automatic per-request isolation using AsyncLocalStorage (default); switchable to live-bindings mode via runtime: "live" config option
  • Cross-Module Access: self and context always available inside API modules via @cldmv/slothlet/runtime
  • Mixed Module Support: Seamlessly blend ESM and CommonJS modules
  • Copy-Left Preservation: Materialized functions stay materialized

🛠 Developer Experience

  • TypeScript-Friendly: Comprehensive JSDoc annotations with auto-generated declarations
  • Configurable Debug: Detailed logging via CLI flags or environment variables
  • Multiple Instances: Parameter-based isolation for complex applications
  • Inspectable APIs: console.log(api.math) now shows real module contents (v3+)
  • Development Checks: Built-in environment detection with silent production behavior

📦 Installation

Requirements

  • Node.js v16.20.2 or higher (required for stack trace API fixes used in path resolution)
    • Node.js 16.4–16.19 has a stack trace regression. For these versions, use slothlet 2.10.0: npm install @cldmv/slothlet@2.10.0

Install

npm install @cldmv/slothlet

🚀 Quick Start

ESM (ES Modules)

import slothlet from "@cldmv/slothlet";

// Direct usage - eager mode by default
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 asyncResult = await api.async.processData({ data: "async" });

// Access both ESM and CJS modules seamlessly
const esmResult = api.mathEsm.multiply(4, 5);
const cjsResult = api.mathCjs.divide(10, 2);

CommonJS (CJS)

const slothlet = require("@cldmv/slothlet");

const api = await slothlet({
	dir: "./api",
	context: { env: "production" }
});

const result = api.math.multiply(4, 5);
const mixedResult = await api.interop.processData({ data: "test" });

Lazy Loading Mode

import slothlet from "@cldmv/slothlet";

// Lazy mode with copy-left materialization
const api = await slothlet({
	mode: "lazy",
	dir: "./api",
	apiDepth: 3
});

// First access: materialization overhead (~1.45ms average)
const result1 = await api.math.add(2, 3);

// Subsequent access: materialized function (near-eager performance)
const result2 = await api.math.add(5, 7);

Hook System Example (v3 API)

import slothlet from "@cldmv/slothlet";

const api = await slothlet({
	dir: "./api",
	hook: true // Enable hooks - note: "hook" singular (v3)
});

// Before hook: Modify arguments
api.slothlet.hook.on(
	"before:math.add",
	({ path, args }) => {
		console.log(`Calling ${path} with args:`, args);
		return [args[0] * 2, args[1] * 2]; // Return array to replace arguments
	},
	{ id: "double-args", priority: 100 }
);

// After hook: Transform result
api.slothlet.hook.on(
	"after:math.*",
	({ path, result }) => {
		console.log(`${path} returned:`, result);
		return result * 10;
	},
	{ id: "scale-result" }
);

// Always hook: Observe final result (read-only)
api.slothlet.hook.on(
	"always:**",
	({ path, result, hasError }) => {
		console.log(hasError ? `${path} failed` : `${path} succeeded`);
	},
	{ id: "logger" }
);

// Error hook: Monitor errors with source tracking
api.slothlet.hook.on(
	"error:**",
	({ path, error, source }) => {
		console.error(`Error in ${path}:`, error.message);
		console.error(`Source: ${source.type}`); // 'before' | 'after' | 'function' | 'always'
	},
	{ id: "error-monitor" }
);

// Call function - hooks execute automatically
const result = await api.math.add(2, 3);

Dynamic API Extension (v3 API)

import slothlet from "@cldmv/slothlet";

const api = await slothlet({ dir: "./api" });

// Add modules at runtime
await api.slothlet.api.add("plugins", "./plugins-folder");
api.plugins.myPlugin();

// Create nested API structures
await api.slothlet.api.add("runtime.plugins", "./more-plugins");
api.runtime.plugins.loader();

// Add with metadata for security/authorization
await api.slothlet.api.add("plugins.trusted", "./trusted-plugins", {
	trusted: true,
	permissions: ["read", "write", "admin"]
});

// Remove and reload
await api.slothlet.api.remove("oldModule");
await api.slothlet.api.reload("database.*");

📚 Configuration Options

Option Type Default Description
dir string "api" Directory to load API modules from (absolute or relative path)
mode string "eager" Loading mode - "lazy" for on-demand loading, "eager" for immediate loading
runtime string "async" Runtime binding system: "async" for AsyncLocalStorage (default), "live" for live-bindings
apiDepth number Infinity Directory traversal depth - 0 for root only, Infinity for all levels
debug boolean false Enable verbose logging (also via --slothletdebug flag or SLOTHLET_DEBUG=true env var)
context object {} Context data injected into live-binding (available via import { context } from "@cldmv/slothlet/runtime")
reference object {} Reference object merged into API root level
sanitize object {} Advanced filename-to-API transformation control with lowerFirst, preserveAllUpper, preserveAllLower, and rules (supports exact matches, glob patterns *json*, and boundary patterns **url**)
hook mixed false Enable hook system: true (enable all), "pattern" (enable with pattern), or object with enabled, pattern, suppressErrors options - note: hook singular, not hooks
backgroundMaterialize boolean false In lazy mode: start background pre-loading of all modules immediately after init; automatically enables materialization tracking and the materialized:complete lifecycle event
api.collision mixed "merge" Collision mode for API namespace conflicts: "merge", "skip", "overwrite", "throw" - or { initial: "merge", api: "skip" } to set independently for load vs runtime add()
api.mutations object all true Per-operation mutation controls: { add: true, remove: true, reload: true } - set any to false to disable
i18n object {} Internationalization settings: { language: "en" } - supported: en, es, fr, de, pt, it, ja, zh, ko

🔀 How Slothlet Works: Loading Modes Explained

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" }

    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

    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

    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

    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

    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

    linkStyle default stroke:#9BC66B,stroke-width:3px,opacity:0.5
    linkStyle 4,5,6,7,8,18,19 stroke-width:0px
Loading

🚀 Performance Modes

Eager Mode (Default - Production Ready)

Best for: Production environments, maximum runtime performance, predictable behavior

const api = await slothlet({ dir: "./api" }); // mode: "eager" by default

// Functions behave as originally defined
const result = api.math.add(2, 3); // Sync - no await needed
const asyncResult = await api.async.processData({ data: "test" }); // Async needs await

Benefits:

  • ✅ Fast function calls (9.46μs average - within 6% of lazy mode)
  • ✅ Predictable performance (no materialization delays)
  • ✅ Functions behave exactly as originally defined

Lazy Mode with Copy-Left Materialization (Production Ready)

Best for: Startup-sensitive applications, memory efficiency, loading only what you use

const api = await slothlet({ mode: "lazy", dir: "./api" });

// ALL calls must be awaited (materialization process)
const result1 = await api.math.add(2, 3); // First: ~538μs avg (materialization)
const result2 = await api.math.add(5, 7); // Subsequent: ~10μs (materialized)

Benefits:

  • ✅ 2.2x faster startup (15.41ms vs 34.28ms)
  • ✅ Equal function call performance (9.99μs vs 9.46μs eager - within 6% measurement noise)
  • ✅ Memory efficient (loads only what you use)
  • ✅ Copy-left optimization (once loaded, stays loaded)

Lazy Mode with Background Materialization (new in v3)

Best for: Lazy startup performance with eager runtime performance - pre-warm everything in the background

const api = await slothlet({
	mode: "lazy",
	dir: "./api",
	backgroundMaterialize: true
});

// Subscribe to completion
api.slothlet.lifecycle.on("materialized:complete", (data) => {
	console.log(`${data.total} modules materialized`);
});

// Or await all modules to be ready before serving traffic
await api.slothlet.materialize.wait();

// Check progress at any time
const stats = api.slothlet.materialize.get();
// { total, materialized, remaining, percentage }

Tip

Choose your strategy:

  • Startup-sensitive? → Lazy mode (2.2x faster startup)
  • Call-intensive? → Either mode (function calls essentially equal after materialization)
  • Need predictability? → Eager mode (no materialization delays)
  • Large API, use subset? → Lazy mode (memory efficient)
  • Want lazy startup + eager runtime? → Lazy mode + backgroundMaterialize: true

🎣 Hook System (v3)

Hook Configuration

// Simple enable (default pattern "**")
const api = await slothlet({ dir: "./api", hook: true });

// Enable with default pattern filter
const api = await slothlet({ dir: "./api", hook: "database.*" });

// Full configuration
const api = await slothlet({
	dir: "./api",
	hook: {
		enabled: true,
		pattern: "**",
		suppressErrors: false // true = errors suppressed (returns undefined instead of throwing)
	}
});

Hook Types

  • before - Executes before the function. Can modify arguments or short-circuit. Must be synchronous.
  • after - Executes after successful completion. Can transform the return value.
  • always - Read-only observer. Always executes (even on short-circuit). Return value ignored.
  • error - Executes only when an error occurs. Receives error with source tracking.

Basic Usage

The hook.on(typePattern, handler, options) signature uses "type:pattern" as the first argument:

// Before hook - modify arguments
api.slothlet.hook.on(
	"before:math.add",
	({ path, args }) => {
		return [args[0] * 2, args[1] * 2]; // Return array to replace arguments
		// Return non-array non-undefined to short-circuit (skip function)
		// Return undefined to continue with original args
	},
	{ id: "double-args", priority: 100 }
);

// After hook - transform result
api.slothlet.hook.on(
	"after:math.*",
	({ path, result }) => {
		return result * 10; // Return value replaces result; undefined = no change
	},
	{ id: "scale-result" }
);

// Always hook - observe (read-only)
api.slothlet.hook.on(
	"always:**",
	({ path, result, hasError, errors }) => {
		if (hasError) console.error(`${path} failed:`, errors);
		else console.log(`${path} returned:`, result);
		// Return value is ignored
	},
	{ id: "logger" }
);

// Error hook - monitor failures
api.slothlet.hook.on(
	"error:**",
	({ path, error, source }) => {
		// source.type: "before" | "after" | "always" | "function"
		console.error(`Error in ${path} (from ${source.type}):`, error.message);
	},
	{ id: "error-monitor" }
);

Hook Subsets (new in v3)

Each hook type has three ordered execution phases:

Subset Order Typical use
"before" First Auth checks, security validation
"primary" Middle (default) Main hook logic
"after" Last Audit trails, cleanup
// Auth check runs first - always
api.slothlet.hook.on(
	"before:protected.*",
	({ ctx }) => { if (!ctx.user) throw new Error("Unauthorized"); },
	{ id: "auth", subset: "before", priority: 2000 }
);

// Main validation logic - default subset
api.slothlet.hook.on(
	"before:protected.*",
	({ args }) => { /* validate */ },
	{ id: "validate" } // subset: "primary" by default
);

// Audit log always runs last
api.slothlet.hook.on(
	"after:protected.*",
	({ path, result }) => { /* log */ },
	{ id: "audit", subset: "after" }
);

Pattern Matching

Syntax Description Example
exact.path Exact match "before:math.add"
namespace.* All functions in namespace "after:math.*"
*.funcName Function name across namespaces "always:*.add"
** All functions "error:**"
{a,b} Brace expansion "before:{math,utils}.*"
!pattern Negation "before:!internal.*"

Hook Management

// Remove by ID
api.slothlet.hook.remove({ id: "my-hook" });
api.slothlet.hook.off("my-hook"); // alias

// Remove by filter
api.slothlet.hook.remove({ type: "before", pattern: "math.*" });

// Remove all
api.slothlet.hook.clear();

// List hooks
const all = api.slothlet.hook.list();
const active = api.slothlet.hook.list({ enabled: true });

// Enable / disable without unregistering
api.slothlet.hook.disable();
api.slothlet.hook.disable({ pattern: "math.*" });
api.slothlet.hook.enable();
api.slothlet.hook.enable({ type: "before" });

🔄 Per-Request Context (v3 API)

const api = await slothlet({
	dir: "./api",
	context: { appName: "MyApp", version: "3.0" }
});

// run() - execute a function inside a scoped context
await api.slothlet.context.run({ userId: "alice", role: "admin" }, async () => {
	// Inside this scope: context = { appName, version, userId, role }
	await api.database.query();
	await api.audit.log();
});

// scope() - return a new API object with merged context
const scopedApi = api.slothlet.context.scope({ userId: "bob" });
await scopedApi.database.query(); // context includes userId: "bob"

// Deep merge strategy
await api.slothlet.context.run(
	{ nested: { prop: "value" } },
	handler,
	{ mergeStrategy: "deep" }
);

Automatic EventEmitter Context Propagation

Context propagates automatically through EventEmitter callbacks:

import net from "net";
import { context } from "@cldmv/slothlet/runtime";

export const server = {
	async start() {
		const tcpServer = net.createServer((socket) => {
			console.log(`User ${context.userId} connected`);

			socket.on("data", (data) => {
				// Context preserved in all nested callbacks
				console.log(`Data from ${context.userId}: ${data}`);
			});
		});
		tcpServer.listen(3000);
	}
};

📖 See docs/CONTEXT-PROPAGATION.md


🏷️ Metadata System

Tag API paths with metadata for authorization, auditing, and security.

// Attach metadata when loading
await api.slothlet.api.add("plugins/trusted", "./trusted-dir", {
	metadata: { trusted: true, securityLevel: "high" }
});

// Set metadata at runtime
api.slothlet.metadata.set("plugins.trusted.someFunc", { version: 2 });
api.slothlet.metadata.setGlobal({ environment: "production" });
api.slothlet.metadata.setFor("plugins/trusted", { owner: "core-team" });
api.slothlet.metadata.remove("plugins.old.func");

🔒 For complete metadata documentation, see docs/METADATA.md


🔁 Hot Reload / Dynamic API Management (v3 API)

// Add new modules at runtime
await api.slothlet.api.add("newModule", "./new-module-path");
await api.slothlet.api.add("plugins", "./plugins", { collision: "merge" });

// Remove modules
await api.slothlet.api.remove("oldModule");

// Reload specific path or all modules
await api.slothlet.api.reload("database.*");
await api.slothlet.api.reload("plugins.auth");

Lazy mode reload behavior: In lazy mode, reload restores modules to an unmaterialized proxy state - existing references are intentionally not preserved. Eager mode merges new module exports into the existing live wrapper, preserving references.

Collision Modes (new in v3)

Control what happens when a loaded path already exists:

const api = await slothlet({
	dir: "./api",
	api: {
		collision: {
			initial: "merge",   // During initial load()
			api: "skip"         // During api.slothlet.api.add()
		}
	}
});
Mode Behavior
"overwrite" Replace existing (default)
"merge" Deep-merge new into existing
"skip" Keep existing, ignore new
"throw" Throw an error on conflict

Mutation Controls (new in v3)

Restrict which API operations are permitted:

const api = await slothlet({
	dir: "./api",
	api: {
		mutations: {
			add: true,
			remove: false, // Prevent removal in production
			reload: false  // Prevent reload in production
		}
	}
});

⚡ Lifecycle Events (new in v3)

Subscribe to internal module lifecycle events:

// Available events
api.slothlet.lifecycle.on("materialized:complete", (data) => {
	console.log(`${data.total} modules materialized`);
});

api.slothlet.lifecycle.on("impl:created", (data) => {
	console.log(`Module created at ${data.apiPath}`);
});

api.slothlet.lifecycle.on("impl:changed", (data) => {
	console.log(`Module at ${data.apiPath} was reloaded`);
});

api.slothlet.lifecycle.on("impl:removed", (data) => {
	console.log(`Module at ${data.apiPath} was removed`);
});

// Unsubscribe
const handler = (data) => console.log(data);
api.slothlet.lifecycle.on("impl:changed", handler);
api.slothlet.lifecycle.off("impl:changed", handler);

Available events: "materialized:complete", "impl:created", "impl:changed", "impl:removed"

Note

api.slothlet.lifecycle exposes on and off only. emit, subscribe, and unsubscribe are internal - they are not present on the public API object.


📁 File Organization Best Practices

✅ Clean Folder Structure

api/
├── config.mjs              → api.config.*
├── math/
│   └── math.mjs            → api.math.* (flattened - filename matches folder)
├── util/
│   ├── util.mjs            → api.util.* (flattened methods)
│   ├── extract.mjs         → api.util.extract.*
│   └── controller.mjs      → api.util.controller.*
├── nested/
│   └── date/
│       └── date.mjs        → api.nested.date.*
└── multi/
    ├── alpha.mjs           → api.multi.alpha.*
    └── beta.mjs            → api.multi.beta.*

✅ Naming Conventions

  • Filename matches folder → Auto-flattening (math/math.mjsapi.math.*)
  • Different filename → Nested structure preserved
  • Dash-separated names → camelCase API (auto-ip.mjsapi.autoIP)
  • Function name preferred → Original capitalization kept over sanitized form (see Rule 9)

✅ Use self for Cross-Module Access

API modules must never import each other directly. Use Slothlet's live-binding system instead:

// ❌ WRONG - breaks lazy loading and context isolation
import { math } from "./math/math.mjs";

// ✅ CORRECT - live binding always reflects current runtime state
import { self, context } from "@cldmv/slothlet/runtime";

export const myModule = {
	async processData(input) {
		const mathResult = self.math.add(2, 3); // Cross-module call via runtime
		console.log(`Caller: ${context.userId}`); // Per-request context
		return `Processed: ${input}, Math: ${mathResult}`;
	}
};

📊 Performance Analysis

For comprehensive performance benchmarks, analysis, and recommendations:

📈 See docs/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 npms.io score

npm unpacked size Repo size


📚 Documentation

Core Documentation

Technical Guides

  • TypeScript Support - Native TypeScript support: fast mode (esbuild), strict mode (tsc), and .d.ts type generation
  • Hook System - Complete hook system documentation with 4 hook types, three-phase subsets, pattern matching, and examples
  • Context Propagation - EventEmitter and class instance context preservation
  • Metadata System - Function metadata tagging and runtime introspection for security, authorization, and auditing
  • Module Structure - Comprehensive module organization patterns and examples
  • Sanitization - Property name sanitization rules
  • Internationalization - i18n support, language configuration, and available translations

API Rules & Transformation

  • API Rules - All 13 API transformation rules with verified test examples
  • API Rules Conditions - Complete technical reference of all conditional statements that control API generation
  • API Flattening - Flattening rules with decision tree and benefits

🌟 Migration from v2.x

Upgrading from v2? See the Migration Guide for all breaking changes, full before/after code examples, a complete config diff, and a list of removed options.


🛡 Error Handling

Slothlet v3 uses a rich SlothletError class with translated messages and contextual hints:

try {
	await api.slothlet.api.add("plugins", "./dir");
} catch (error) {
	console.error(error.message); // Translated error message
	console.error(error.hint);    // Contextual hint for resolution
	console.error(error.code);    // Machine-readable error code
}

🏗️ Production & Development Modes

Production Ready ✅

  • Eager Mode: Stable, battle-tested, maximum performance
  • Lazy Mode: Production-ready with copy-left optimization
  • Background Materialization: Lazy startup + eager runtime performance
  • Mixed Module Loading: ESM/CJS interoperability fully supported

Development Features 🛠️

  • Debug Mode: Comprehensive i18n-translated logging via --slothletdebug flag or SLOTHLET_DEBUG=true
  • Development Check: devcheck.mjs for environment validation
  • Source Detection: Automatic src/ vs dist/ mode detection
  • API Inspection: console.log(api.math) shows real module contents (v3+)

🤝 Contributing

We welcome contributions! Please:

  1. Review the code in src/lib/ for implementation details
  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 for detailed contribution guidelines.

Contributors Sponsor shinrai


🔗 Links


📄 License

GitHub license npm license

Apache-2.0 © Shinrai / CLDMV


🙏 Acknowledgments

To my wife and children - thank you for your patience, your encouragement, and the countless hours you gave me to build this. None of it would exist without your support.

About

No description, website, or topics provided.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Sponsor this project

 

Packages

 
 
 

Contributors