@cldmv/slothlet 2.7.0 → 2.8.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/README.md +114 -1
- package/dist/lib/helpers/als-eventemitter.mjs +137 -0
- package/dist/lib/helpers/hooks.mjs +8 -0
- package/dist/slothlet.mjs +275 -6
- package/index.cjs +2 -1
- package/index.mjs +2 -1
- package/package.json +3 -3
- package/types/dist/lib/helpers/als-eventemitter.d.mts +33 -0
- package/types/dist/lib/helpers/als-eventemitter.d.mts.map +1 -1
- package/types/dist/lib/helpers/hooks.d.mts +12 -0
- package/types/dist/lib/helpers/hooks.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +23 -2
- package/types/dist/slothlet.d.mts.map +1 -1
package/README.md
CHANGED
|
@@ -464,6 +464,7 @@ Creates and loads an API instance with the specified configuration.
|
|
|
464
464
|
| `apiDepth` | `number` | `Infinity` | Directory traversal depth control - `0` for root only, `Infinity` for all levels |
|
|
465
465
|
| `debug` | `boolean` | `false` | Enable verbose logging. Can also be set via `--slothletdebug` command line flag or `SLOTHLET_DEBUG=true` environment variable |
|
|
466
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
|
+
| `allowApiOverwrite` | `boolean` | `true` | Controls whether `addApi()` can overwrite existing API endpoints:<br/>• `true`: Allow overwrites (default, backwards compatible)<br/>• `false`: Prevent overwrites - logs warning and skips when attempting to overwrite existing endpoints<br/>Applies to both function and object overwrites at the final key of the API path |
|
|
467
468
|
| `context` | `object` | `{}` | Context data object injected into live-binding `context` reference. Available to all loaded modules via `import { context } from "@cldmv/slothlet/runtime"` |
|
|
468
469
|
| `reference` | `object` | `{}` | Reference object merged into the API root level. Properties not conflicting with loaded modules are added directly to the API |
|
|
469
470
|
| `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) |
|
|
@@ -522,10 +523,121 @@ Returns true if the API is loaded.
|
|
|
522
523
|
|
|
523
524
|
#### `slothlet.shutdown()` ⇒ `Promise<void>`
|
|
524
525
|
|
|
525
|
-
Gracefully shuts down the API and
|
|
526
|
+
Gracefully shuts down the API and performs comprehensive resource cleanup to prevent hanging processes.
|
|
527
|
+
|
|
528
|
+
**Cleanup includes:**
|
|
529
|
+
|
|
530
|
+
- Hook manager state and registered hooks
|
|
531
|
+
- AsyncLocalStorage context and bindings
|
|
532
|
+
- EventEmitter listeners and AsyncResource instances (including third-party libraries)
|
|
533
|
+
- Instance data and runtime coordination
|
|
526
534
|
|
|
527
535
|
**Returns:** `Promise<void>` - Resolves when shutdown is complete
|
|
528
536
|
|
|
537
|
+
> [!IMPORTANT]
|
|
538
|
+
> **🛡️ Process Cleanup**: The shutdown method now performs comprehensive cleanup of all EventEmitter listeners created after slothlet loads, including those from third-party libraries like pg-pool. This prevents hanging AsyncResource instances that could prevent your Node.js process from exiting cleanly.
|
|
539
|
+
|
|
540
|
+
#### `api.addApi(apiPath, folderPath)` ⇒ `Promise<void>` ⭐ NEW
|
|
541
|
+
|
|
542
|
+
Dynamically extend your API at runtime by loading additional modules and merging them into a specified path.
|
|
543
|
+
|
|
544
|
+
**Parameters:**
|
|
545
|
+
|
|
546
|
+
| Param | Type | Description |
|
|
547
|
+
| ------------ | -------- | ------------------------------------------------------------------- |
|
|
548
|
+
| `apiPath` | `string` | Dotted path where modules will be added (e.g., `"runtime.plugins"`) |
|
|
549
|
+
| `folderPath` | `string` | Path to folder containing modules to load (relative or absolute) |
|
|
550
|
+
|
|
551
|
+
**Returns:** `Promise<void>` - Resolves when the API extension is complete
|
|
552
|
+
|
|
553
|
+
**Features:**
|
|
554
|
+
|
|
555
|
+
- ✅ **Dynamic Loading**: Add modules after initial API creation
|
|
556
|
+
- ✅ **Path Creation**: Automatically creates intermediate objects for nested paths
|
|
557
|
+
- ✅ **Smart Merging**: Merges into existing objects or creates new namespaces
|
|
558
|
+
- ✅ **Mode Respect**: Uses same loading mode (lazy/eager) as parent API
|
|
559
|
+
- ✅ **Live Binding Updates**: Automatically updates `self`, `context`, and `reference`
|
|
560
|
+
- ✅ **Function Support**: Can traverse through functions (slothlet's function.property pattern)
|
|
561
|
+
- ✅ **Validation**: Prevents extension through primitives, validates path format
|
|
562
|
+
- ✅ **Overwrite Protection**: Optional `allowApiOverwrite` config prevents accidental endpoint overwrites
|
|
563
|
+
|
|
564
|
+
**Configuration:**
|
|
565
|
+
|
|
566
|
+
The `allowApiOverwrite` option controls whether `addApi` can overwrite existing endpoints:
|
|
567
|
+
|
|
568
|
+
```javascript
|
|
569
|
+
// Default behavior - allows overwrites (backwards compatible)
|
|
570
|
+
const api = await slothlet({
|
|
571
|
+
dir: "./api",
|
|
572
|
+
allowApiOverwrite: true // default
|
|
573
|
+
});
|
|
574
|
+
await api.addApi("tools", "./new-tools"); // Overwrites existing api.tools
|
|
575
|
+
|
|
576
|
+
// Protected mode - prevents overwrites
|
|
577
|
+
const api = await slothlet({
|
|
578
|
+
dir: "./api",
|
|
579
|
+
allowApiOverwrite: false // protection enabled
|
|
580
|
+
});
|
|
581
|
+
await api.addApi("tools", "./new-tools"); // Logs warning and skips
|
|
582
|
+
// Console: "[slothlet] Skipping addApi: API path "tools" final key "tools" already exists..."
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
**Usage Examples:**
|
|
586
|
+
|
|
587
|
+
```javascript
|
|
588
|
+
const api = await slothlet({ dir: "./api" });
|
|
589
|
+
|
|
590
|
+
// Add modules to nested path
|
|
591
|
+
await api.addApi("runtime.plugins", "./plugins");
|
|
592
|
+
api.runtime.plugins.myPlugin(); // New modules accessible
|
|
593
|
+
|
|
594
|
+
// Add to root level
|
|
595
|
+
await api.addApi("utilities", "./utils");
|
|
596
|
+
api.utilities.helperFunc(); // Root-level addition
|
|
597
|
+
|
|
598
|
+
// Deep nesting (creates intermediate objects)
|
|
599
|
+
await api.addApi("services.external.github", "./integrations/github");
|
|
600
|
+
api.services.external.github.getUser(); // Deep path created
|
|
601
|
+
|
|
602
|
+
// Merge into existing namespace
|
|
603
|
+
await api.addApi("math", "./advanced-math"); // Merges with existing api.math
|
|
604
|
+
api.math.add(2, 3); // Original functions preserved
|
|
605
|
+
api.math.advancedCalc(); // New functions added
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
**Path Validation:**
|
|
609
|
+
|
|
610
|
+
```javascript
|
|
611
|
+
// ❌ Invalid paths throw errors
|
|
612
|
+
await api.addApi("", "./modules"); // Empty string
|
|
613
|
+
await api.addApi(".path", "./modules"); // Leading dot
|
|
614
|
+
await api.addApi("path.", "./modules"); // Trailing dot
|
|
615
|
+
await api.addApi("path..name", "./modules"); // Consecutive dots
|
|
616
|
+
|
|
617
|
+
// ✅ Valid paths
|
|
618
|
+
await api.addApi("simple", "./modules"); // Single segment
|
|
619
|
+
await api.addApi("nested.path", "./modules"); // Dotted path
|
|
620
|
+
await api.addApi("very.deep.nested", "./modules"); // Multi-level
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
**Type Safety:**
|
|
624
|
+
|
|
625
|
+
```javascript
|
|
626
|
+
// ✅ Can extend through objects and functions
|
|
627
|
+
api.logger = { info: () => {} };
|
|
628
|
+
await api.addApi("logger.plugins", "./logger-plugins"); // Works - object
|
|
629
|
+
|
|
630
|
+
api.handler = () => "handler";
|
|
631
|
+
await api.addApi("handler.middleware", "./middleware"); // Works - function
|
|
632
|
+
|
|
633
|
+
// ❌ Cannot extend through primitives
|
|
634
|
+
api.config.timeout = 5000;
|
|
635
|
+
await api.addApi("config.timeout.advanced", "./modules"); // Throws error
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
> [!WARNING]
|
|
639
|
+
> **⚠️ Concurrency Limitation:** The `addApi` method is **not thread-safe**. Concurrent calls to `addApi` with overlapping paths may result in race conditions and inconsistent API state. Ensure calls are properly sequenced using `await` or other synchronization mechanisms. Do not call `addApi` from multiple threads/workers simultaneously.
|
|
640
|
+
|
|
529
641
|
> [!NOTE]
|
|
530
642
|
> **📚 For detailed API documentation with comprehensive parameter descriptions, method signatures, and examples, see [docs/API.md](https://github.com/CLDMV/slothlet/blob/HEAD/docs/API.md)**
|
|
531
643
|
|
|
@@ -649,6 +761,7 @@ console.log("TCP server started with context preservation");
|
|
|
649
761
|
- ✅ **Nested Events**: Works with any depth of EventEmitter nesting (server → socket → custom emitters)
|
|
650
762
|
- ✅ **Universal Support**: All EventEmitter methods (`on`, `once`, `addListener`) are automatically context-aware
|
|
651
763
|
- ✅ **Production Ready**: Uses Node.js AsyncResource patterns for reliable context propagation
|
|
764
|
+
- ✅ **Clean Shutdown**: Automatically cleans up all AsyncResource instances during shutdown to prevent hanging processes
|
|
652
765
|
- ✅ **Zero Overhead**: Only wraps listeners when context is active, minimal performance impact
|
|
653
766
|
|
|
654
767
|
> [!TIP]
|
|
@@ -26,6 +26,17 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
26
26
|
const defaultALS = new AsyncLocalStorage();
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
let originalMethods = null;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const globalResourceSet = new Set();
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
const globalListenerTracker = new WeakMap();
|
|
37
|
+
const allPatchedListeners = new Set();
|
|
38
|
+
|
|
39
|
+
|
|
29
40
|
export function enableAlsForEventEmitters(als = defaultALS) {
|
|
30
41
|
|
|
31
42
|
const kPatched = Symbol.for("slothlet.als.patched");
|
|
@@ -52,6 +63,9 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
52
63
|
const resource = new AsyncResource("slothlet-als-listener");
|
|
53
64
|
|
|
54
65
|
|
|
66
|
+
globalResourceSet.add(resource);
|
|
67
|
+
|
|
68
|
+
|
|
55
69
|
const runtime_wrappedListener = function (...args) {
|
|
56
70
|
return resource.runInAsyncScope(
|
|
57
71
|
() => {
|
|
@@ -62,6 +76,9 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
62
76
|
);
|
|
63
77
|
};
|
|
64
78
|
|
|
79
|
+
|
|
80
|
+
runtime_wrappedListener._slothletResource = resource;
|
|
81
|
+
|
|
65
82
|
return runtime_wrappedListener;
|
|
66
83
|
}
|
|
67
84
|
|
|
@@ -81,6 +98,22 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
81
98
|
proto[addFnName] = function (event, listener) {
|
|
82
99
|
const map = runtime_ensureMap(this);
|
|
83
100
|
const wrapped = runtime_wrapListener(listener);
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
if (!globalListenerTracker.has(this)) {
|
|
105
|
+
globalListenerTracker.set(this, new Set());
|
|
106
|
+
}
|
|
107
|
+
const listenerInfo = {
|
|
108
|
+
emitter: this,
|
|
109
|
+
event,
|
|
110
|
+
originalListener: listener,
|
|
111
|
+
wrappedListener: wrapped,
|
|
112
|
+
addMethod: addFnName
|
|
113
|
+
};
|
|
114
|
+
globalListenerTracker.get(this).add(listenerInfo);
|
|
115
|
+
allPatchedListeners.add(listenerInfo);
|
|
116
|
+
|
|
84
117
|
if (wrapped !== listener) map.set(listener, wrapped);
|
|
85
118
|
return orig.call(this, event, wrapped);
|
|
86
119
|
};
|
|
@@ -99,6 +132,30 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
99
132
|
const runtime_removeWrapper = function (event, listener) {
|
|
100
133
|
const map = runtime_ensureMap(this);
|
|
101
134
|
const wrapped = map.get(listener) || listener;
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if (globalListenerTracker.has(this)) {
|
|
138
|
+
const emitterListeners = globalListenerTracker.get(this);
|
|
139
|
+
for (const info of emitterListeners) {
|
|
140
|
+
if (info.originalListener === listener || info.wrappedListener === wrapped) {
|
|
141
|
+
emitterListeners.delete(info);
|
|
142
|
+
allPatchedListeners.delete(info);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
if (wrapped && wrapped._slothletResource) {
|
|
150
|
+
const resource = wrapped._slothletResource;
|
|
151
|
+
globalResourceSet.delete(resource);
|
|
152
|
+
try {
|
|
153
|
+
resource.emitDestroy();
|
|
154
|
+
} catch (err) {
|
|
155
|
+
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
102
159
|
map.delete(listener);
|
|
103
160
|
return method.call(this, event, wrapped);
|
|
104
161
|
};
|
|
@@ -117,4 +174,84 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
117
174
|
if (this[kMap]) this[kMap] = new WeakMap();
|
|
118
175
|
return res;
|
|
119
176
|
};
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if (!originalMethods) {
|
|
180
|
+
originalMethods = {
|
|
181
|
+
on: origOn,
|
|
182
|
+
once: origOnce,
|
|
183
|
+
addListener: origAdd,
|
|
184
|
+
prependListener: origPre,
|
|
185
|
+
prependOnceListener: origPreO,
|
|
186
|
+
off: origOff,
|
|
187
|
+
removeListener: origRem,
|
|
188
|
+
removeAllListeners: origRemoveAll
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
export function cleanupAllSlothletListeners() {
|
|
196
|
+
let cleanedCount = 0;
|
|
197
|
+
let errorCount = 0;
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
for (const listenerInfo of allPatchedListeners) {
|
|
201
|
+
try {
|
|
202
|
+
const { emitter, event, wrappedListener } = listenerInfo;
|
|
203
|
+
if (emitter && typeof emitter.removeListener === "function") {
|
|
204
|
+
emitter.removeListener(event, wrappedListener);
|
|
205
|
+
cleanedCount++;
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
errorCount++;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
allPatchedListeners.clear();
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
if (process.env.NODE_ENV === "development" || process.env.SLOTHLET_DEBUG) {
|
|
218
|
+
console.log(`[slothlet] Cleaned up ${cleanedCount} listeners (${errorCount} errors)`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function disableAlsForEventEmitters() {
|
|
223
|
+
const kPatched = Symbol.for("slothlet.als.patched");
|
|
224
|
+
|
|
225
|
+
if (!EventEmitter.prototype[kPatched] || !originalMethods) return;
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
cleanupAllSlothletListeners();
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
for (const resource of globalResourceSet) {
|
|
232
|
+
try {
|
|
233
|
+
resource.emitDestroy();
|
|
234
|
+
} catch (err) {
|
|
235
|
+
|
|
236
|
+
console.warn("[slothlet] AsyncResource cleanup warning:", err.message);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
globalResourceSet.clear();
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
const proto = EventEmitter.prototype;
|
|
243
|
+
proto.on = originalMethods.on;
|
|
244
|
+
proto.once = originalMethods.once;
|
|
245
|
+
proto.addListener = originalMethods.addListener;
|
|
246
|
+
if (originalMethods.prependListener) proto.prependListener = originalMethods.prependListener;
|
|
247
|
+
if (originalMethods.prependOnceListener) proto.prependOnceListener = originalMethods.prependOnceListener;
|
|
248
|
+
if (originalMethods.off) proto.off = originalMethods.off;
|
|
249
|
+
proto.removeListener = originalMethods.removeListener;
|
|
250
|
+
proto.removeAllListeners = originalMethods.removeAllListeners;
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
delete EventEmitter.prototype[kPatched];
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
originalMethods = null;
|
|
120
257
|
}
|
|
@@ -53,6 +53,14 @@ export class HookManager {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
cleanup() {
|
|
57
|
+
this.hooks.clear();
|
|
58
|
+
this.reportedErrors = new WeakSet();
|
|
59
|
+
this.registrationOrder = 0;
|
|
60
|
+
this.enabled = false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
56
64
|
off(nameOrPattern) {
|
|
57
65
|
|
|
58
66
|
if (this.hooks.has(nameOrPattern)) {
|
package/dist/slothlet.mjs
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
buildCategoryDecisions
|
|
32
32
|
} from "@cldmv/slothlet/helpers/api_builder";
|
|
33
33
|
import { updateInstanceData, cleanupInstance } from "./lib/helpers/instance-manager.mjs";
|
|
34
|
+
import { disableAlsForEventEmitters, cleanupAllSlothletListeners } from "./lib/helpers/als-eventemitter.mjs";
|
|
34
35
|
import { HookManager } from "./lib/helpers/hooks.mjs";
|
|
35
36
|
|
|
36
37
|
|
|
@@ -139,7 +140,7 @@ const slothletObject = {
|
|
|
139
140
|
reference: {},
|
|
140
141
|
mode: "singleton",
|
|
141
142
|
loaded: false,
|
|
142
|
-
config: { lazy: false, apiDepth: Infinity, debug: DEBUG, dir: null, sanitize: null },
|
|
143
|
+
config: { lazy: false, apiDepth: Infinity, debug: DEBUG, dir: null, sanitize: null, allowApiOverwrite: true },
|
|
143
144
|
_dispose: null,
|
|
144
145
|
_boundAPIShutdown: null,
|
|
145
146
|
instanceId: null,
|
|
@@ -993,9 +994,12 @@ const slothletObject = {
|
|
|
993
994
|
|
|
994
995
|
|
|
995
996
|
|
|
997
|
+
|
|
998
|
+
|
|
999
|
+
const instance = this;
|
|
996
1000
|
this.safeDefine(boundApi, "describe", function (showAll = false) {
|
|
997
1001
|
|
|
998
|
-
if (
|
|
1002
|
+
if (instance.config && instance.config.lazy) {
|
|
999
1003
|
if (!showAll) {
|
|
1000
1004
|
return Reflect.ownKeys(boundApi);
|
|
1001
1005
|
}
|
|
@@ -1051,17 +1055,36 @@ const slothletObject = {
|
|
|
1051
1055
|
} else {
|
|
1052
1056
|
this._boundAPIShutdown = null;
|
|
1053
1057
|
}
|
|
1058
|
+
|
|
1059
|
+
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
const hasUserDefinedShutdown = this._boundAPIShutdown !== null;
|
|
1063
|
+
|
|
1054
1064
|
const shutdownDesc = Object.getOwnPropertyDescriptor(boundApi, "shutdown");
|
|
1055
1065
|
if (!shutdownDesc || shutdownDesc.configurable) {
|
|
1056
1066
|
Object.defineProperty(boundApi, "shutdown", {
|
|
1057
1067
|
value: this.shutdown.bind(this),
|
|
1058
1068
|
writable: true,
|
|
1059
1069
|
configurable: true,
|
|
1060
|
-
enumerable:
|
|
1070
|
+
enumerable: hasUserDefinedShutdown
|
|
1061
1071
|
});
|
|
1062
1072
|
} else if (this.config && this.config.debug) {
|
|
1063
1073
|
console.warn("Could not redefine boundApi.shutdown: not configurable");
|
|
1064
1074
|
}
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
const addApiDesc = Object.getOwnPropertyDescriptor(boundApi, "addApi");
|
|
1078
|
+
if (!addApiDesc || addApiDesc.configurable) {
|
|
1079
|
+
Object.defineProperty(boundApi, "addApi", {
|
|
1080
|
+
value: this.addApi.bind(this),
|
|
1081
|
+
writable: true,
|
|
1082
|
+
configurable: true,
|
|
1083
|
+
enumerable: false
|
|
1084
|
+
});
|
|
1085
|
+
} else if (this.config && this.config.debug) {
|
|
1086
|
+
console.warn("Could not redefine boundApi.addApi: not configurable");
|
|
1087
|
+
}
|
|
1065
1088
|
|
|
1066
1089
|
|
|
1067
1090
|
|
|
@@ -1073,21 +1096,21 @@ const slothletObject = {
|
|
|
1073
1096
|
},
|
|
1074
1097
|
|
|
1075
1098
|
|
|
1076
|
-
safeDefine(obj, key, value) {
|
|
1099
|
+
safeDefine(obj, key, value, enumerable = false) {
|
|
1077
1100
|
const desc = Object.getOwnPropertyDescriptor(obj, key);
|
|
1078
1101
|
if (!desc) {
|
|
1079
1102
|
Object.defineProperty(obj, key, {
|
|
1080
1103
|
value,
|
|
1081
1104
|
writable: true,
|
|
1082
1105
|
configurable: true,
|
|
1083
|
-
enumerable
|
|
1106
|
+
enumerable
|
|
1084
1107
|
});
|
|
1085
1108
|
} else if (desc.configurable) {
|
|
1086
1109
|
Object.defineProperty(obj, key, {
|
|
1087
1110
|
value,
|
|
1088
1111
|
writable: true,
|
|
1089
1112
|
configurable: true,
|
|
1090
|
-
enumerable
|
|
1113
|
+
enumerable
|
|
1091
1114
|
});
|
|
1092
1115
|
} else if (this.config && this.config.debug) {
|
|
1093
1116
|
console.warn(`Could not redefine boundApi.${key}: not configurable`);
|
|
@@ -1110,6 +1133,220 @@ const slothletObject = {
|
|
|
1110
1133
|
},
|
|
1111
1134
|
|
|
1112
1135
|
|
|
1136
|
+
async addApi(apiPath, folderPath) {
|
|
1137
|
+
if (!this.loaded) {
|
|
1138
|
+
throw new Error("[slothlet] Cannot add API: API not loaded. Call create() or load() first.");
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
|
|
1142
|
+
if (typeof apiPath !== "string") {
|
|
1143
|
+
throw new TypeError("[slothlet] addApi: 'apiPath' must be a string.");
|
|
1144
|
+
}
|
|
1145
|
+
const normalizedApiPath = apiPath.trim();
|
|
1146
|
+
if (normalizedApiPath === "") {
|
|
1147
|
+
throw new TypeError("[slothlet] addApi: 'apiPath' must be a non-empty, non-whitespace string.");
|
|
1148
|
+
}
|
|
1149
|
+
const pathParts = normalizedApiPath.split(".");
|
|
1150
|
+
if (pathParts.some((part) => part === "")) {
|
|
1151
|
+
throw new Error(`[slothlet] addApi: 'apiPath' must not contain empty segments. Received: "${normalizedApiPath}"`);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
if (typeof folderPath !== "string") {
|
|
1156
|
+
throw new TypeError("[slothlet] addApi: 'folderPath' must be a string.");
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
|
|
1160
|
+
let resolvedFolderPath = folderPath;
|
|
1161
|
+
if (!path.isAbsolute(folderPath)) {
|
|
1162
|
+
resolvedFolderPath = resolvePathFromCaller(folderPath);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
let stats;
|
|
1167
|
+
try {
|
|
1168
|
+
stats = await fs.stat(resolvedFolderPath);
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
throw new Error(`[slothlet] addApi: Cannot access folder: ${resolvedFolderPath} - ${error.message}`);
|
|
1171
|
+
}
|
|
1172
|
+
if (!stats.isDirectory()) {
|
|
1173
|
+
throw new Error(`[slothlet] addApi: Path is not a directory: ${resolvedFolderPath}`);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
if (this.config.debug) {
|
|
1177
|
+
console.log(`[DEBUG] addApi: Loading modules from ${resolvedFolderPath} to path: ${normalizedApiPath}`);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
let newModules;
|
|
1182
|
+
if (this.config.lazy) {
|
|
1183
|
+
|
|
1184
|
+
newModules = await this.modes.lazy.create.call(this, resolvedFolderPath, this.config.apiDepth || Infinity, 0);
|
|
1185
|
+
} else {
|
|
1186
|
+
|
|
1187
|
+
newModules = await this.modes.eager.create.call(this, resolvedFolderPath, this.config.apiDepth || Infinity, 0);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
if (this.config.debug) {
|
|
1191
|
+
if (newModules && typeof newModules === "object") {
|
|
1192
|
+
console.log(`[DEBUG] addApi: Loaded modules:`, Object.keys(newModules));
|
|
1193
|
+
} else {
|
|
1194
|
+
console.log(
|
|
1195
|
+
`[DEBUG] addApi: Loaded modules (non-object):`,
|
|
1196
|
+
typeof newModules === "function" ? `[Function: ${newModules.name || "anonymous"}]` : newModules
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
|
|
1202
|
+
let currentTarget = this.api;
|
|
1203
|
+
let currentBoundTarget = this.boundapi;
|
|
1204
|
+
|
|
1205
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
1206
|
+
const part = pathParts[i];
|
|
1207
|
+
const key = this._toapiPathKey(part);
|
|
1208
|
+
|
|
1209
|
+
|
|
1210
|
+
|
|
1211
|
+
|
|
1212
|
+
if (Object.prototype.hasOwnProperty.call(currentTarget, key)) {
|
|
1213
|
+
const existing = currentTarget[key];
|
|
1214
|
+
if (existing === null || (typeof existing !== "object" && typeof existing !== "function")) {
|
|
1215
|
+
throw new Error(
|
|
1216
|
+
`[slothlet] Cannot extend API path "${normalizedApiPath}" through segment "${part}": ` +
|
|
1217
|
+
`existing value is type "${typeof existing}", cannot add properties.`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
|
|
1222
|
+
} else {
|
|
1223
|
+
currentTarget[key] = {};
|
|
1224
|
+
}
|
|
1225
|
+
if (Object.prototype.hasOwnProperty.call(currentBoundTarget, key)) {
|
|
1226
|
+
const existingBound = currentBoundTarget[key];
|
|
1227
|
+
if (existingBound === null || (typeof existingBound !== "object" && typeof existingBound !== "function")) {
|
|
1228
|
+
throw new Error(
|
|
1229
|
+
`[slothlet] Cannot extend bound API path "${normalizedApiPath}" through segment "${part}": ` +
|
|
1230
|
+
`existing value is type "${typeof existingBound}", cannot add properties.`
|
|
1231
|
+
);
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
} else {
|
|
1235
|
+
currentBoundTarget[key] = {};
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
|
|
1239
|
+
currentTarget = currentTarget[key];
|
|
1240
|
+
currentBoundTarget = currentBoundTarget[key];
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
|
|
1244
|
+
const finalKey = this._toapiPathKey(pathParts[pathParts.length - 1]);
|
|
1245
|
+
|
|
1246
|
+
|
|
1247
|
+
if (typeof newModules === "function") {
|
|
1248
|
+
|
|
1249
|
+
|
|
1250
|
+
if (Object.prototype.hasOwnProperty.call(currentTarget, finalKey)) {
|
|
1251
|
+
const existing = currentTarget[finalKey];
|
|
1252
|
+
|
|
1253
|
+
|
|
1254
|
+
if (this.config.allowApiOverwrite === false) {
|
|
1255
|
+
console.warn(
|
|
1256
|
+
`[slothlet] Skipping addApi: API path "${normalizedApiPath}" final key "${finalKey}" ` +
|
|
1257
|
+
`already exists (type: "${typeof existing}"). Set allowApiOverwrite: true to allow overwrites.`
|
|
1258
|
+
);
|
|
1259
|
+
return;
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
|
|
1263
|
+
if (existing !== null && typeof existing !== "function") {
|
|
1264
|
+
console.warn(
|
|
1265
|
+
`[slothlet] Overwriting existing non-function value at API path "${normalizedApiPath}" ` +
|
|
1266
|
+
`final key "${finalKey}" with a function. Previous type: "${typeof existing}".`
|
|
1267
|
+
);
|
|
1268
|
+
} else if (typeof existing === "function") {
|
|
1269
|
+
|
|
1270
|
+
console.warn(
|
|
1271
|
+
`[slothlet] Overwriting existing function at API path "${normalizedApiPath}" ` + `final key "${finalKey}" with a new function.`
|
|
1272
|
+
);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
currentTarget[finalKey] = newModules;
|
|
1276
|
+
currentBoundTarget[finalKey] = newModules;
|
|
1277
|
+
} else if (typeof newModules === "object" && newModules !== null) {
|
|
1278
|
+
|
|
1279
|
+
if (Object.prototype.hasOwnProperty.call(currentTarget, finalKey)) {
|
|
1280
|
+
const existing = currentTarget[finalKey];
|
|
1281
|
+
|
|
1282
|
+
|
|
1283
|
+
if (this.config.allowApiOverwrite === false && existing !== undefined && existing !== null) {
|
|
1284
|
+
|
|
1285
|
+
const hasContent = typeof existing === "object" ? Object.keys(existing).length > 0 : true;
|
|
1286
|
+
if (hasContent) {
|
|
1287
|
+
console.warn(
|
|
1288
|
+
`[slothlet] Skipping addApi merge: API path "${normalizedApiPath}" final key "${finalKey}" ` +
|
|
1289
|
+
`already exists with content (type: "${typeof existing}"). Set allowApiOverwrite: true to allow merging.`
|
|
1290
|
+
);
|
|
1291
|
+
return;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
if (existing !== null && typeof existing !== "object" && typeof existing !== "function") {
|
|
1296
|
+
throw new Error(
|
|
1297
|
+
`[slothlet] Cannot merge API at "${normalizedApiPath}": ` +
|
|
1298
|
+
`existing value at final key "${finalKey}" is type "${typeof existing}", cannot merge into primitives.`
|
|
1299
|
+
);
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
if (Object.prototype.hasOwnProperty.call(currentBoundTarget, finalKey)) {
|
|
1303
|
+
const existingBound = currentBoundTarget[finalKey];
|
|
1304
|
+
if (existingBound !== null && typeof existingBound !== "object" && typeof existingBound !== "function") {
|
|
1305
|
+
throw new Error(
|
|
1306
|
+
`[slothlet] Cannot merge bound API at "${normalizedApiPath}": ` +
|
|
1307
|
+
`existing value at final key "${finalKey}" is type "${typeof existingBound}", cannot merge into primitives.`
|
|
1308
|
+
);
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
if (!currentTarget[finalKey]) {
|
|
1314
|
+
currentTarget[finalKey] = {};
|
|
1315
|
+
}
|
|
1316
|
+
if (!currentBoundTarget[finalKey]) {
|
|
1317
|
+
currentBoundTarget[finalKey] = {};
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
|
|
1323
|
+
|
|
1324
|
+
|
|
1325
|
+
Object.assign(currentTarget[finalKey], newModules);
|
|
1326
|
+
Object.assign(currentBoundTarget[finalKey], newModules);
|
|
1327
|
+
} else if (newModules === null || newModules === undefined) {
|
|
1328
|
+
|
|
1329
|
+
const receivedType = newModules === null ? "null" : "undefined";
|
|
1330
|
+
console.warn(
|
|
1331
|
+
`[slothlet] addApi: No modules loaded from folder at API path "${normalizedApiPath}". ` +
|
|
1332
|
+
`Loaded modules resulted in ${receivedType}. Check that the folder contains valid module files.`
|
|
1333
|
+
);
|
|
1334
|
+
} else {
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
currentTarget[finalKey] = newModules;
|
|
1338
|
+
currentBoundTarget[finalKey] = newModules;
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
|
|
1342
|
+
this.updateBindings(this.context, this.reference, this.boundapi);
|
|
1343
|
+
|
|
1344
|
+
if (this.config.debug) {
|
|
1345
|
+
console.log(`[DEBUG] addApi: Successfully added modules at ${normalizedApiPath}`);
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
|
|
1349
|
+
|
|
1113
1350
|
async shutdown() {
|
|
1114
1351
|
|
|
1115
1352
|
|
|
@@ -1152,12 +1389,30 @@ const slothletObject = {
|
|
|
1152
1389
|
this._boundAPIShutdown = null;
|
|
1153
1390
|
|
|
1154
1391
|
|
|
1392
|
+
if (this.hookManager) {
|
|
1393
|
+
this.hookManager.cleanup();
|
|
1394
|
+
this.hookManager = null;
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
|
|
1155
1398
|
|
|
1156
1399
|
|
|
1157
1400
|
if (this.instanceId) {
|
|
1158
1401
|
await cleanupInstance(this.instanceId);
|
|
1159
1402
|
}
|
|
1160
1403
|
|
|
1404
|
+
|
|
1405
|
+
|
|
1406
|
+
try {
|
|
1407
|
+
|
|
1408
|
+
cleanupAllSlothletListeners();
|
|
1409
|
+
|
|
1410
|
+
disableAlsForEventEmitters();
|
|
1411
|
+
} catch (cleanupError) {
|
|
1412
|
+
|
|
1413
|
+
console.warn("[slothlet] Warning: EventEmitter cleanup failed:", cleanupError.message);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1161
1416
|
if (apiError || internalError) throw apiError || internalError;
|
|
1162
1417
|
}
|
|
1163
1418
|
} finally {
|
|
@@ -1197,6 +1452,18 @@ export function mutateLiveBindingFunction(target, source) {
|
|
|
1197
1452
|
}
|
|
1198
1453
|
}
|
|
1199
1454
|
|
|
1455
|
+
const managementMethods = ["shutdown", "addApi", "describe"];
|
|
1456
|
+
for (const method of managementMethods) {
|
|
1457
|
+
const desc = Object.getOwnPropertyDescriptor(source, method);
|
|
1458
|
+
if (desc) {
|
|
1459
|
+
try {
|
|
1460
|
+
Object.defineProperty(target, method, desc);
|
|
1461
|
+
} catch {
|
|
1462
|
+
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1200
1467
|
if (typeof source._impl === "function") {
|
|
1201
1468
|
target._impl = source._impl;
|
|
1202
1469
|
}
|
|
@@ -1211,3 +1478,5 @@ export default slothlet;
|
|
|
1211
1478
|
|
|
1212
1479
|
|
|
1213
1480
|
|
|
1481
|
+
|
|
1482
|
+
|
package/index.cjs
CHANGED
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
* @param {string} [options.mode="singleton"] - Execution mode (singleton, vm, worker, fork)
|
|
31
31
|
* @param {string} [options.api_mode="auto"] - API structure mode (auto, function, object)
|
|
32
32
|
* @param {string} [options.runtime] - Runtime type ("async", "asynclocalstorage", "live", "livebindings", "experimental")
|
|
33
|
+
* @param {boolean} [options.allowApiOverwrite=true] - Allow addApi to overwrite existing API endpoints
|
|
33
34
|
* @param {object} [options.context={}] - Context data for live bindings
|
|
34
35
|
* @param {object} [options.reference={}] - Reference objects to merge into API root
|
|
35
|
-
* @returns {Promise<
|
|
36
|
+
* @returns {Promise<import("./src/slothlet.mjs").SlothletAPI>} The bound API object with management methods
|
|
36
37
|
*
|
|
37
38
|
* @example // CJS usage
|
|
38
39
|
* const slothlet = require("@cldmv/slothlet");
|
package/index.mjs
CHANGED
|
@@ -55,9 +55,10 @@ function normalizeRuntimeType(runtime) {
|
|
|
55
55
|
* @param {number} [options.apiDepth=Infinity] - Maximum directory depth to scan
|
|
56
56
|
* @param {boolean} [options.debug=false] - Enable debug logging
|
|
57
57
|
* @param {string} [options.api_mode="auto"] - API structure mode (auto, function, object)
|
|
58
|
+
* @param {boolean} [options.allowApiOverwrite=true] - Allow addApi to overwrite existing API endpoints
|
|
58
59
|
* @param {object} [options.context={}] - Context data for live bindings
|
|
59
60
|
* @param {object} [options.reference={}] - Reference objects to merge into API root
|
|
60
|
-
* @returns {Promise<
|
|
61
|
+
* @returns {Promise<import("./src/slothlet.mjs").SlothletAPI>} The bound API object with management methods
|
|
61
62
|
*
|
|
62
63
|
* @example // ESM
|
|
63
64
|
* import slothlet from "@cldmv/slothlet";
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cldmv/slothlet",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"moduleVersions": {
|
|
5
|
-
"lazy": "1.3.
|
|
6
|
-
"eager": "1.3.
|
|
5
|
+
"lazy": "1.3.1",
|
|
6
|
+
"eager": "1.3.1"
|
|
7
7
|
},
|
|
8
8
|
"description": "Slothlet: Modular API Loader for Node.js. Lazy mode dynamically loads API modules and submodules only when accessed, supporting both lazy and eager loading.",
|
|
9
9
|
"main": "./index.cjs",
|
|
@@ -19,5 +19,38 @@
|
|
|
19
19
|
* enableAlsForEventEmitters(als);
|
|
20
20
|
*/
|
|
21
21
|
export function enableAlsForEventEmitters(als?: AsyncLocalStorage<any>): void;
|
|
22
|
+
/**
|
|
23
|
+
* Disable AsyncLocalStorage context propagation for EventEmitter instances.
|
|
24
|
+
*
|
|
25
|
+
* @function disableAlsForEventEmitters
|
|
26
|
+
* @package
|
|
27
|
+
*
|
|
28
|
+
* @description
|
|
29
|
+
* Restores original EventEmitter methods, removing the AsyncLocalStorage
|
|
30
|
+
* context propagation. This should be called during cleanup to prevent
|
|
31
|
+
* hanging AsyncResource instances that can keep the event loop alive.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* // Disable ALS patching during shutdown
|
|
35
|
+
* disableAlsForEventEmitters();
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* Clean up ALL listeners that went through slothlet's EventEmitter patching.
|
|
39
|
+
*
|
|
40
|
+
* @function cleanupAllSlothletListeners
|
|
41
|
+
* @package
|
|
42
|
+
*
|
|
43
|
+
* @description
|
|
44
|
+
* Removes all event listeners that were registered through slothlet's patched
|
|
45
|
+
* EventEmitter methods. This includes listeners from third-party libraries
|
|
46
|
+
* that got wrapped with AsyncResource instances. This nuclear cleanup option
|
|
47
|
+
* should be called during shutdown to prevent hanging listeners.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Clean up all patched listeners during shutdown
|
|
51
|
+
* cleanupAllSlothletListeners();
|
|
52
|
+
*/
|
|
53
|
+
export function cleanupAllSlothletListeners(): void;
|
|
54
|
+
export function disableAlsForEventEmitters(): void;
|
|
22
55
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
23
56
|
//# sourceMappingURL=als-eventemitter.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"als-eventemitter.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/als-eventemitter.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"als-eventemitter.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/als-eventemitter.mjs"],"names":[],"mappings":"AAkDA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,8EAiNC;AAED;;;;;;;;;;;;;;GAcG;AACH;;;;;;;;;;;;;;;GAeG;AACH,oDAyBC;AAED,mDAmCC;kCApViC,kBAAkB"}
|
|
@@ -55,6 +55,18 @@ export class HookManager {
|
|
|
55
55
|
priority?: number;
|
|
56
56
|
pattern?: string;
|
|
57
57
|
}): string;
|
|
58
|
+
/**
|
|
59
|
+
* Clean up all hooks and resources
|
|
60
|
+
* @public
|
|
61
|
+
* @description
|
|
62
|
+
* Clears all registered hooks and resets internal state.
|
|
63
|
+
* Should be called during shutdown to prevent memory leaks.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // Clean up during shutdown
|
|
67
|
+
* manager.cleanup();
|
|
68
|
+
*/
|
|
69
|
+
public cleanup(): void;
|
|
58
70
|
/**
|
|
59
71
|
* @function off
|
|
60
72
|
* @public
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/hooks.mjs"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;GAaG;AACH;IACC;;;;;;OAMG;IACH,sBALW,OAAO,mBACP,MAAM,YAEd;QAA0B,cAAc,GAAhC,OAAO;KACjB,EAQA;IANA,iBAAsB;IACtB,uBAAoC;IACpC,wBAAqD;IACrD,qBAAsB;IACtB,0BAA0B;IAC1B,gCAAmC;IAGpC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,gBAnBW,MAAM,QACN,MAAM,+BAOd;QAAyB,QAAQ,GAAzB,MAAM;QACW,OAAO,GAAxB,MAAM;KACd,GAAU,MAAM,CA0BlB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAdW,MAAM,GACJ,OAAO,CA6BnB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,oBAdW,MAAM,GACJ,IAAI,CA0BhB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAfW,MAAM,GACJ,KAAK,CAAC,MAAM,CAAC,CA4BzB;IAED;;;;;;;;;;;;OAYG;IACH,wBAVW,MAAM,GACJ,IAAI,CAchB;IAED;;;;;;;;;;;OAWG;IACH,kBATa,IAAI,CAWhB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,2BAkCC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAwBC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,2BAqBC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,0BAyBC;IAED;;;;;;;;;;;;;;OAcG;IACH,0BAkBC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,wBAmBC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,sBA4CC;IAED;;;;;;;;;;;;;OAaG;IACH,2BAuBC;IAED;;;;;;;;;;;;;OAaG;IACH,wBAiBC;IAED;;;;;;;;;;;;;;OAcG;IACH,sBAOC;CACD"}
|
|
1
|
+
{"version":3,"file":"hooks.d.mts","sourceRoot":"","sources":["../../../../dist/lib/helpers/hooks.mjs"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;GAaG;AACH;IACC;;;;;;OAMG;IACH,sBALW,OAAO,mBACP,MAAM,YAEd;QAA0B,cAAc,GAAhC,OAAO;KACjB,EAQA;IANA,iBAAsB;IACtB,uBAAoC;IACpC,wBAAqD;IACrD,qBAAsB;IACtB,0BAA0B;IAC1B,gCAAmC;IAGpC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,gBAnBW,MAAM,QACN,MAAM,+BAOd;QAAyB,QAAQ,GAAzB,MAAM;QACW,OAAO,GAAxB,MAAM;KACd,GAAU,MAAM,CA0BlB;IAED;;;;;;;;;;OAUG;IACH,uBAKC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAdW,MAAM,GACJ,OAAO,CA6BnB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,oBAdW,MAAM,GACJ,IAAI,CA0BhB;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,mBAfW,MAAM,GACJ,KAAK,CAAC,MAAM,CAAC,CA4BzB;IAED;;;;;;;;;;;;OAYG;IACH,wBAVW,MAAM,GACJ,IAAI,CAchB;IAED;;;;;;;;;;;OAWG;IACH,kBATa,IAAI,CAWhB;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,2BAkCC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,0BAwBC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,2BAqBC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,0BAyBC;IAED;;;;;;;;;;;;;;OAcG;IACH,0BAkBC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,wBAmBC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,sBA4CC;IAED;;;;;;;;;;;;;OAaG;IACH,2BAuBC;IAED;;;;;;;;;;;;;OAaG;IACH,wBAiBC;IAED;;;;;;;;;;;;;;OAcG;IACH,sBAOC;CACD"}
|
|
@@ -92,6 +92,13 @@ export type SlothletOptions = {
|
|
|
92
92
|
* - `"object"`: Force API to be plain object with method properties
|
|
93
93
|
*/
|
|
94
94
|
api_mode?: string;
|
|
95
|
+
/**
|
|
96
|
+
* - Controls whether addApi can overwrite existing API endpoints:
|
|
97
|
+
* - `true`: Allow overwrites (default, backwards compatible)
|
|
98
|
+
* - `false`: Prevent overwrites, log warning and skip when attempting to overwrite existing endpoints
|
|
99
|
+
* - Applies to both function and object overwrites at the final key of the API path
|
|
100
|
+
*/
|
|
101
|
+
allowApiOverwrite?: boolean;
|
|
95
102
|
/**
|
|
96
103
|
* - Context data object injected into live-binding `context` reference.
|
|
97
104
|
* - Available to all loaded modules via `import { context } from "@cldmv/slothlet/runtime"`. Useful for request data,
|
|
@@ -121,14 +128,28 @@ export type SlothletOptions = {
|
|
|
121
128
|
};
|
|
122
129
|
};
|
|
123
130
|
};
|
|
131
|
+
export type SlothletAPI = {
|
|
132
|
+
/**
|
|
133
|
+
* - Shuts down the API instance and cleans up all resources
|
|
134
|
+
*/
|
|
135
|
+
shutdown: () => Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* - Dynamically adds API modules from a folder to a specified API path
|
|
138
|
+
*/
|
|
139
|
+
addApi: (apiPath: string, folderPath: string) => Promise<void>;
|
|
140
|
+
/**
|
|
141
|
+
* - Returns metadata about the current API instance configuration. In lazy mode with showAll=false, returns an array of property keys. In lazy mode with showAll=true, returns a Promise resolving to an object. In eager mode, returns a plain object.
|
|
142
|
+
*/
|
|
143
|
+
describe: (showAll?: boolean) => ((string | symbol)[] | object | Promise<object>);
|
|
144
|
+
};
|
|
124
145
|
/**
|
|
125
146
|
* Creates a slothlet API instance with the specified configuration.
|
|
126
147
|
* This is the main entry point that can be called directly as a function.
|
|
127
148
|
* @async
|
|
128
149
|
* @alias module:@cldmv/slothlet
|
|
129
150
|
* @param {SlothletOptions} [options={}] - Configuration options for creating the API
|
|
130
|
-
* @returns {Promise<
|
|
151
|
+
* @returns {Promise<SlothletAPI>} The bound API object or function with management methods
|
|
131
152
|
* @public
|
|
132
153
|
*/
|
|
133
|
-
export function slothlet(options?: SlothletOptions): Promise<
|
|
154
|
+
export function slothlet(options?: SlothletOptions): Promise<SlothletAPI>;
|
|
134
155
|
//# sourceMappingURL=slothlet.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slothlet.d.mts","sourceRoot":"","sources":["../../dist/slothlet.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"slothlet.d.mts","sourceRoot":"","sources":["../../dist/slothlet.mjs"],"names":[],"mappings":"AA80DA;;;;;;;;;GASG;AACH,kDARW,WAAS,MAAM,UACf,WAAS,MAAM,QAoDzB;AA9sDD;;;;;;;GAOG;AACH,mBAJU,MAAM,CAIO;AAEvB;;;;;GAKG;AACH,sBAJU,MAAM,CAIU;AAE1B;;;;;GAKG;AACH,wBAJU,MAAM,CAIY;;;;;;;;;UAisDd,MAAM;;;;;;WAIN,OAAO;;;;;;;;WAGP,MAAM;;;;;;;;aAKN,MAAM;;;;;;;cAKN,MAAM;;;;;;;eAIN,MAAM;;;;;;;;YAIN,OAAO;;;;;;;eAKP,MAAM;;;;;;;wBAIN,OAAO;;;;;;cAIP,MAAM;;;;;;gBAGN,MAAM;;;;;;eAMjB;QAA8B,UAAU,GAA7B,OAAO;QACY,gBAAgB,GAAnC,OAAO;QACY,gBAAgB,GAAnC,OAAO;QACW,KAAK,GAClC;YAAqC,KAAK,GAA/B,MAAM,EAAE;YACkB,gBAAgB,GAA1C,MAAM,EAAE;YACkB,KAAK,GAA/B,MAAM,EAAE;YACkB,KAAK,GAA/B,MAAM,EAAE;SACrB;KAAA;;;;;;cAIa,MAAM,OAAO,CAAC,IAAI,CAAC;;;;YACnB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC;;;;cACtD,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC,CAAC,MAAM,GAAC,MAAM,CAAC,EAAE,GAAC,MAAM,GAAC,OAAO,CAAC,MAAM,CAAC,CAAC;;AA5vD/E;;;;;;;;GAQG;AACH,mCAJW,eAAe,GACb,OAAO,CAAC,WAAW,CAAC,CAiChC"}
|