@cldmv/slothlet 2.6.1 â 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENT-USAGE.md +536 -0
- package/API-RULES-CONDITIONS.md +367 -0
- package/API-RULES.md +777 -0
- package/README.md +631 -39
- package/dist/lib/helpers/hooks.mjs +381 -0
- package/dist/lib/modes/slothlet_lazy.mjs +25 -19
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +97 -16
- package/dist/lib/runtime/runtime-livebindings.mjs +127 -5
- package/dist/slothlet.mjs +50 -2
- package/package.json +12 -8
- package/types/dist/lib/helpers/hooks.d.mts +330 -0
- package/types/dist/lib/helpers/hooks.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +4 -3
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +7 -0
- package/types/dist/slothlet.d.mts.map +1 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ The name might suggest we're taking it easy, but don't be fooled. **Slothlet del
|
|
|
40
40
|
v2.0 represents a ground-up rewrite with enterprise-grade features:
|
|
41
41
|
|
|
42
42
|
- **Universal Module Support**: Load both ESM (`.mjs`) and CommonJS (`.cjs`) files seamlessly
|
|
43
|
-
- **
|
|
43
|
+
- **Dual Runtime System**: Choose AsyncLocalStorage or live-bindings for context isolation
|
|
44
44
|
- **4.3x Faster Startup**: Lazy mode achieves 564.17Ξs vs 2.45ms in eager mode
|
|
45
45
|
- **Copy-Left Materialization**: Once loaded, modules stay materialized for optimal performance
|
|
46
46
|
- **Zero Dependencies**: Pure Node.js implementation with no external dependencies
|
|
@@ -74,6 +74,15 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
|
|
|
74
74
|
- **AsyncResource Integration**: Production-ready context management following Node.js best practices
|
|
75
75
|
- **Zero Configuration**: Works automatically with TCP servers, HTTP servers, and any EventEmitter-based patterns
|
|
76
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
|
+
|
|
77
86
|
---
|
|
78
87
|
|
|
79
88
|
## ð Key Features
|
|
@@ -113,14 +122,14 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
|
|
|
113
122
|
### ð **Advanced Binding System**
|
|
114
123
|
|
|
115
124
|
- **Live Bindings**: Dynamic context and reference binding for runtime API mutation
|
|
116
|
-
- **
|
|
125
|
+
- **Context Isolation**: Dual runtime options for per-instance context isolation with seamless integration
|
|
117
126
|
- **Copy-Left Preservation**: Materialized functions stay materialized, preserving performance gains
|
|
118
127
|
- **Bubble-Up Updates**: Parent API synchronization ensures consistency across the API tree
|
|
119
128
|
- **Mixed Module Support**: Seamlessly blend ESM and CommonJS modules in the same API
|
|
120
129
|
|
|
121
130
|
### ð **Developer Experience**
|
|
122
131
|
|
|
123
|
-
- **
|
|
132
|
+
- **Enhanced Error Handling**: Clear JavaScript errors with module suggestions and descriptive errors (planned for v3.0.0)
|
|
124
133
|
- **TypeScript-Friendly**: Comprehensive JSDoc annotations for excellent editor support with auto-generated declarations
|
|
125
134
|
- **Configurable Debug**: Detailed logging for development and troubleshooting via CLI flags or environment variables
|
|
126
135
|
- **Multiple Instances**: Parameter-based isolation for complex applications with instance ID management
|
|
@@ -132,7 +141,7 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
|
|
|
132
141
|
- **Universal Loading**: CommonJS and ESM files work together seamlessly
|
|
133
142
|
- **Zero Dependencies**: Lightweight footprint with no external dependencies
|
|
134
143
|
- **Cross-Platform**: Works seamlessly across all Node.js environments
|
|
135
|
-
- **Extensible**: Modular architecture
|
|
144
|
+
- **Extensible**: Modular architecture with flexible API composition patterns
|
|
136
145
|
|
|
137
146
|
---
|
|
138
147
|
|
|
@@ -140,11 +149,16 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
|
|
|
140
149
|
|
|
141
150
|
### Requirements
|
|
142
151
|
|
|
143
|
-
- **Node.js
|
|
144
|
-
- **
|
|
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"`)
|
|
145
154
|
|
|
146
155
|
> [!IMPORTANT]
|
|
147
|
-
> **v2.x
|
|
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.
|
|
148
162
|
|
|
149
163
|
### Install
|
|
150
164
|
|
|
@@ -194,6 +208,28 @@ const result = api.math.multiply(4, 5); // 20
|
|
|
194
208
|
const mixedResult = await api.interop.processData({ data: "test" }); // CJS+ESM interop
|
|
195
209
|
```
|
|
196
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
|
+
|
|
197
233
|
### Lazy Loading Mode
|
|
198
234
|
|
|
199
235
|
```javascript
|
|
@@ -260,7 +296,7 @@ const api = await slothlet({
|
|
|
260
296
|
|
|
261
297
|
### Sanitize Options Examples
|
|
262
298
|
|
|
263
|
-
Transform module filenames into clean, professional API property names:
|
|
299
|
+
Transform module filenames into clean, professional API property names with sophisticated control:
|
|
264
300
|
|
|
265
301
|
```javascript
|
|
266
302
|
// Without sanitize options (default behavior)
|
|
@@ -272,23 +308,96 @@ const api = await slothlet({ dir: "./api" });
|
|
|
272
308
|
const api = await slothlet({
|
|
273
309
|
dir: "./api",
|
|
274
310
|
sanitize: {
|
|
275
|
-
lowerFirst: false,
|
|
311
|
+
lowerFirst: false, // Keep first character casing
|
|
312
|
+
preserveAllUpper: true, // Preserve identifiers like "COMMON_APPS"
|
|
313
|
+
preserveAllLower: false, // Transform identifiers like "common_apps"
|
|
276
314
|
rules: {
|
|
277
|
-
leave: ["parseJSON"], // Exact match preservation
|
|
278
|
-
|
|
279
|
-
|
|
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
|
|
280
319
|
}
|
|
281
320
|
}
|
|
282
321
|
});
|
|
283
322
|
// Result: api.buildURLWithParams, api.parseJSON, api.autoIP
|
|
284
323
|
```
|
|
285
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
|
+
|
|
286
362
|
**Sanitize Pattern Types:**
|
|
287
363
|
|
|
288
364
|
- **Exact Match**: `"parseJSON"` - Matches exact string only
|
|
289
|
-
- **Glob Patterns**:
|
|
290
|
-
-
|
|
291
|
-
-
|
|
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
|
+
```
|
|
292
401
|
|
|
293
402
|
### Multiple Instances
|
|
294
403
|
|
|
@@ -323,7 +432,7 @@ console.log(api2.context.tenant); // "bob"
|
|
|
323
432
|
```
|
|
324
433
|
|
|
325
434
|
> [!NOTE]
|
|
326
|
-
> **v2.x Simplification**: Unlike v1.x which required query string parameters or `withInstanceId()` methods, v2.x automatically creates isolated instances with each `slothlet()` call,
|
|
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.
|
|
327
436
|
|
|
328
437
|
---
|
|
329
438
|
|
|
@@ -345,20 +454,21 @@ Creates and loads an API instance with the specified configuration.
|
|
|
345
454
|
|
|
346
455
|
**Options:**
|
|
347
456
|
|
|
348
|
-
| Option | Type | Default | Description
|
|
349
|
-
| ----------- | --------- | ------------- |
|
|
350
|
-
| `dir` | `string` | `"api"` | Directory to load API modules from. Can be absolute or relative path. If relative, resolved from process.cwd().
|
|
351
|
-
| `lazy` | `boolean` | `false` | **Legacy** loading strategy - `true` for lazy loading (on-demand), `false` for eager loading (immediate). Use `mode` option instead.
|
|
352
|
-
| `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.
|
|
353
|
-
| `engine` | `string` | `"singleton"` | **New** execution environment mode - `"singleton"`, `"vm"`, `"worker"`, or `"fork"`
|
|
354
|
-
| `
|
|
355
|
-
| `
|
|
356
|
-
| `
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
|
|
361
|
-
|
|
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
|
|
362
472
|
|
|
363
473
|
The option structure has been improved for better clarity:
|
|
364
474
|
|
|
@@ -611,6 +721,488 @@ console.log("Processing completed with context preservation");
|
|
|
611
721
|
> [!TIP]
|
|
612
722
|
> **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.
|
|
613
723
|
|
|
724
|
+
### Hook System
|
|
725
|
+
|
|
726
|
+
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).
|
|
727
|
+
|
|
728
|
+
#### Hook Configuration
|
|
729
|
+
|
|
730
|
+
Hooks can be configured when creating a slothlet instance:
|
|
731
|
+
|
|
732
|
+
```javascript
|
|
733
|
+
// Enable hooks (simple boolean)
|
|
734
|
+
const api = await slothlet({
|
|
735
|
+
dir: "./api",
|
|
736
|
+
hooks: true // Enable all hooks with default pattern "**"
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// Enable with custom pattern
|
|
740
|
+
const api = await slothlet({
|
|
741
|
+
dir: "./api",
|
|
742
|
+
hooks: "database.*" // Only enable for database functions
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
// Full configuration object
|
|
746
|
+
const api = await slothlet({
|
|
747
|
+
dir: "./api",
|
|
748
|
+
hooks: {
|
|
749
|
+
enabled: true,
|
|
750
|
+
pattern: "**", // Default pattern for filtering
|
|
751
|
+
suppressErrors: false // Control error throwing behavior
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
```
|
|
755
|
+
|
|
756
|
+
**Configuration Options:**
|
|
757
|
+
|
|
758
|
+
- **`enabled`** (boolean): Enable or disable hook execution
|
|
759
|
+
- **`pattern`** (string): Default pattern for filtering which functions hooks apply to
|
|
760
|
+
- **`suppressErrors`** (boolean): Control error throwing behavior
|
|
761
|
+
- `false` (default): Errors are sent to error hooks, THEN thrown (normal behavior)
|
|
762
|
+
- `true`: Errors are sent to error hooks, BUT NOT thrown (returns `undefined`)
|
|
763
|
+
|
|
764
|
+
**Error Suppression Behavior:**
|
|
765
|
+
|
|
766
|
+
Error hooks **ALWAYS receive errors** regardless of this setting. The `suppressErrors` option only controls whether errors are thrown after error hooks execute.
|
|
767
|
+
|
|
768
|
+
> [!IMPORTANT]
|
|
769
|
+
> **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.
|
|
770
|
+
|
|
771
|
+
When `suppressErrors: true`, errors are caught and sent to error hooks, but not thrown:
|
|
772
|
+
|
|
773
|
+
```javascript
|
|
774
|
+
const api = await slothlet({
|
|
775
|
+
dir: "./api",
|
|
776
|
+
hooks: {
|
|
777
|
+
enabled: true,
|
|
778
|
+
pattern: "**",
|
|
779
|
+
suppressErrors: true // Suppress all errors
|
|
780
|
+
}
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
// Register error hook to monitor failures
|
|
784
|
+
api.hooks.on(
|
|
785
|
+
"error-monitor",
|
|
786
|
+
"error",
|
|
787
|
+
({ path, error, source }) => {
|
|
788
|
+
console.error(`Error in ${path}:`, error.message);
|
|
789
|
+
// Log to monitoring service without crashing
|
|
790
|
+
},
|
|
791
|
+
{ pattern: "**" }
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
// Function errors won't crash the application
|
|
795
|
+
const result = await api.riskyOperation();
|
|
796
|
+
if (result === undefined) {
|
|
797
|
+
// Function failed but didn't throw
|
|
798
|
+
console.log("Operation failed gracefully");
|
|
799
|
+
}
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
**Error Flow:**
|
|
803
|
+
|
|
804
|
+
1. Error occurs (in before hook, function, or after hook)
|
|
805
|
+
2. Error hooks execute and receive the error
|
|
806
|
+
3. **If `suppressErrors: false`** â Error is thrown (crashes if uncaught)
|
|
807
|
+
4. **If `suppressErrors: true`** â Error is NOT thrown, function returns `undefined`
|
|
808
|
+
|
|
809
|
+
**What Gets Suppressed (when `suppressErrors: true`):**
|
|
810
|
+
|
|
811
|
+
- â
Before hook errors â Sent to error hooks, NOT thrown
|
|
812
|
+
- â
Function execution errors â Sent to error hooks, NOT thrown
|
|
813
|
+
- â
After hook errors â Sent to error hooks, NOT thrown
|
|
814
|
+
- â
Always hook errors â Sent to error hooks, never thrown (regardless of setting)
|
|
815
|
+
|
|
816
|
+
> [!TIP]
|
|
817
|
+
> **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.
|
|
818
|
+
|
|
819
|
+
> [!CAUTION]
|
|
820
|
+
> **Critical Operations**: For validation or authorization hooks where errors MUST stop execution, use `suppressErrors: false` (default) to ensure errors propagate normally.
|
|
821
|
+
|
|
822
|
+
#### Hook Types
|
|
823
|
+
|
|
824
|
+
**Four hook types with distinct responsibilities:**
|
|
825
|
+
|
|
826
|
+
- **`before`**: Intercept before function execution
|
|
827
|
+
- Modify arguments passed to functions
|
|
828
|
+
- Cancel execution and return custom values (short-circuit)
|
|
829
|
+
- Execute validation or logging before function runs
|
|
830
|
+
- **`after`**: Transform results after successful execution
|
|
831
|
+
- Transform function return values
|
|
832
|
+
- Only runs if function executes (skipped on short-circuit)
|
|
833
|
+
- Chain multiple transformations in priority order
|
|
834
|
+
- **`always`**: Observe final result with full execution context
|
|
835
|
+
- Always executes after function completes
|
|
836
|
+
- Runs even when `before` hooks cancel execution or errors occur
|
|
837
|
+
- Receives complete context: `{ path, result, hasError, errors }`
|
|
838
|
+
- Cannot modify result (read-only observation)
|
|
839
|
+
- Perfect for unified logging of both success and error scenarios
|
|
840
|
+
- **`error`**: Monitor and handle errors
|
|
841
|
+
- Receives detailed error context with source tracking
|
|
842
|
+
- Error source types: 'before', 'function', 'after', 'always', 'unknown'
|
|
843
|
+
- Includes error type, hook ID, hook tag, timestamp, and stack trace
|
|
844
|
+
- Perfect for error monitoring, logging, and alerting
|
|
845
|
+
|
|
846
|
+
#### Basic Usage
|
|
847
|
+
|
|
848
|
+
```javascript
|
|
849
|
+
import slothlet from "@cldmv/slothlet";
|
|
850
|
+
|
|
851
|
+
const api = await slothlet({
|
|
852
|
+
dir: "./api",
|
|
853
|
+
hooks: true // Enable hooks
|
|
854
|
+
});
|
|
855
|
+
|
|
856
|
+
// Before hook: Modify arguments
|
|
857
|
+
api.hooks.on(
|
|
858
|
+
"validate-input",
|
|
859
|
+
"before",
|
|
860
|
+
({ path, args }) => {
|
|
861
|
+
console.log(`Calling ${path} with args:`, args);
|
|
862
|
+
// Return modified args or original
|
|
863
|
+
return [args[0] * 2, args[1] * 2];
|
|
864
|
+
},
|
|
865
|
+
{ pattern: "math.add", priority: 100 }
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
// After hook: Transform result
|
|
869
|
+
api.hooks.on(
|
|
870
|
+
"format-output",
|
|
871
|
+
"after",
|
|
872
|
+
({ path, result }) => {
|
|
873
|
+
console.log(`${path} returned:`, result);
|
|
874
|
+
// Return transformed result
|
|
875
|
+
return result * 10;
|
|
876
|
+
},
|
|
877
|
+
{ pattern: "math.*", priority: 100 }
|
|
878
|
+
);
|
|
879
|
+
|
|
880
|
+
// Always hook: Observe final result with error context
|
|
881
|
+
api.hooks.on(
|
|
882
|
+
"log-execution",
|
|
883
|
+
"always",
|
|
884
|
+
({ path, result, hasError, errors }) => {
|
|
885
|
+
if (hasError) {
|
|
886
|
+
console.log(`${path} failed with ${errors.length} error(s):`, errors);
|
|
887
|
+
} else {
|
|
888
|
+
console.log(`${path} succeeded with result:`, result);
|
|
889
|
+
}
|
|
890
|
+
// Return value ignored - read-only observer
|
|
891
|
+
},
|
|
892
|
+
{ pattern: "**" } // All functions
|
|
893
|
+
);
|
|
894
|
+
|
|
895
|
+
// Call function - hooks execute automatically
|
|
896
|
+
const result = await api.math.add(2, 3);
|
|
897
|
+
// Logs: "Calling math.add with args: [2, 3]"
|
|
898
|
+
// Logs: "math.add returned: 10" (4+6)
|
|
899
|
+
// Logs: "Final result for math.add: 100" (10*10)
|
|
900
|
+
// result === 100
|
|
901
|
+
```
|
|
902
|
+
|
|
903
|
+
#### Short-Circuit Execution
|
|
904
|
+
|
|
905
|
+
`before` hooks can cancel function execution and return custom values:
|
|
906
|
+
|
|
907
|
+
```javascript
|
|
908
|
+
// Caching hook example
|
|
909
|
+
const cache = new Map();
|
|
910
|
+
|
|
911
|
+
api.hooks.on(
|
|
912
|
+
"cache-check",
|
|
913
|
+
"before",
|
|
914
|
+
({ path, args }) => {
|
|
915
|
+
const key = JSON.stringify({ path, args });
|
|
916
|
+
if (cache.has(key)) {
|
|
917
|
+
console.log(`Cache hit for ${path}`);
|
|
918
|
+
return cache.get(key); // Short-circuit: return cached value
|
|
919
|
+
}
|
|
920
|
+
// Return undefined to continue to function
|
|
921
|
+
},
|
|
922
|
+
{ pattern: "**", priority: 1000 } // High priority
|
|
923
|
+
);
|
|
924
|
+
|
|
925
|
+
api.hooks.on(
|
|
926
|
+
"cache-store",
|
|
927
|
+
"after",
|
|
928
|
+
({ path, args, result }) => {
|
|
929
|
+
const key = JSON.stringify({ path, args });
|
|
930
|
+
cache.set(key, result);
|
|
931
|
+
return result; // Pass through
|
|
932
|
+
},
|
|
933
|
+
{ pattern: "**", priority: 100 }
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
// First call - executes function and caches
|
|
937
|
+
await api.math.add(2, 3); // Computes and stores
|
|
938
|
+
|
|
939
|
+
// Second call - returns cached value (function not executed)
|
|
940
|
+
await api.math.add(2, 3); // Cache hit! No computation
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
#### Pattern Matching
|
|
944
|
+
|
|
945
|
+
Hooks support flexible pattern matching:
|
|
946
|
+
|
|
947
|
+
```javascript
|
|
948
|
+
// Exact match
|
|
949
|
+
api.hooks.on("hook1", "before", handler, { pattern: "math.add" });
|
|
950
|
+
|
|
951
|
+
// Wildcard: all functions in namespace
|
|
952
|
+
api.hooks.on("hook2", "before", handler, { pattern: "math.*" });
|
|
953
|
+
|
|
954
|
+
// Wildcard: specific function in all namespaces
|
|
955
|
+
api.hooks.on("hook3", "before", handler, { pattern: "*.add" });
|
|
956
|
+
|
|
957
|
+
// Global: all functions
|
|
958
|
+
api.hooks.on("hook4", "before", handler, { pattern: "**" });
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
#### Priority and Chaining
|
|
962
|
+
|
|
963
|
+
Multiple hooks execute in priority order (highest first):
|
|
964
|
+
|
|
965
|
+
```javascript
|
|
966
|
+
// High priority - runs first
|
|
967
|
+
api.hooks.on(
|
|
968
|
+
"validate",
|
|
969
|
+
"before",
|
|
970
|
+
({ args }) => {
|
|
971
|
+
if (args[0] < 0) throw new Error("Negative numbers not allowed");
|
|
972
|
+
return args;
|
|
973
|
+
},
|
|
974
|
+
{ pattern: "math.*", priority: 1000 }
|
|
975
|
+
);
|
|
976
|
+
|
|
977
|
+
// Medium priority - runs second
|
|
978
|
+
api.hooks.on("double", "before", ({ args }) => [args[0] * 2, args[1] * 2], { pattern: "math.*", priority: 500 });
|
|
979
|
+
|
|
980
|
+
// Low priority - runs last
|
|
981
|
+
api.hooks.on(
|
|
982
|
+
"log",
|
|
983
|
+
"before",
|
|
984
|
+
({ path, args }) => {
|
|
985
|
+
console.log(`Final args for ${path}:`, args);
|
|
986
|
+
return args;
|
|
987
|
+
},
|
|
988
|
+
{ pattern: "math.*", priority: 100 }
|
|
989
|
+
);
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
#### Runtime Control
|
|
993
|
+
|
|
994
|
+
Enable and disable hooks at runtime:
|
|
995
|
+
|
|
996
|
+
```javascript
|
|
997
|
+
const api = await slothlet({ dir: "./api", hooks: true });
|
|
998
|
+
|
|
999
|
+
// Add hooks
|
|
1000
|
+
api.hooks.on("test", "before", handler, { pattern: "math.*" });
|
|
1001
|
+
|
|
1002
|
+
// Disable all hooks
|
|
1003
|
+
api.hooks.disable();
|
|
1004
|
+
await api.math.add(2, 3); // No hooks execute
|
|
1005
|
+
|
|
1006
|
+
// Re-enable all hooks
|
|
1007
|
+
api.hooks.enable();
|
|
1008
|
+
await api.math.add(2, 3); // Hooks execute
|
|
1009
|
+
|
|
1010
|
+
// Enable specific pattern only
|
|
1011
|
+
api.hooks.disable();
|
|
1012
|
+
api.hooks.enable("math.*"); // Only math.* pattern enabled
|
|
1013
|
+
await api.math.add(2, 3); // math.* hooks execute
|
|
1014
|
+
await api.other.func(); // No hooks execute
|
|
1015
|
+
```
|
|
1016
|
+
|
|
1017
|
+
#### Hook Management
|
|
1018
|
+
|
|
1019
|
+
```javascript
|
|
1020
|
+
// List registered hooks
|
|
1021
|
+
const beforeHooks = api.hooks.list("before");
|
|
1022
|
+
const afterHooks = api.hooks.list("after");
|
|
1023
|
+
const allHooks = api.hooks.list(); // All types
|
|
1024
|
+
|
|
1025
|
+
// Remove specific hook by ID
|
|
1026
|
+
const id = api.hooks.on("temp", "before", handler, { pattern: "math.*" });
|
|
1027
|
+
api.hooks.off(id);
|
|
1028
|
+
|
|
1029
|
+
// Remove all hooks matching pattern
|
|
1030
|
+
api.hooks.off("math.*");
|
|
1031
|
+
|
|
1032
|
+
// Clear all hooks of a type
|
|
1033
|
+
api.hooks.clear("before"); // Remove all before hooks
|
|
1034
|
+
api.hooks.clear(); // Remove all hooks
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
#### Error Handling
|
|
1038
|
+
|
|
1039
|
+
Hooks have a special `error` type for observing function errors with detailed source tracking:
|
|
1040
|
+
|
|
1041
|
+
```javascript
|
|
1042
|
+
api.hooks.on(
|
|
1043
|
+
"error-logger",
|
|
1044
|
+
"error",
|
|
1045
|
+
({ path, error, source }) => {
|
|
1046
|
+
console.error(`Error in ${path}:`, error.message);
|
|
1047
|
+
console.error(`Source: ${source.type}`); // 'before', 'after', 'always', 'function', 'unknown'
|
|
1048
|
+
|
|
1049
|
+
if (source.type === "function") {
|
|
1050
|
+
console.error("Error occurred in function execution");
|
|
1051
|
+
} else if (["before", "after", "always"].includes(source.type)) {
|
|
1052
|
+
console.error(`Error occurred in ${source.type} hook:`);
|
|
1053
|
+
console.error(` Hook ID: ${source.hookId}`);
|
|
1054
|
+
console.error(` Hook Tag: ${source.hookTag}`);
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
console.error(`Timestamp: ${source.timestamp}`);
|
|
1058
|
+
console.error(`Stack trace:\n${source.stack}`);
|
|
1059
|
+
|
|
1060
|
+
// Log to monitoring service with full context
|
|
1061
|
+
// Error is re-thrown after all error hooks execute
|
|
1062
|
+
},
|
|
1063
|
+
{ pattern: "**" }
|
|
1064
|
+
);
|
|
1065
|
+
|
|
1066
|
+
try {
|
|
1067
|
+
await api.validateData({ invalid: true });
|
|
1068
|
+
} catch (error) {
|
|
1069
|
+
// Error hooks executed before this catch block
|
|
1070
|
+
console.log("Caught error:", error);
|
|
1071
|
+
}
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
##### Error Source Tracking
|
|
1075
|
+
|
|
1076
|
+
Error hooks receive detailed context about where errors originated:
|
|
1077
|
+
|
|
1078
|
+
**Source Types:**
|
|
1079
|
+
|
|
1080
|
+
- `"function"`: Error occurred during function execution
|
|
1081
|
+
- `"before"`: Error occurred in a before hook
|
|
1082
|
+
- `"after"`: Error occurred in an after hook
|
|
1083
|
+
- `"always"`: Error occurred in an always hook
|
|
1084
|
+
- `"unknown"`: Error source could not be determined
|
|
1085
|
+
|
|
1086
|
+
**Source Metadata:**
|
|
1087
|
+
|
|
1088
|
+
- `source.type`: Error source type (see above)
|
|
1089
|
+
- `source.hookId`: Hook identifier (for hook errors)
|
|
1090
|
+
- `source.hookTag`: Hook tag/name (for hook errors)
|
|
1091
|
+
- `source.timestamp`: ISO timestamp when error occurred
|
|
1092
|
+
- `source.stack`: Full stack trace
|
|
1093
|
+
|
|
1094
|
+
**Example: Comprehensive Error Monitoring**
|
|
1095
|
+
|
|
1096
|
+
```javascript
|
|
1097
|
+
const errorStats = {
|
|
1098
|
+
function: 0,
|
|
1099
|
+
before: 0,
|
|
1100
|
+
after: 0,
|
|
1101
|
+
always: 0,
|
|
1102
|
+
byHook: {}
|
|
1103
|
+
};
|
|
1104
|
+
|
|
1105
|
+
api.hooks.on(
|
|
1106
|
+
"error-analytics",
|
|
1107
|
+
"error",
|
|
1108
|
+
({ path, error, source }) => {
|
|
1109
|
+
// Track error source statistics
|
|
1110
|
+
errorStats[source.type]++;
|
|
1111
|
+
|
|
1112
|
+
if (source.hookId) {
|
|
1113
|
+
if (!errorStats.byHook[source.hookTag]) {
|
|
1114
|
+
errorStats.byHook[source.hookTag] = 0;
|
|
1115
|
+
}
|
|
1116
|
+
errorStats.byHook[source.hookTag]++;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
// Log detailed error info
|
|
1120
|
+
console.error(`[${source.timestamp}] Error in ${path}:`);
|
|
1121
|
+
console.error(` Type: ${source.type}`);
|
|
1122
|
+
console.error(` Message: ${error.message}`);
|
|
1123
|
+
|
|
1124
|
+
if (source.type === "function") {
|
|
1125
|
+
// Function-level error - might be a bug in implementation
|
|
1126
|
+
console.error(" Action: Review function implementation");
|
|
1127
|
+
} else {
|
|
1128
|
+
// Hook-level error - might be a bug in hook logic
|
|
1129
|
+
console.error(` Action: Review ${source.hookTag} hook (${source.type})`);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Send to monitoring service
|
|
1133
|
+
sendToMonitoring({
|
|
1134
|
+
timestamp: source.timestamp,
|
|
1135
|
+
path,
|
|
1136
|
+
errorType: source.type,
|
|
1137
|
+
hookId: source.hookId,
|
|
1138
|
+
hookTag: source.hookTag,
|
|
1139
|
+
message: error.message,
|
|
1140
|
+
stack: source.stack
|
|
1141
|
+
});
|
|
1142
|
+
},
|
|
1143
|
+
{ pattern: "**" }
|
|
1144
|
+
);
|
|
1145
|
+
|
|
1146
|
+
// Later: Analyze error patterns
|
|
1147
|
+
console.log("Error Statistics:", errorStats);
|
|
1148
|
+
// {
|
|
1149
|
+
// function: 5,
|
|
1150
|
+
// before: 2,
|
|
1151
|
+
// after: 1,
|
|
1152
|
+
// always: 0,
|
|
1153
|
+
// byHook: {
|
|
1154
|
+
// "validate-input": 2,
|
|
1155
|
+
// "format-output": 1
|
|
1156
|
+
// }
|
|
1157
|
+
// }
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
**Important Notes:**
|
|
1161
|
+
|
|
1162
|
+
- Errors from `before` and `after` hooks are re-thrown after error hooks execute
|
|
1163
|
+
- Errors from `always` hooks are caught and logged but do NOT crash execution
|
|
1164
|
+
- Error hooks themselves do not receive errors from other error hooks (no recursion)
|
|
1165
|
+
- The `_hookSourceReported` flag prevents double-reporting of errors
|
|
1166
|
+
|
|
1167
|
+
#### Cross-Mode Compatibility
|
|
1168
|
+
|
|
1169
|
+
Hooks work identically across all configurations:
|
|
1170
|
+
|
|
1171
|
+
```javascript
|
|
1172
|
+
// Eager + AsyncLocalStorage
|
|
1173
|
+
const api1 = await slothlet({ dir: "./api", lazy: false, runtime: "async", hooks: true });
|
|
1174
|
+
|
|
1175
|
+
// Eager + Live Bindings
|
|
1176
|
+
const api2 = await slothlet({ dir: "./api", lazy: false, runtime: "live", hooks: true });
|
|
1177
|
+
|
|
1178
|
+
// Lazy + AsyncLocalStorage
|
|
1179
|
+
const api3 = await slothlet({ dir: "./api", lazy: true, runtime: "async", hooks: true });
|
|
1180
|
+
|
|
1181
|
+
// Lazy + Live Bindings
|
|
1182
|
+
const api4 = await slothlet({ dir: "./api", lazy: true, runtime: "live", hooks: true });
|
|
1183
|
+
|
|
1184
|
+
// Same hook code works with all configurations
|
|
1185
|
+
[api1, api2, api3, api4].forEach((api) => {
|
|
1186
|
+
api.hooks.on(
|
|
1187
|
+
"universal",
|
|
1188
|
+
"before",
|
|
1189
|
+
({ args }) => {
|
|
1190
|
+
return [args[0] * 10, args[1] * 10];
|
|
1191
|
+
},
|
|
1192
|
+
{ pattern: "math.add" }
|
|
1193
|
+
);
|
|
1194
|
+
});
|
|
1195
|
+
```
|
|
1196
|
+
|
|
1197
|
+
**Key Benefits:**
|
|
1198
|
+
|
|
1199
|
+
- â
**Universal**: Works across all 4 mode/runtime combinations
|
|
1200
|
+
- â
**Flexible**: Pattern matching with wildcards and priorities
|
|
1201
|
+
- â
**Powerful**: Modify args, transform results, observe execution
|
|
1202
|
+
- â
**Composable**: Chain multiple hooks with priority control
|
|
1203
|
+
- â
**Dynamic**: Enable/disable at runtime globally or by pattern
|
|
1204
|
+
- â
**Observable**: Separate hook types for different responsibilities
|
|
1205
|
+
|
|
614
1206
|
### API Mode Configuration
|
|
615
1207
|
|
|
616
1208
|
The `api_mode` option controls how slothlet handles root-level default function exports:
|
|
@@ -839,16 +1431,16 @@ flowchart TD
|
|
|
839
1431
|
HASDEFAULT -->|Yes| NAMESPACE
|
|
840
1432
|
|
|
841
1433
|
HASDEFAULT -->|No| NAMEDONLY{Only Named Exports?}
|
|
842
|
-
NAMEDONLY -->|Yes| FLATTEN[Flatten All Named Exports<br/>api.connect
|
|
1434
|
+
NAMEDONLY -->|Yes| FLATTEN["Flatten All Named Exports<br/>api.connect, api.disconnect"]
|
|
843
1435
|
|
|
844
1436
|
NAMEDONLY -->|No| SINGLENAMED{Single Named Export<br/>Matching Filename?}
|
|
845
1437
|
SINGLENAMED -->|Yes| FLATTENSINGLE[Flatten Single Export<br/>api.math]
|
|
846
1438
|
SINGLENAMED -->|No| NAMESPACE
|
|
847
1439
|
|
|
848
|
-
style FLATTEN fill:#e1f5fe
|
|
849
|
-
style FLATTENSINGLE fill:#e8f5e8
|
|
850
|
-
style NAMESPACE fill:#fff3e0
|
|
851
|
-
style PRESERVE fill:#fce4ec
|
|
1440
|
+
style FLATTEN fill:#e1f5fe,stroke:#0277bd,stroke-width:2px,color:#000
|
|
1441
|
+
style FLATTENSINGLE fill:#e8f5e8,stroke:#2e7d32,stroke-width:2px,color:#000
|
|
1442
|
+
style NAMESPACE fill:#fff3e0,stroke:#e65100,stroke-width:2px,color:#000
|
|
1443
|
+
style PRESERVE fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#000
|
|
852
1444
|
```
|
|
853
1445
|
|
|
854
1446
|
### ð Benefits of Smart Flattening
|
|
@@ -1049,7 +1641,7 @@ const syncResult = await api.string.format("Hello"); // Originally sync, but nee
|
|
|
1049
1641
|
## ðĄ Error Handling
|
|
1050
1642
|
|
|
1051
1643
|
> [!NOTE]
|
|
1052
|
-
> **Current Error Behavior**: Slothlet currently uses standard JavaScript error handling. Enhanced error handling with module suggestions is planned for
|
|
1644
|
+
> **Current Error Behavior**: Slothlet currently uses standard JavaScript error handling. Enhanced error handling with module suggestions is planned for v3.0.0 but not yet implemented.
|
|
1053
1645
|
|
|
1054
1646
|
**Current behavior:**
|
|
1055
1647
|
|
|
@@ -1062,7 +1654,7 @@ try {
|
|
|
1062
1654
|
}
|
|
1063
1655
|
```
|
|
1064
1656
|
|
|
1065
|
-
**Planned Enhanced Error Features (
|
|
1657
|
+
**Planned Enhanced Error Features (v3.0.0):**
|
|
1066
1658
|
|
|
1067
1659
|
> [!TIP]
|
|
1068
1660
|
> **Coming Soon**: Enhanced error handling with descriptive messages and module suggestions:
|
|
@@ -1123,7 +1715,7 @@ try {
|
|
|
1123
1715
|
2. **Configuration**: New options (`api_mode`, `context`, `reference`)
|
|
1124
1716
|
3. **Function names**: Enhanced preservation of original capitalization
|
|
1125
1717
|
4. **Module structure**: Mixed ESM/CJS support
|
|
1126
|
-
5. **Live bindings**:
|
|
1718
|
+
5. **Live bindings**: Dual runtime system with AsyncLocalStorage and live-bindings options
|
|
1127
1719
|
|
|
1128
1720
|
### Migration Steps
|
|
1129
1721
|
|
|
@@ -1234,7 +1826,7 @@ Apache-2.0 ÂĐ Shinrai / CLDMV
|
|
|
1234
1826
|
|
|
1235
1827
|
Slothlet v2.0 represents a complete architectural rewrite with enterprise-grade features and performance. Special thanks to all contributors who made this comprehensive enhancement possible.
|
|
1236
1828
|
|
|
1237
|
-
**ð Welcome to the future of module loading with Slothlet
|
|
1829
|
+
**ð Welcome to the future of module loading with Slothlet!**
|
|
1238
1830
|
|
|
1239
1831
|
> _Where sophisticated architecture meets blazing performance - slothlet is anything but slow._
|
|
1240
1832
|
|