@cldmv/slothlet 2.6.3 → 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 +491 -0
- 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 +4 -1
- 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.map +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
@@ -712,6 +721,488 @@ console.log("Processing completed with context preservation");
|
|
|
712
721
|
> [!TIP]
|
|
713
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.
|
|
714
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
|
+
|
|
715
1206
|
### API Mode Configuration
|
|
716
1207
|
|
|
717
1208
|
The `api_mode` option controls how slothlet handles root-level default function exports:
|