@cldmv/slothlet 2.9.0 → 2.11.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.
Files changed (53) hide show
  1. package/AGENT-USAGE.md +41 -0
  2. package/README.md +95 -20
  3. package/dist/lib/engine/slothlet_child.mjs +1 -1
  4. package/dist/lib/engine/slothlet_engine.mjs +1 -1
  5. package/dist/lib/engine/slothlet_esm.mjs +1 -1
  6. package/dist/lib/engine/slothlet_helpers.mjs +1 -1
  7. package/dist/lib/engine/slothlet_worker.mjs +1 -1
  8. package/dist/lib/helpers/als-eventemitter.mjs +1 -1
  9. package/dist/lib/helpers/api_builder/add_api.mjs +321 -5
  10. package/dist/lib/helpers/api_builder/analysis.mjs +13 -3
  11. package/dist/lib/helpers/api_builder/construction.mjs +41 -3
  12. package/dist/lib/helpers/api_builder/decisions.mjs +16 -5
  13. package/dist/lib/helpers/api_builder/metadata.mjs +248 -0
  14. package/dist/lib/helpers/api_builder.mjs +1 -1
  15. package/dist/lib/helpers/auto-wrap.mjs +1 -1
  16. package/dist/lib/helpers/hooks.mjs +1 -1
  17. package/dist/lib/helpers/instance-manager.mjs +1 -1
  18. package/dist/lib/helpers/metadata-api.mjs +201 -0
  19. package/dist/lib/helpers/multidefault.mjs +12 -3
  20. package/dist/lib/helpers/resolve-from-caller.mjs +48 -11
  21. package/dist/lib/helpers/sanitize.mjs +1 -1
  22. package/dist/lib/helpers/utilities.mjs +1 -1
  23. package/dist/lib/modes/slothlet_eager.mjs +30 -2
  24. package/dist/lib/modes/slothlet_lazy.mjs +95 -5
  25. package/dist/lib/runtime/runtime-asynclocalstorage.mjs +5 -1
  26. package/dist/lib/runtime/runtime-livebindings.mjs +5 -1
  27. package/dist/lib/runtime/runtime.mjs +12 -1
  28. package/dist/slothlet.mjs +70 -6
  29. package/docs/API-RULES-CONDITIONS.md +511 -307
  30. package/docs/API-RULES.md +617 -589
  31. package/package.json +2 -2
  32. package/types/dist/lib/helpers/api_builder/add_api.d.mts +64 -22
  33. package/types/dist/lib/helpers/api_builder/add_api.d.mts.map +1 -1
  34. package/types/dist/lib/helpers/api_builder/analysis.d.mts.map +1 -1
  35. package/types/dist/lib/helpers/api_builder/construction.d.mts.map +1 -1
  36. package/types/dist/lib/helpers/api_builder/decisions.d.mts.map +1 -1
  37. package/types/dist/lib/helpers/api_builder/metadata.d.mts +99 -0
  38. package/types/dist/lib/helpers/api_builder/metadata.d.mts.map +1 -0
  39. package/types/dist/lib/helpers/metadata-api.d.mts +132 -0
  40. package/types/dist/lib/helpers/metadata-api.d.mts.map +1 -0
  41. package/types/dist/lib/helpers/multidefault.d.mts.map +1 -1
  42. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
  43. package/types/dist/lib/modes/slothlet_eager.d.mts.map +1 -1
  44. package/types/dist/lib/modes/slothlet_lazy.d.mts.map +1 -1
  45. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +2 -0
  46. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
  47. package/types/dist/lib/runtime/runtime-livebindings.d.mts +2 -0
  48. package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
  49. package/types/dist/lib/runtime/runtime.d.mts +1 -0
  50. package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
  51. package/types/dist/slothlet.d.mts +8 -0
  52. package/types/dist/slothlet.d.mts.map +1 -1
  53. package/types/index.d.mts.map +1 -0
