@cldmv/slothlet 2.5.6 → 2.6.1

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 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 (opt-in)
202
+ // Lazy mode with copy-left materialization
197
203
  const api = await slothlet({
198
- lazy: true,
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
- lazy: false, // Loading strategy
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` | Loading strategy - `true` for lazy loading (on-demand), `false` for eager loading (immediate) |
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 { sharedALS } from "../runtime/runtime.mjs";
23
+ import { AsyncLocalStorage } from "node:async_hooks";
24
24
 
25
25
 
26
- export function enableAlsForEventEmitters(als = sharedALS) {
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 separator = moduleUrl.includes("?") ? "&" : "?";
60
- importUrl = `${moduleUrl}${separator}slothlet_instance=${instance.instanceId}`;
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
- const { self } = await import("@cldmv/slothlet/runtime");
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("../runtime/runtime.mjs");
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) {