@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.
- package/AGENT-USAGE.md +41 -0
- package/README.md +95 -20
- package/dist/lib/engine/slothlet_child.mjs +1 -1
- package/dist/lib/engine/slothlet_engine.mjs +1 -1
- package/dist/lib/engine/slothlet_esm.mjs +1 -1
- package/dist/lib/engine/slothlet_helpers.mjs +1 -1
- package/dist/lib/engine/slothlet_worker.mjs +1 -1
- package/dist/lib/helpers/als-eventemitter.mjs +1 -1
- package/dist/lib/helpers/api_builder/add_api.mjs +321 -5
- package/dist/lib/helpers/api_builder/analysis.mjs +13 -3
- package/dist/lib/helpers/api_builder/construction.mjs +41 -3
- package/dist/lib/helpers/api_builder/decisions.mjs +16 -5
- package/dist/lib/helpers/api_builder/metadata.mjs +248 -0
- package/dist/lib/helpers/api_builder.mjs +1 -1
- package/dist/lib/helpers/auto-wrap.mjs +1 -1
- package/dist/lib/helpers/hooks.mjs +1 -1
- package/dist/lib/helpers/instance-manager.mjs +1 -1
- package/dist/lib/helpers/metadata-api.mjs +201 -0
- package/dist/lib/helpers/multidefault.mjs +12 -3
- package/dist/lib/helpers/resolve-from-caller.mjs +48 -11
- package/dist/lib/helpers/sanitize.mjs +1 -1
- package/dist/lib/helpers/utilities.mjs +1 -1
- package/dist/lib/modes/slothlet_eager.mjs +30 -2
- package/dist/lib/modes/slothlet_lazy.mjs +95 -5
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +5 -1
- package/dist/lib/runtime/runtime-livebindings.mjs +5 -1
- package/dist/lib/runtime/runtime.mjs +12 -1
- package/dist/slothlet.mjs +70 -6
- package/docs/API-RULES-CONDITIONS.md +511 -307
- package/docs/API-RULES.md +617 -589
- package/package.json +2 -2
- package/types/dist/lib/helpers/api_builder/add_api.d.mts +64 -22
- package/types/dist/lib/helpers/api_builder/add_api.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder/analysis.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder/construction.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder/decisions.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder/metadata.d.mts +99 -0
- package/types/dist/lib/helpers/api_builder/metadata.d.mts.map +1 -0
- package/types/dist/lib/helpers/metadata-api.d.mts +132 -0
- package/types/dist/lib/helpers/metadata-api.d.mts.map +1 -0
- package/types/dist/lib/helpers/multidefault.d.mts.map +1 -1
- package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
- package/types/dist/lib/modes/slothlet_eager.d.mts.map +1 -1
- package/types/dist/lib/modes/slothlet_lazy.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +2 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +2 -0
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime.d.mts +1 -0
- package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +8 -0
- package/types/dist/slothlet.d.mts.map +1 -1
- 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.
|
|
38
|
+
### Latest: v2.11.0 (January 2025)
|
|
39
39
|
|
|
40
|
-
- **
|
|
41
|
-
-
|
|
42
|
-
-
|
|
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.
|
|
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
|
|
261
|
-
|
|
|
262
|
-
| `dir`
|
|
263
|
-
| `mode`
|
|
264
|
-
| `lazy`
|
|
265
|
-
| `engine`
|
|
266
|
-
| `runtime`
|
|
267
|
-
| `apiDepth`
|
|
268
|
-
| `debug`
|
|
269
|
-
| `api_mode`
|
|
270
|
-
| `allowApiOverwrite`
|
|
271
|
-
| `
|
|
272
|
-
| `
|
|
273
|
-
| `
|
|
274
|
-
| `
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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}`);
|