package/AGENT-USAGE.md CHANGED
@@ -144,6 +144,47 @@ export function rootFunctionShout(message) {
144
144
 
145
145
  > 📖 **See**: [API-RULES.md Rule 4](./API-RULES.md#rule-4-default-export-container-pattern) for root-level default export handling
146
146
 
147
+ ### Pattern 5: AddApi Special File Pattern (Rule 11)
148
+
149
+ **File**: `addapi.mjs` loaded via `addApi()` → **API**: Always flattened for API extensions
150
+
151
+ ```js
152
+ // File: plugins/addapi.mjs
153
+ /**
154
+ * Special addapi.mjs file for runtime API extensions.
155
+ * Always flattens regardless of autoFlatten setting.
156
+ */
157
+ export function initializePlugin() {
158
+ return "Plugin initialized";
159
+ }
160
+
161
+ export function cleanup() {
162
+ return "Plugin cleaned up";
163
+ }
164
+
165
+ export function configure(options) {
166
+ return `Configured with ${options}`;
167
+ }
168
+
169
+ // Usage:
170
+ await api.addApi("plugins", "./plugins-folder");
171
+
172
+ // Result: Always flattened (no .addapi. level)
173
+ api.plugins.initializePlugin(); // ✅ Direct extension
174
+ api.plugins.cleanup(); // ✅ No intermediate namespace
175
+ api.plugins.configure(opts); // ✅ Seamless integration
176
+ ```
177
+
178
+ **Result**: `addapi.mjs` always flattens → Perfect for plugin systems and runtime extensions
179
+
180
+ **Use Cases**:
181
+
182
+ - 🔌 **Plugin Systems**: Runtime plugin loading
183
+ - 🔄 **Hot Reloading**: Dynamic API updates during development
184
+ - 📦 **Modular Extensions**: Clean extension of existing API surfaces
185
+
186
+ > 📖 **See**: [API-RULES.md Rule 11](./API-RULES.md#rule-11-addapi-special-file-pattern) for technical implementation details
187
+
147
188
  ## 🔄 Cross-Module Communication Patterns
148
189
 
149
190
  ### ✅ Using Live Bindings
package/README.md CHANGED
@@ -35,14 +35,19 @@ The name might suggest we're taking it easy, but don't be fooled. **Slothlet del
35
35
 
36
36
  ## ✨ What's New
37
37
 
38
- ### Latest: v2.9 (December 30, 2025)
38
+ ### Latest: v2.11.0 (January 2025)
39
39
 
40
- - **Per-Request Context Isolation** - New `api.run()` and `api.scope()` methods for isolated context execution ([Documentation](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md#per-request-context-isolation))
41
- - API Builder Modularization - Improved maintainability and code organization
42
- - [View Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.9.md)
40
+ - **AddApi Special File Pattern (Rule 11)** - Files named `addapi.mjs` now always flatten for seamless API namespace extensions
41
+ - **Filename-Matches-Container in addApi (Rule 1 Extension)** - Auto-flattening now works in runtime `addApi()` contexts
42
+ - **Enhanced addApi Content Preservation** - Fixed critical issue where multiple addApi calls were overwriting previous content instead of merging
43
+ - **Rule 12 Smart Flattening Enhancements** - Comprehensive smart flattening improvements with 168-scenario test coverage
44
+ - **API Documentation Suite Overhaul** - Enhanced 3-tier navigation system with verified examples and cross-references
45
+ - [View Changelog](./docs/changelog/v2.11.md)
43
46
 
44
47
  ### Recent Releases
45
48
 
49
+ - **v2.10.0** - Function metadata tagging and introspection capabilities ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.10.md))
50
+ - **v2.9** - Per-Request Context Isolation with `api.run()` and `api.scope()` methods ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.9.md))
46
51
  - **v2.8** - NPM security fixes and package workflow updates ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.8.md))
47
52
  - **v2.7** - Security updates ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.7.md))
