@cldmv/slothlet 2.5.6 → 2.6.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 +59 -5
- package/dist/lib/helpers/als-eventemitter.mjs +5 -4
- package/dist/lib/helpers/api_builder.mjs +14 -2
- package/dist/lib/helpers/auto-wrap.mjs +4 -2
- package/dist/lib/helpers/instance-manager.mjs +111 -0
- package/dist/lib/modes/slothlet_lazy.mjs +47 -4
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +435 -0
- package/dist/lib/runtime/runtime-livebindings.mjs +298 -0
- package/dist/lib/runtime/runtime.mjs +152 -360
- package/dist/slothlet.mjs +88 -10
- package/index.cjs +30 -39
- package/index.mjs +45 -15
- package/package.json +17 -1
- package/types/dist/lib/helpers/als-eventemitter.d.mts +3 -3
- package/types/dist/lib/helpers/als-eventemitter.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder.d.mts.map +1 -1
- package/types/dist/lib/helpers/auto-wrap.d.mts.map +1 -1
- package/types/dist/lib/helpers/instance-manager.d.mts +41 -0
- package/types/dist/lib/helpers/instance-manager.d.mts.map +1 -0
- package/types/dist/lib/modes/slothlet_lazy.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +58 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +58 -0
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime.d.mts +8 -57
- package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +17 -9
- package/types/dist/slothlet.d.mts.map +1 -1
package/README.md
CHANGED
|
@@ -104,6 +104,12 @@ v2.0 represents a ground-up rewrite with enterprise-grade features:
|
|
|
104
104
|
> [!TIP]
|
|
105
105
|
> **📁 For comprehensive examples of API flattening, naming conventions, and function preservation patterns, see the test modules in [api_tests/](https://github.com/CLDMV/slothlet/blob/HEAD/api_tests) and their documentation in [docs/api_tests/](https://github.com/CLDMV/slothlet/blob/HEAD/docs/api_tests)**
|
|
106
106
|
|
|
107
|
+
> [!NOTE]
|
|
108
|
+
> **🔍 For detailed technical documentation on API transformation rules:**
|
|
109
|
+
>
|
|
110
|
+
> - **[API-RULES.md](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES.md)** - Verified API transformation rules with examples and test cases
|
|
111
|
+
> - **[API-RULES-CONDITIONS.md](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES-CONDITIONS.md)** - Complete technical reference of all conditional logic that controls API generation
|
|
112
|
+
|
|
107
113
|
### 🔗 **Advanced Binding System**
|
|
108
114
|
|
|
109
115
|
- **Live Bindings**: Dynamic context and reference binding for runtime API mutation
|
|
@@ -193,9 +199,16 @@ const mixedResult = await api.interop.processData({ data: "test" }); // CJS+ESM
|
|
|
193
199
|
```javascript
|
|
194
200
|
import slothlet from "@cldmv/slothlet";
|
|
195
201
|
|
|
196
|
-
// Lazy mode with copy-left materialization
|
|
202
|
+
// Lazy mode with copy-left materialization
|
|
197
203
|
const api = await slothlet({
|
|
198
|
-
|
|
204
|
+
mode: "lazy", // New preferred syntax
|
|
205
|
+
dir: "./api",
|
|
206
|
+
apiDepth: 3
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Or use legacy syntax (still supported)
|
|
210
|
+
const apiLegacy = await slothlet({
|
|
211
|
+
lazy: true, // Legacy syntax
|
|
199
212
|
dir: "./api",
|
|
200
213
|
apiDepth: 3
|
|
201
214
|
});
|
|
@@ -214,7 +227,8 @@ import slothlet from "@cldmv/slothlet";
|
|
|
214
227
|
|
|
215
228
|
const api = await slothlet({
|
|
216
229
|
dir: "./api",
|
|
217
|
-
|
|
230
|
+
mode: "eager", // New: Loading strategy (lazy/eager)
|
|
231
|
+
engine: "singleton", // New: Execution environment
|
|
218
232
|
api_mode: "auto", // API structure behavior
|
|
219
233
|
apiDepth: Infinity, // Directory traversal depth
|
|
220
234
|
debug: false, // Enable verbose logging
|
|
@@ -334,15 +348,50 @@ Creates and loads an API instance with the specified configuration.
|
|
|
334
348
|
| Option | Type | Default | Description |
|
|
335
349
|
| ----------- | --------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
336
350
|
| `dir` | `string` | `"api"` | Directory to load API modules from. Can be absolute or relative path. If relative, resolved from process.cwd(). |
|
|
337
|
-
| `lazy` | `boolean` | `false` |
|
|
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"` |
|
|
338
354
|
| `apiDepth` | `number` | `Infinity` | Directory traversal depth control - `0` for root only, `Infinity` for all levels |
|
|
339
355
|
| `debug` | `boolean` | `false` | Enable verbose logging. Can also be set via `--slothletdebug` command line flag or `SLOTHLET_DEBUG=true` environment variable |
|
|
340
|
-
| `mode` | `string` | `"singleton"` | Execution environment mode - `"singleton"`, `"vm"`, `"worker"`, or `"fork"` |
|
|
341
356
|
| `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) |
|
|
342
357
|
| `context` | `object` | `{}` | Context data object injected into live-binding `context` reference. Available to all loaded modules via `import { context } from "@cldmv/slothlet/runtime"` |
|
|
343
358
|
| `reference` | `object` | `{}` | Reference object merged into the API root level. Properties not conflicting with loaded modules are added directly to the API |
|
|
344
359
|
| `sanitize` | `object` | `{}` | **🔧 NEW**: Control how filenames become API property names. Supports exact matches, glob patterns (`*json*`), and boundary patterns (`**url**`). Configure `lowerFirst` and `rules` for `leave`, `leaveInsensitive`, `upper`, and `lower` transformations |
|
|
345
360
|
|
|
361
|
+
#### ✨ New Option Format (v2.6.0+)
|
|
362
|
+
|
|
363
|
+
The option structure has been improved for better clarity:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
// ✅ New recommended syntax
|
|
367
|
+
const api = await slothlet({
|
|
368
|
+
mode: "lazy", // Loading strategy: "lazy" | "eager"
|
|
369
|
+
engine: "singleton", // Execution environment: "singleton" | "vm" | "worker" | "fork"
|
|
370
|
+
dir: "./api"
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// ✅ Legacy syntax (still fully supported)
|
|
374
|
+
const api = await slothlet({
|
|
375
|
+
lazy: true, // Boolean loading strategy
|
|
376
|
+
mode: "singleton", // Execution environment (legacy placement)
|
|
377
|
+
dir: "./api"
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// ✅ Mixed usage (mode takes precedence)
|
|
381
|
+
const api = await slothlet({
|
|
382
|
+
lazy: false, // Will be overridden
|
|
383
|
+
mode: "lazy", // Takes precedence - results in lazy loading
|
|
384
|
+
engine: "singleton"
|
|
385
|
+
});
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
**Benefits of the new syntax:**
|
|
389
|
+
|
|
390
|
+
- **Clearer separation**: `mode` for loading strategy, `engine` for execution environment
|
|
391
|
+
- **Better discoverability**: String values are more self-documenting than boolean flags
|
|
392
|
+
- **Future-proof**: Easier to extend with additional loading strategies
|
|
393
|
+
- **Backward compatible**: All existing code continues to work unchanged
|
|
394
|
+
|
|
346
395
|
#### `slothlet.getApi()` ⇒ `object`
|
|
347
396
|
|
|
348
397
|
Returns the raw API object (Proxy or plain object).
|
|
@@ -1157,6 +1206,11 @@ Key highlights:
|
|
|
1157
1206
|
- **[Security Policy](https://github.com/CLDMV/slothlet/blob/HEAD/SECURITY.md)** - Security guidelines and reporting
|
|
1158
1207
|
- **[Test Documentation](https://github.com/CLDMV/slothlet/blob/HEAD/api_tests)** - Comprehensive test module examples
|
|
1159
1208
|
|
|
1209
|
+
### 🔧 Technical Documentation
|
|
1210
|
+
|
|
1211
|
+
- **[API Rules](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES.md)** - Systematically verified API transformation rules with real examples and test cases
|
|
1212
|
+
- **[API Rules Conditions](https://github.com/CLDMV/slothlet/blob/HEAD/API-RULES-CONDITIONS.md)** - Complete technical reference of all 26 conditional statements that control API generation
|
|
1213
|
+
|
|
1160
1214
|
---
|
|
1161
1215
|
|
|
1162
1216
|
## 🔗 Links
|
|
@@ -20,10 +20,13 @@
|
|
|
20
20
|
|
|
21
21
|
import { AsyncResource } from "node:async_hooks";
|
|
22
22
|
import { EventEmitter } from "node:events";
|
|
23
|
-
import {
|
|
23
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
const defaultALS = new AsyncLocalStorage();
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export function enableAlsForEventEmitters(als = defaultALS) {
|
|
27
30
|
|
|
28
31
|
const kPatched = Symbol.for("slothlet.als.patched");
|
|
29
32
|
|
|
@@ -115,5 +118,3 @@ export function enableAlsForEventEmitters(als = sharedALS) {
|
|
|
115
118
|
return res;
|
|
116
119
|
};
|
|
117
120
|
}
|
|
118
|
-
|
|
119
|
-
|
|
@@ -23,6 +23,7 @@ import path from "node:path";
|
|
|
23
23
|
import { types as utilTypes } from "node:util";
|
|
24
24
|
import { pathToFileURL } from "node:url";
|
|
25
25
|
import { multidefault_analyzeModules } from "@cldmv/slothlet/helpers/multidefault";
|
|
26
|
+
import { setActiveInstance } from "@cldmv/slothlet/helpers/instance-manager";
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
|
|
@@ -56,8 +57,19 @@ export async function analyzeModule(modulePath, options = {}) {
|
|
|
56
57
|
|
|
57
58
|
let importUrl = moduleUrl;
|
|
58
59
|
if (instance && instance.instanceId) {
|
|
59
|
-
const
|
|
60
|
-
|
|
60
|
+
const runtimeType = instance.config?.runtime || "async";
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if (runtimeType === "live") {
|
|
64
|
+
const separator = moduleUrl.includes("?") ? "&" : "?";
|
|
65
|
+
importUrl = `${moduleUrl}${separator}slothlet_instance=${instance.instanceId}`;
|
|
66
|
+
importUrl = `${importUrl}&slothlet_runtime=${runtimeType}`;
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
setActiveInstance(instance.instanceId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
const rawModule = await import(importUrl);
|
|
@@ -22,13 +22,15 @@
|
|
|
22
22
|
export async function autoWrapEventEmitters(nodeModule) {
|
|
23
23
|
|
|
24
24
|
try {
|
|
25
|
-
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const { self } = await import("@cldmv/slothlet/runtime/async");
|
|
26
28
|
if (!self?.__ctx) {
|
|
27
29
|
|
|
28
30
|
return nodeModule;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
const { makeWrapper } = await import("
|
|
33
|
+
const { makeWrapper } = await import("@cldmv/slothlet/runtime/async");
|
|
32
34
|
const wrapper = makeWrapper(self.__ctx);
|
|
33
35
|
|
|
34
36
|
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2025 CLDMV/Shinrai
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
const instanceRegistry = new Map();
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
let currentActiveInstanceId = null;
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export function getInstanceData(instanceId) {
|
|
27
|
+
return instanceRegistry.get(instanceId) || null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
export function updateInstanceData(instanceId, key, value) {
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
let instanceData = instanceRegistry.get(instanceId);
|
|
39
|
+
if (!instanceData) {
|
|
40
|
+
instanceData = {
|
|
41
|
+
self: null,
|
|
42
|
+
context: {},
|
|
43
|
+
reference: {}
|
|
44
|
+
};
|
|
45
|
+
instanceRegistry.set(instanceId, instanceData);
|
|
46
|
+
}
|
|
47
|
+
instanceData[key] = value;
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
export async function cleanupInstance(instanceId) {
|
|
54
|
+
|
|
55
|
+
instanceRegistry.delete(instanceId);
|
|
56
|
+
|
|
57
|
+
if (currentActiveInstanceId === instanceId) {
|
|
58
|
+
currentActiveInstanceId = null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
export function setActiveInstance(instanceId) {
|
|
64
|
+
currentActiveInstanceId = instanceId;
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
export function getCurrentActiveInstanceId() {
|
|
70
|
+
return currentActiveInstanceId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
export function detectCurrentInstanceId() {
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if (currentActiveInstanceId && instanceRegistry.has(currentActiveInstanceId)) {
|
|
84
|
+
return currentActiveInstanceId;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
const stack = new Error().stack;
|
|
89
|
+
if (stack) {
|
|
90
|
+
|
|
91
|
+
const matches = stack.match(/slothlet_instance=([^&\s):]+)/g);
|
|
92
|
+
|
|
93
|
+
if (matches && matches.length > 0) {
|
|
94
|
+
|
|
95
|
+
const instanceParam = matches[0];
|
|
96
|
+
const instanceId = instanceParam.replace(/slothlet_instance=([^&\s):]+).*/, "$1");
|
|
97
|
+
|
|
98
|
+
if (instanceRegistry.has(instanceId)) {
|
|
99
|
+
return instanceId;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
export function getAllInstanceIds() {
|
|
110
|
+
return Array.from(instanceRegistry.keys());
|
|
111
|
+
}
|
|
@@ -21,13 +21,17 @@ import fs from "node:fs/promises";
|
|
|
21
21
|
import { readdirSync } from "node:fs";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import { types as utilTypes } from "node:util";
|
|
24
|
-
import { runWithCtx } from "@cldmv/slothlet/runtime";
|
|
25
24
|
import { processModuleForAPI } from "@cldmv/slothlet/helpers/api_builder";
|
|
26
25
|
import { multidefault_analyzeModules } from "@cldmv/slothlet/helpers/multidefault";
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
export async function create(dir, maxDepth = Infinity, currentDepth = 0) {
|
|
30
29
|
const instance = this;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
const runtimePath = instance.config.runtime === "live" ? "@cldmv/slothlet/runtime/live" : "@cldmv/slothlet/runtime/async";
|
|
33
|
+
const { runWithCtx } = await import(runtimePath);
|
|
34
|
+
|
|
31
35
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
32
36
|
let api = {};
|
|
33
37
|
let rootDefaultFn = null;
|
|
@@ -156,7 +160,8 @@ export async function create(dir, maxDepth = Infinity, currentDepth = 0) {
|
|
|
156
160
|
instance,
|
|
157
161
|
depth,
|
|
158
162
|
maxDepth,
|
|
159
|
-
pathParts: [key]
|
|
163
|
+
pathParts: [key],
|
|
164
|
+
runWithCtx
|
|
160
165
|
});
|
|
161
166
|
parent[key] = proxy;
|
|
162
167
|
}
|
|
@@ -223,7 +228,7 @@ function replacePlaceholder(parent, key, placeholder, value, instance, depth) {
|
|
|
223
228
|
}
|
|
224
229
|
|
|
225
230
|
|
|
226
|
-
function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth, pathParts }) {
|
|
231
|
+
function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth, pathParts, runWithCtx }) {
|
|
227
232
|
let materialized = null;
|
|
228
233
|
let inFlight = null;
|
|
229
234
|
|
|
@@ -246,7 +251,8 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
|
|
|
246
251
|
instance,
|
|
247
252
|
depth: cd + 1,
|
|
248
253
|
maxDepth: md,
|
|
249
|
-
pathParts: [...pathParts, nestedKey]
|
|
254
|
+
pathParts: [...pathParts, nestedKey],
|
|
255
|
+
runWithCtx
|
|
250
256
|
})
|
|
251
257
|
});
|
|
252
258
|
materialized = value;
|
|
@@ -374,6 +380,43 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
|
|
|
374
380
|
return new Proxy(
|
|
375
381
|
function lazy_deepPropertyAccessor() {},
|
|
376
382
|
{
|
|
383
|
+
get(target, nextProp) {
|
|
384
|
+
if (nextProp === "name") return `lazy_${prop}_${subProp}`;
|
|
385
|
+
if (nextProp === "length") return 0;
|
|
386
|
+
|
|
387
|
+
return new Proxy(function lazy_deeperPropertyAccessor() {}, {
|
|
388
|
+
apply(target, thisArg, args) {
|
|
389
|
+
if (materialized) {
|
|
390
|
+
const value = materialized[prop];
|
|
391
|
+
const subValue = value ? value[subProp] : undefined;
|
|
392
|
+
if (subValue && typeof subValue[nextProp] === "function") {
|
|
393
|
+
const ctx = instance.boundapi?.__ctx;
|
|
394
|
+
if (ctx) {
|
|
395
|
+
return runWithCtx(ctx, subValue[nextProp], thisArg, args);
|
|
396
|
+
} else {
|
|
397
|
+
return subValue[nextProp].apply(thisArg, args);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return subValue ? subValue[nextProp] : undefined;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!inFlight) inFlight = _materialize();
|
|
404
|
+
return inFlight.then(function lazy_handleDeeperResolvedValue(resolved) {
|
|
405
|
+
const value = resolved ? resolved[prop] : undefined;
|
|
406
|
+
const subValue = value ? value[subProp] : undefined;
|
|
407
|
+
if (subValue && typeof subValue[nextProp] === "function") {
|
|
408
|
+
const ctx = instance.boundapi?.__ctx;
|
|
409
|
+
if (ctx) {
|
|
410
|
+
return runWithCtx(ctx, subValue[nextProp], thisArg, args);
|
|
411
|
+
} else {
|
|
412
|
+
return subValue[nextProp].apply(thisArg, args);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return subValue ? subValue[nextProp] : undefined;
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
},
|
|
377
420
|
apply(target, thisArg, args) {
|
|
378
421
|
|
|
379
422
|
if (materialized) {
|