48
53
  - **v2.6** - Hook System with 4 interceptor types ([Changelog](https://github.com/CLDMV/slothlet/blob/master/docs/changelog/v2.6.md))
@@ -130,7 +135,8 @@ Automatic context preservation across all asynchronous boundaries:
130
135
 
131
136
  ### Requirements
132
137
 
133
- - **Node.js v16.4.0 or higher** (required for AsyncLocalStorage support)
138
+ - **Node.js v16.20.2 or higher** (required for stack trace API fixes used in path resolution)
139
+ - Node.js 16.4-16.19 has a stack trace regression. For these versions, use slothlet 2.10.0: `npm install @cldmv/slothlet@2.10.0`
134
140
 
135
141
  ### Install
136
142
 
@@ -253,25 +259,93 @@ api.hooks.on(
253
259
  const result = await api.math.add(2, 3);
254
260
  ```
255
261
 
262
+ ### Dynamic API Extension with addApi()
263
+
264
+ Load additional modules at runtime and extend your API dynamically:
265
+
266
+ ```javascript
267
+ import slothlet from "@cldmv/slothlet";
268
+
269
+ const api = await slothlet({ dir: "./api" });
270
+
271
+ // Add plugins at runtime
272
+ await api.addApi("plugins", "./plugins-folder");
273
+ api.plugins.myPlugin();
274
+
275
+ // Create nested API structures
276
+ await api.addApi("runtime.plugins", "./more-plugins");
277
+ api.runtime.plugins.loader();
278
+
279
+ // Add with metadata for security/authorization
280
+ await api.addApi("plugins.trusted", "./trusted-plugins", {
281
+ trusted: true,
282
+ permissions: ["read", "write", "admin"],
283
+ version: "1.0.0"
284
+ });
285
+
286
+ await api.addApi("plugins.external", "./third-party", {
287
+ trusted: false,
288
+ permissions: ["read"]
289
+ });
290
+
291
+ // Access metadata on functions
292
+ const meta = api.plugins.trusted.someFunc.__metadata;
293
+ console.log(meta.trusted); // true
294
+ console.log(meta.permissions); // ["read", "write", "admin"]
295
+ ```
296
+
297
+ **Security & Authorization with metadataAPI:**
298
+
299
+ ```javascript
300
+ // Inside your modules, use metadataAPI for runtime introspection
301
+ import { metadataAPI } from "@cldmv/slothlet/runtime";
302
+
303
+ export async function sensitiveOperation() {
304
+ // Check caller's metadata
305
+ const caller = await metadataAPI.caller();
306
+
307
+ if (!caller?.trusted) {
308
+ throw new Error("Unauthorized: Caller is not trusted");
309
+ }
310
+
311
+ if (!caller.permissions.includes("admin")) {
312
+ throw new Error("Unauthorized: Admin permission required");
313
+ }
314
+
315
+ // Proceed with secure operation
316
+ return "Success";
317
+ }
318
+
319
+ // Get metadata by path
320
+ const meta = await metadataAPI.get("plugins.trusted.someFunc");
321
+
322
+ // Get current function's metadata
323
+ const self = await metadataAPI.self();
324
+ console.log("My version:", self.version);
325
+ ```
326
+
327
+ 🔒 **For complete metadata system documentation, see [docs/METADATA.md](https://github.com/CLDMV/slothlet/blob/master/docs/METADATA.md)**
328
+
256
329
  ---
257
330
 
258
331
  ## 📚 Configuration Options
259
332
 
260
- | Option | Type | Default | Description |
261
- | ------------------- | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
262
- | `dir` | `string` | `"api"` | Directory to load API modules from (absolute or relative path) |
263
- | `mode` | `string` | `"eager"` | **New** loading mode - `"lazy"` for on-demand loading, `"eager"` for immediate loading |
264
- | `lazy` | `boolean` | `false` | **Legacy** loading strategy (use `mode` instead) |
265
- | `engine` | `string` | `"singleton"` | Execution environment: `"singleton"`, `"vm"`, `"worker"`, or `"fork"` (experimental modes) |
266
- | `runtime` | `string` | `"async"` | Runtime binding system: `"async"` for AsyncLocalStorage (requires Node.js v16.4.0+), `"live"` for live-bindings (works on Node.js v12.20.0+) |
267
- | `apiDepth` | `number` | `Infinity` | Directory traversal depth - `0` for root only, `Infinity` for all levels |
268
- | `debug` | `boolean` | `false` | Enable verbose logging (also via `--slothletdebug` flag or `SLOTHLET_DEBUG=true` env var) |
269
- | `api_mode` | `string` | `"auto"` | API structure behavior: `"auto"` (detect), `"function"` (force callable), `"object"` (force object) |
270
- | `allowApiOverwrite` | `boolean` | `true` | Allow `addApi()` to overwrite existing endpoints (`false` = prevent overwrites with warning) |
271
- | `context` | `object` | `{}` | Context data injected into live-binding (available via `import { context } from "@cldmv/slothlet/runtime"`) |
272
- | `reference` | `object` | `{}` | Reference object merged into API root level |
273
- | `sanitize` | `object` | `{}` | Advanced filename-to-API transformation control with `lowerFirst`, `preserveAllUpper`, `preserveAllLower`, and `rules` (supports exact matches, glob patterns `*json*`, and boundary patterns `**url**`) |
274
- | `hooks` | `mixed` | `false` | Enable hook system: `true` (enable all), `"pattern"` (enable with pattern), or object with `enabled`, `pattern`, `suppressErrors` options |
333
+ | Option | Type | Default | Description |
334
+ | ----------------------- | --------- | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
335
+ | `dir` | `string` | `"api"` | Directory to load API modules from (absolute or relative path) |
336
+ | `mode` | `string` | `"eager"` | **New** loading mode - `"lazy"` for on-demand loading, `"eager"` for immediate loading |
337
+ | `lazy` | `boolean` | `false` | **Legacy** loading strategy (use `mode` instead) |
338
+ | `engine` | `string` | `"singleton"` | Execution environment: `"singleton"`, `"vm"`, `"worker"`, or `"fork"` (experimental modes) |
339
+ | `runtime` | `string` | `"async"` | Runtime binding system: `"async"` for AsyncLocalStorage (requires Node.js v16.20.2+), `"live"` for live-bindings (works on Node.js v12.20.0+) |
340
+ | `apiDepth` | `number` | `Infinity` | Directory traversal depth - `0` for root only, `Infinity` for all levels |
341
+ | `debug` | `boolean` | `false` | Enable verbose logging (also via `--slothletdebug` flag or `SLOTHLET_DEBUG=true` env var) |
342
+ | `api_mode` | `string` | `"auto"` | API structure behavior: `"auto"` (detect), `"function"` (force callable), `"object"` (force object) |
343
+ | `allowApiOverwrite` | `boolean` | `true` | Allow `addApi()` to overwrite existing endpoints (`false` = prevent overwrites with warning) |
344
+ | `enableModuleOwnership` | `boolean` | `false` | Enable module-based API ownership tracking (`true` = track ownership for selective overwrites, `false` = disabled for performance) |
345
+ | `context` | `object` | `{}` | Context data injected into live-binding (available via `import { context } from "@cldmv/slothlet/runtime"`) |
346
+ | `reference` | `object` | `{}` | Reference object merged into API root level |
347
+ | `sanitize` | `object` | `{}` | Advanced filename-to-API transformation control with `lowerFirst`, `preserveAllUpper`, `preserveAllLower`, and `rules` (supports exact matches, glob patterns `*json*`, and boundary patterns `**url**`) |
348
+ | `hooks` | `mixed` | `false` | Enable hook system: `true` (enable all), `"pattern"` (enable with pattern), or object with `enabled`, `pattern`, `suppressErrors` options |
275
349
 
276
350
  **For complete API documentation with detailed parameter descriptions and examples, see [docs/generated/API.md](https://github.com/CLDMV/slothlet/blob/master/docs/generated/API.md)**
277
351
 
@@ -481,6 +555,7 @@ Key highlights:
481
555
 
482
556
  - **[Hook System](https://github.com/CLDMV/slothlet/blob/master/docs/HOOKS.md)** - Complete hook system documentation with 4 hook types, pattern matching, and examples
483
557
  - **[Context Propagation](https://github.com/CLDMV/slothlet/blob/master/docs/CONTEXT-PROPAGATION.md)** - EventEmitter and class instance context preservation
558
+ - **[Metadata System](https://github.com/CLDMV/slothlet/blob/master/docs/METADATA.md)** - Function metadata tagging and runtime introspection for security, authorization, and auditing
484
559
  - **[Module Structure](https://github.com/CLDMV/slothlet/blob/master/docs/MODULE-STRUCTURE.md)** - Comprehensive module organization patterns and examples
485
560
  - **[API Flattening](https://github.com/CLDMV/slothlet/blob/master/docs/API-FLATTENING.md)** - The 5 flattening rules with decision tree and benefits
486
561
 
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -1,5 +1,5 @@
1
1
  /*
2
- Copyright 2025 CLDMV/Shinrai
2
+ Copyright 2026 CLDMV/Shinrai
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
5
5
  you may not use this file except in compliance with the License.
@@ -21,9 +21,23 @@
21
21
  import fs from "node:fs/promises";
22
22
  import path from "node:path";
23
23
  import { resolvePathFromCaller } from "@cldmv/slothlet/helpers/resolve-from-caller";
24
+ import { cleanMetadata, tagLoadedFunctions } from "./metadata.mjs";
24
25
 
25
26
 
26
- export async function addApiFromFolder({ apiPath, folderPath, instance }) {
27
+
28
+
29
+ export async function addApiFromFolder({ apiPath, folderPath, instance, metadata = {}, options = {} }) {
30
+ const { forceOverwrite = false, moduleId } = options;
31
+
32
+
33
+ if (forceOverwrite && !moduleId) {
34
+ throw new Error(`[slothlet] Rule 12: forceOverwrite requires moduleId parameter for ownership tracking`);
35
+ }
36
+
37
+ if (forceOverwrite && !instance.config.enableModuleOwnership) {
38
+ throw new Error(`[slothlet] Rule 12: forceOverwrite requires enableModuleOwnership: true in slothlet configuration`);
39
+ }
40
+
27
41
  if (!instance.loaded) {
28
42
  throw new Error("[slothlet] Cannot add API: API not loaded. Call create() or load() first.");
29
43
  }
@@ -47,9 +61,21 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
47
61
  }
48
62
 
49
63
 
64
+
65
+
50
66
  let resolvedFolderPath = folderPath;
51
- if (!path.isAbsolute(folderPath)) {
67
+ const isAbsolute = path.isAbsolute(folderPath);
68
+
69
+ if (instance.config.debug) {
70
+ console.log(`[DEBUG] addApi: folderPath="${folderPath}"`);
71
+ console.log(`[DEBUG] addApi: isAbsolute=${isAbsolute}`);
72
+ }
73
+
74
+ if (!isAbsolute) {
52
75
  resolvedFolderPath = resolvePathFromCaller(folderPath);
76
+ if (instance.config.debug) {
77
+ console.log(`[DEBUG] addApi: Resolved relative path to: ${resolvedFolderPath}`);
78
+ }
53
79
  }
54
80
 
55
81
 
@@ -68,6 +94,74 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
68
94
  }
69
95
 
70
96
 
97
+
98
+
99
+
100
+
101
+ let earlyCurrentTarget = instance.api;
102
+ let earlyCurrentBoundTarget = instance.boundapi;
103
+ const earlyPathParts = normalizedApiPath.split(".");
104
+
105
+ for (let i = 0; i < earlyPathParts.length - 1; i++) {
106
+ const part = earlyPathParts[i];
107
+ const key = instance._toapiPathKey(part);
108
+ if (earlyCurrentTarget[key]) {
109
+ earlyCurrentTarget = earlyCurrentTarget[key];
110
+ } else {
111
+ earlyCurrentTarget = null;
112
+ break;
113
+ }
114
+ if (earlyCurrentBoundTarget[key]) {
115
+ earlyCurrentBoundTarget = earlyCurrentBoundTarget[key];
116
+ } else {
117
+ earlyCurrentBoundTarget = null;
118
+ break;
119
+ }
120
+ }
121
+
122
+ const earlyFinalKey = instance._toapiPathKey(earlyPathParts[earlyPathParts.length - 1]);
123
+ let superEarlyExistingTargetContent = null;
124
+ let superEarlyExistingBoundContent = null;
125
+
126
+ if (earlyCurrentTarget && earlyCurrentTarget[earlyFinalKey]) {
127
+ if (typeof earlyCurrentTarget[earlyFinalKey] === "function" && earlyCurrentTarget[earlyFinalKey].__slothletPath) {
128
+ if (instance.config.debug) {
129
+ console.log(`[DEBUG] addApi: SUPER EARLY - Target is lazy proxy - materializing to capture existing content`);
130
+ }
131
+ const _ = earlyCurrentTarget[earlyFinalKey].__trigger;
132
+ await earlyCurrentTarget[earlyFinalKey]();
133
+ }
134
+ if (typeof earlyCurrentTarget[earlyFinalKey] === "object") {
135
+ superEarlyExistingTargetContent = { ...earlyCurrentTarget[earlyFinalKey] };
136
+ if (instance.config.debug) {
137
+ console.log(`[DEBUG] addApi: SUPER EARLY - Captured existing target content:`, Object.keys(superEarlyExistingTargetContent));
138
+ }
139
+ }
140
+ }
141
+
142
+ if (earlyCurrentBoundTarget && earlyCurrentBoundTarget[earlyFinalKey]) {
143
+ if (instance.config.debug) {
144
+ console.log(
145
+ `[DEBUG] addApi: SUPER EARLY - currentBoundTarget[${earlyFinalKey}] exists, type:`,
146
+ typeof earlyCurrentBoundTarget[earlyFinalKey]
147
+ );
148
+ }
149
+ if (typeof earlyCurrentBoundTarget[earlyFinalKey] === "function" && earlyCurrentBoundTarget[earlyFinalKey].__slothletPath) {
150
+ if (instance.config.debug) {
151
+ console.log(`[DEBUG] addApi: SUPER EARLY - Bound target is lazy proxy - materializing to capture existing bound content`);
152
+ }
153
+ const _ = earlyCurrentBoundTarget[earlyFinalKey].__trigger;
154
+ await earlyCurrentBoundTarget[earlyFinalKey]();
155
+ }
156
+ if (typeof earlyCurrentBoundTarget[earlyFinalKey] === "object") {
157
+ superEarlyExistingBoundContent = { ...earlyCurrentBoundTarget[earlyFinalKey] };
158
+ if (instance.config.debug) {
159
+ console.log(`[DEBUG] addApi: SUPER EARLY - Captured existing bound content:`, Object.keys(superEarlyExistingBoundContent));
160
+ }
161
+ }
162
+ }
163
+
164
+
71
165
  let newModules;
72
166
  if (instance.config.lazy) {
73
167
 
@@ -77,6 +171,110 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
77
171
  newModules = await instance.modes.eager.create.call(instance, resolvedFolderPath, instance.config.apiDepth || Infinity, 0);
78
172
  }
79
173
 
174
+ if (instance.config.debug) {
175
+ console.log(`[DEBUG] addApi: Loaded modules structure:`, Object.keys(newModules || {}));
176
+ console.log(`[DEBUG] addApi: Full newModules:`, newModules);
177
+ }
178
+
179
+
180
+
181
+ if (newModules && typeof newModules === "object" && newModules.addapi) {
182
+ if (instance.config.debug) {
183
+ console.log(`[DEBUG] addApi: Found addapi.mjs - applying Rule 6 flattening`);
184
+ console.log(`[DEBUG] addApi: Original structure:`, Object.keys(newModules));
185
+ console.log(`[DEBUG] addApi: Addapi contents:`, Object.keys(newModules.addapi));
186
+ }
187
+
188
+
189
+ const addapiContent = newModules.addapi;
190
+
191
+
192
+ delete newModules.addapi;
193
+
194
+
195
+ if (addapiContent && typeof addapiContent === "object") {
196
+
197
+ Object.assign(newModules, addapiContent);
198
+
199
+ if (instance.config.debug) {
200
+ console.log(`[DEBUG] addApi: After addapi flattening:`, Object.keys(newModules));
201
+ }
202
+ } else if (typeof addapiContent === "function") {
203
+
204
+ Object.assign(newModules, addapiContent);
205
+
206
+ if (instance.config.debug) {
207
+ console.log(`[DEBUG] addApi: Flattened addapi function with properties:`, Object.keys(newModules));
208
+ }
209
+ }
210
+ }
211
+
212
+
213
+
214
+
215
+ const pathSegments = normalizedApiPath.split(".");
216
+ const lastSegment = pathSegments[pathSegments.length - 1];
217
+ let rootLevelFileContent = null;
218
+
219
+ if (newModules && typeof newModules === "object" && newModules[lastSegment]) {
220
+ if (instance.config.debug) {
221
+ console.log(`[DEBUG] addApi: Found root-level file matching API path segment "${lastSegment}" - applying Rule 7 flattening`);
222
+ }
223
+
224
+
225
+ let fileContent = newModules[lastSegment];
226
+ if (typeof fileContent === "function" && fileContent.name && fileContent.name.startsWith("lazyFolder_")) {
227
+
228
+ if (fileContent.__slothletPath) {
229
+ const _ = fileContent.__trigger;
230
+ await fileContent();
231
+
232
+ fileContent = newModules[lastSegment];
233
+ if (instance.config.debug) {
234
+ console.log(`[DEBUG] addApi: Materialized lazy proxy for root-level file:`, Object.keys(fileContent || {}));
235
+ }
236
+ }
237
+ }
238
+
239
+ if (instance.config.debug) {
240
+ console.log(`[DEBUG] addApi: Root-level file content:`, Object.keys(fileContent || {}));
241
+ }
242
+
243
+
244
+
245
+ rootLevelFileContent = fileContent && typeof fileContent === "object" ? { ...fileContent } : fileContent;
246
+
247
+
248
+ delete newModules[lastSegment];
249
+
250
+ if (instance.config.debug) {
251
+ console.log(`[DEBUG] addApi: After removing root-level file, remaining structure:`, Object.keys(newModules));
252
+ }
253
+ }
254
+
255
+
256
+
257
+
258
+ if (newModules && metadata && typeof metadata === "object" && Object.keys(metadata).length > 0) {
259
+
260
+ const fullMetadata = {
261
+ ...metadata,
262
+ sourceFolder: resolvedFolderPath
263
+ };
264
+
265
+ if (instance.config.debug) {
266
+ console.log(`[DEBUG] addApi: Tagging functions with metadata:`, Object.keys(fullMetadata));
267
+ }
268
+
269
+ tagLoadedFunctions(newModules, fullMetadata, resolvedFolderPath);
270
+ } else if (newModules) {
271
+
272
+ if (instance.config.debug) {
273
+ console.log(`[DEBUG] addApi: Cleaning metadata from functions (no metadata provided)`);
274
+ }
275
+ cleanMetadata(newModules);
276
+ }
277
+
80
278
  if (instance.config.debug) {
81
279
  if (newModules && typeof newModules === "object") {
82
280
  console.log(`[DEBUG] addApi: Loaded modules:`, Object.keys(newModules));
@@ -133,6 +331,14 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
133
331
 
134
332
  const finalKey = instance._toapiPathKey(pathParts[pathParts.length - 1]);
135
333
 
334
+ if (instance.config.debug) {
335
+ console.log(`[DEBUG] addApi: Final assignment - newModules type:`, typeof newModules, "keys:", Object.keys(newModules || {}));
336
+ }
337
+
338
+
339
+
340
+
341
+
136
342
 
137
343
  if (typeof newModules === "function") {
138
344
 
@@ -141,7 +347,21 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
141
347
  const existing = currentTarget[finalKey];
142
348
 
143
349
 
144
- if (instance.config.allowApiOverwrite === false) {
350
+ if (instance.config.enableModuleOwnership && existing) {
351
+ const normalizedPath = normalizedApiPath + (normalizedApiPath ? "." : "") + finalKey;
352
+ const canOverwrite = instance._validateModuleOwnership(normalizedPath, moduleId, forceOverwrite);
353
+
354
+ if (forceOverwrite && !canOverwrite) {
355
+ const existingOwner = instance._getApiOwnership(normalizedPath);
356
+ throw new Error(
357
+ `[slothlet] Rule 12: Cannot overwrite API "${normalizedPath}" - owned by module "${existingOwner}", ` +
358
+ `attempted by module "${moduleId}". Modules can only overwrite APIs they own.`
359
+ );
360
+ }
361
+ }
362
+
363
+
364
+ if (!forceOverwrite && instance.config.allowApiOverwrite === false) {
145
365
  console.warn(
146
366
  `[slothlet] Skipping addApi: API path "${normalizedApiPath}" final key "${finalKey}" ` +
147
367
  `already exists (type: "${typeof existing}"). Set allowApiOverwrite: true to allow overwrites.`
@@ -170,7 +390,21 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
170
390
  const existing = currentTarget[finalKey];
171
391
 
172
392
 
173
- if (instance.config.allowApiOverwrite === false && existing !== undefined && existing !== null) {
393
+ if (instance.config.enableModuleOwnership && existing) {
394
+ const normalizedPath = normalizedApiPath + (normalizedApiPath ? "." : "") + finalKey;
395
+ const canOverwrite = instance._validateModuleOwnership(normalizedPath, moduleId, forceOverwrite);
396
+
397
+ if (forceOverwrite && !canOverwrite) {
398
+ const existingOwner = instance._getApiOwnership(normalizedPath);
399
+ throw new Error(
400
+ `[slothlet] Rule 12: Cannot overwrite API "${normalizedPath}" - owned by module "${existingOwner}", ` +
401
+ `attempted by module "${moduleId}". Modules can only overwrite APIs they own.`
402
+ );
403
+ }
404
+ }
405
+
406
+
407
+ if (!forceOverwrite && instance.config.allowApiOverwrite === false && existing !== undefined && existing !== null) {
174
408
 
175
409
  const hasContent = typeof existing === "object" ? Object.keys(existing).length > 0 : true;
176
410
  if (hasContent) {
@@ -200,6 +434,25 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
200
434
  }
201
435
 
202
436
 
437
+
438
+
439
+ const targetValue = currentTarget[finalKey];
440
+ if (typeof targetValue === "function" && targetValue.__slothletPath) {
441
+
442
+
443
+ const _ = targetValue.__trigger;
444
+ await targetValue();
445
+
446
+ }
447
+
448
+ const boundTargetValue = currentBoundTarget[finalKey];
449
+ if (typeof boundTargetValue === "function" && boundTargetValue.__slothletPath) {
450
+
451
+ const _ = boundTargetValue.__trigger;
452
+ await boundTargetValue();
453
+ }
454
+
455
+
203
456
  if (!currentTarget[finalKey]) {
204
457
  currentTarget[finalKey] = {};
205
458
  }
@@ -212,8 +465,54 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
212
465
 
213
466
 
214
467
 
468
+
469
+
470
+ if (superEarlyExistingTargetContent) {
471
+ if (instance.config.debug) {
472
+ console.log(`[DEBUG] addApi: Restoring existing content - keys:`, Object.keys(superEarlyExistingTargetContent));
473
+ }
474
+ Object.assign(currentTarget[finalKey], superEarlyExistingTargetContent);
475
+ }
476
+ if (superEarlyExistingBoundContent) {
477
+ if (instance.config.debug) {
478
+ console.log(`[DEBUG] addApi: Restoring existing BOUND content - keys:`, Object.keys(superEarlyExistingBoundContent));
479
+ }
480
+ Object.assign(currentBoundTarget[finalKey], superEarlyExistingBoundContent);
481
+ }
482
+
483
+
484
+ if (instance.config.debug) {
485
+ console.log(`[DEBUG] addApi: Before merging new modules - current keys:`, Object.keys(currentTarget[finalKey] || {}));
486
+ console.log(`[DEBUG] addApi: New modules to merge - keys:`, Object.keys(newModules));
487
+ }
215
488
  Object.assign(currentTarget[finalKey], newModules);
216
489
  Object.assign(currentBoundTarget[finalKey], newModules);
490
+ if (instance.config.debug) {
491
+ console.log(`[DEBUG] addApi: After merging new modules - keys:`, Object.keys(currentTarget[finalKey] || {}));
492
+ }
493
+
494
+
495
+ if (rootLevelFileContent !== null) {
496
+ if (instance.config.debug) {
497
+ console.log(`[DEBUG] addApi: Merging root-level file content into API path "${normalizedApiPath}"`);
498
+ console.log(`[DEBUG] addApi: Root-level file functions:`, Object.keys(rootLevelFileContent));
499
+ console.log(`[DEBUG] addApi: Target before root-level merge:`, Object.keys(currentTarget[finalKey]));
500
+ }
501
+
502
+
503
+ if (rootLevelFileContent && typeof rootLevelFileContent === "object") {
504
+ Object.assign(currentTarget[finalKey], rootLevelFileContent);
505
+ Object.assign(currentBoundTarget[finalKey], rootLevelFileContent);
506
+ } else if (typeof rootLevelFileContent === "function") {
507
+
508
+ Object.assign(currentTarget[finalKey], rootLevelFileContent);
509
+ Object.assign(currentBoundTarget[finalKey], rootLevelFileContent);
510
+ }
511
+
512
+ if (instance.config.debug) {
513
+ console.log(`[DEBUG] addApi: After merging root-level file, final API structure:`, Object.keys(currentTarget[finalKey]));
514
+ }
515
+ }
217
516
  } else if (newModules === null || newModules === undefined) {
218
517
 
219
518
  const receivedType = newModules === null ? "null" : "undefined";
@@ -229,7 +528,24 @@ export async function addApiFromFolder({ apiPath, folderPath, instance }) {
229
528
  }
230
529
 
231
530
 
531
+ if (instance.config.debug) {
532
+ console.log(`[DEBUG] addApi: Before updateBindings - currentTarget[${finalKey}]:`, Object.keys(currentTarget[finalKey] || {}));
533
+ console.log(
534
+ `[DEBUG] addApi: Before updateBindings - currentBoundTarget[${finalKey}]:`,
535
+ Object.keys(currentBoundTarget[finalKey] || {})
536
+ );
537
+ }
538
+
539
+
540
+ if (instance.config.enableModuleOwnership && moduleId) {
541
+ const fullApiPath = normalizedApiPath + (normalizedApiPath ? "." : "") + finalKey;
542
+ instance._registerApiOwnership(fullApiPath, moduleId);
543
+ }
544
+
232
545
  instance.updateBindings(instance.context, instance.reference, instance.boundapi);
546
+ if (instance.config.debug) {
547
+ console.log(`[DEBUG] addApi: After updateBindings - api[${finalKey}]:`, Object.keys(instance.api[finalKey] || {}));
548
+ }
233
549
 
234
550
  if (instance.config.debug) {
235
551
  console.log(`[DEBUG] addApi: Successfully added modules at ${normalizedApiPath}`);