@cldmv/slothlet 2.7.1 → 2.9.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 +1 -1
- package/README.md +253 -1475
- package/dist/lib/helpers/als-eventemitter.mjs +4 -5
- package/dist/lib/helpers/api_builder/add_api.mjs +237 -0
- package/dist/lib/helpers/api_builder/analysis.mjs +522 -0
- package/dist/lib/helpers/api_builder/construction.mjs +457 -0
- package/dist/lib/helpers/api_builder/decisions.mjs +737 -0
- package/dist/lib/helpers/api_builder.mjs +16 -1567
- package/dist/lib/helpers/utilities.mjs +121 -0
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +44 -17
- package/dist/lib/runtime/runtime-livebindings.mjs +18 -3
- package/dist/lib/runtime/runtime.mjs +3 -3
- package/dist/slothlet.mjs +197 -547
- package/docs/API-RULES-CONDITIONS.md +508 -0
- package/{API-RULES.md → docs/API-RULES.md} +127 -72
- package/index.cjs +2 -1
- package/index.mjs +2 -1
- package/package.json +11 -9
- package/types/dist/lib/helpers/als-eventemitter.d.mts.map +1 -1
- package/types/dist/lib/helpers/api_builder/add_api.d.mts +60 -0
- package/types/dist/lib/helpers/api_builder/add_api.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder/analysis.d.mts +189 -0
- package/types/dist/lib/helpers/api_builder/analysis.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder/construction.d.mts +107 -0
- package/types/dist/lib/helpers/api_builder/construction.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder/decisions.d.mts +213 -0
- package/types/dist/lib/helpers/api_builder/decisions.d.mts.map +1 -0
- package/types/dist/lib/helpers/api_builder.d.mts +5 -448
- package/types/dist/lib/helpers/api_builder.d.mts.map +1 -1
- package/types/dist/lib/helpers/utilities.d.mts +120 -0
- package/types/dist/lib/helpers/utilities.d.mts.map +1 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +7 -0
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +8 -0
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +23 -13
- package/types/dist/slothlet.d.mts.map +1 -1
- package/types/index.d.mts +0 -1
- package/API-RULES-CONDITIONS.md +0 -367
|
@@ -18,9 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
import { AsyncResource } from "node:async_hooks";
|
|
21
|
+
import { AsyncResource, AsyncLocalStorage } from "node:async_hooks";
|
|
22
22
|
import { EventEmitter } from "node:events";
|
|
23
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
const defaultALS = new AsyncLocalStorage();
|
|
@@ -151,7 +150,7 @@ export function enableAlsForEventEmitters(als = defaultALS) {
|
|
|
151
150
|
globalResourceSet.delete(resource);
|
|
152
151
|
try {
|
|
153
152
|
resource.emitDestroy();
|
|
154
|
-
} catch (
|
|
153
|
+
} catch (_) {
|
|
155
154
|
|
|
156
155
|
}
|
|
157
156
|
}
|
|
@@ -204,7 +203,7 @@ export function cleanupAllSlothletListeners() {
|
|
|
204
203
|
emitter.removeListener(event, wrappedListener);
|
|
205
204
|
cleanedCount++;
|
|
206
205
|
}
|
|
207
|
-
} catch (
|
|
206
|
+
} catch (_) {
|
|
208
207
|
errorCount++;
|
|
209
208
|
|
|
210
209
|
}
|
|
@@ -214,7 +213,7 @@ export function cleanupAllSlothletListeners() {
|
|
|
214
213
|
allPatchedListeners.clear();
|
|
215
214
|
|
|
216
215
|
|
|
217
|
-
if (process.env.
|
|
216
|
+
if (process.env.SLOTHLET_DEBUG === "1" || process.env.SLOTHLET_DEBUG === "true") {
|
|
218
217
|
console.log(`[slothlet] Cleaned up ${cleanedCount} listeners (${errorCount} errors)`);
|
|
219
218
|
}
|
|
220
219
|
}
|
|
@@ -0,0 +1,237 @@
|
|
|
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
|
+
|
|
21
|
+
import fs from "node:fs/promises";
|
|
22
|
+
import path from "node:path";
|
|
23
|
+
import { resolvePathFromCaller } from "@cldmv/slothlet/helpers/resolve-from-caller";
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
export async function addApiFromFolder({ apiPath, folderPath, instance }) {
|
|
27
|
+
if (!instance.loaded) {
|
|
28
|
+
throw new Error("[slothlet] Cannot add API: API not loaded. Call create() or load() first.");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
if (typeof apiPath !== "string") {
|
|
33
|
+
throw new TypeError("[slothlet] addApi: 'apiPath' must be a string.");
|
|
34
|
+
}
|
|
35
|
+
const normalizedApiPath = apiPath.trim();
|
|
36
|
+
if (normalizedApiPath === "") {
|
|
37
|
+
throw new TypeError("[slothlet] addApi: 'apiPath' must be a non-empty, non-whitespace string.");
|
|
38
|
+
}
|
|
39
|
+
const pathParts = normalizedApiPath.split(".");
|
|
40
|
+
if (pathParts.some((part) => part === "")) {
|
|
41
|
+
throw new Error(`[slothlet] addApi: 'apiPath' must not contain empty segments. Received: "${normalizedApiPath}"`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if (typeof folderPath !== "string") {
|
|
46
|
+
throw new TypeError("[slothlet] addApi: 'folderPath' must be a string.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
let resolvedFolderPath = folderPath;
|
|
51
|
+
if (!path.isAbsolute(folderPath)) {
|
|
52
|
+
resolvedFolderPath = resolvePathFromCaller(folderPath);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
let stats;
|
|
57
|
+
try {
|
|
58
|
+
stats = await fs.stat(resolvedFolderPath);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw new Error(`[slothlet] addApi: Cannot access folder: ${resolvedFolderPath} - ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
if (!stats.isDirectory()) {
|
|
63
|
+
throw new Error(`[slothlet] addApi: Path is not a directory: ${resolvedFolderPath}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (instance.config.debug) {
|
|
67
|
+
console.log(`[DEBUG] addApi: Loading modules from ${resolvedFolderPath} to path: ${normalizedApiPath}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
let newModules;
|
|
72
|
+
if (instance.config.lazy) {
|
|
73
|
+
|
|
74
|
+
newModules = await instance.modes.lazy.create.call(instance, resolvedFolderPath, instance.config.apiDepth || Infinity, 0);
|
|
75
|
+
} else {
|
|
76
|
+
|
|
77
|
+
newModules = await instance.modes.eager.create.call(instance, resolvedFolderPath, instance.config.apiDepth || Infinity, 0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (instance.config.debug) {
|
|
81
|
+
if (newModules && typeof newModules === "object") {
|
|
82
|
+
console.log(`[DEBUG] addApi: Loaded modules:`, Object.keys(newModules));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(
|
|
85
|
+
`[DEBUG] addApi: Loaded modules (non-object):`,
|
|
86
|
+
typeof newModules === "function" ? `[Function: ${newModules.name || "anonymous"}]` : newModules
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
let currentTarget = instance.api;
|
|
93
|
+
let currentBoundTarget = instance.boundapi;
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
96
|
+
const part = pathParts[i];
|
|
97
|
+
const key = instance._toapiPathKey(part);
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if (Object.prototype.hasOwnProperty.call(currentTarget, key)) {
|
|
103
|
+
const existing = currentTarget[key];
|
|
104
|
+
if (existing === null || (typeof existing !== "object" && typeof existing !== "function")) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`[slothlet] Cannot extend API path "${normalizedApiPath}" through segment "${part}": ` +
|
|
107
|
+
`existing value is type "${typeof existing}", cannot add properties.`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
} else {
|
|
113
|
+
currentTarget[key] = {};
|
|
114
|
+
}
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(currentBoundTarget, key)) {
|
|
116
|
+
const existingBound = currentBoundTarget[key];
|
|
117
|
+
if (existingBound === null || (typeof existingBound !== "object" && typeof existingBound !== "function")) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`[slothlet] Cannot extend bound API path "${normalizedApiPath}" through segment "${part}": ` +
|
|
120
|
+
`existing value is type "${typeof existingBound}", cannot add properties.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
} else {
|
|
125
|
+
currentBoundTarget[key] = {};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
currentTarget = currentTarget[key];
|
|
130
|
+
currentBoundTarget = currentBoundTarget[key];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
const finalKey = instance._toapiPathKey(pathParts[pathParts.length - 1]);
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if (typeof newModules === "function") {
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
if (Object.prototype.hasOwnProperty.call(currentTarget, finalKey)) {
|
|
141
|
+
const existing = currentTarget[finalKey];
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if (instance.config.allowApiOverwrite === false) {
|
|
145
|
+
console.warn(
|
|
146
|
+
`[slothlet] Skipping addApi: API path "${normalizedApiPath}" final key "${finalKey}" ` +
|
|
147
|
+
`already exists (type: "${typeof existing}"). Set allowApiOverwrite: true to allow overwrites.`
|
|
148
|
+
);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if (existing !== null && typeof existing !== "function") {
|
|
154
|
+
console.warn(
|
|
155
|
+
`[slothlet] Overwriting existing non-function value at API path "${normalizedApiPath}" ` +
|
|
156
|
+
`final key "${finalKey}" with a function. Previous type: "${typeof existing}".`
|
|
157
|
+
);
|
|
158
|
+
} else if (typeof existing === "function") {
|
|
159
|
+
|
|
160
|
+
console.warn(
|
|
161
|
+
`[slothlet] Overwriting existing function at API path "${normalizedApiPath}" ` + `final key "${finalKey}" with a new function.`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
currentTarget[finalKey] = newModules;
|
|
166
|
+
currentBoundTarget[finalKey] = newModules;
|
|
167
|
+
} else if (typeof newModules === "object" && newModules !== null) {
|
|
168
|
+
|
|
169
|
+
if (Object.prototype.hasOwnProperty.call(currentTarget, finalKey)) {
|
|
170
|
+
const existing = currentTarget[finalKey];
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if (instance.config.allowApiOverwrite === false && existing !== undefined && existing !== null) {
|
|
174
|
+
|
|
175
|
+
const hasContent = typeof existing === "object" ? Object.keys(existing).length > 0 : true;
|
|
176
|
+
if (hasContent) {
|
|
177
|
+
console.warn(
|
|
178
|
+
`[slothlet] Skipping addApi merge: API path "${normalizedApiPath}" final key "${finalKey}" ` +
|
|
179
|
+
`already exists with content (type: "${typeof existing}"). Set allowApiOverwrite: true to allow merging.`
|
|
180
|
+
);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (existing !== null && typeof existing !== "object" && typeof existing !== "function") {
|
|
186
|
+
throw new Error(
|
|
187
|
+
`[slothlet] Cannot merge API at "${normalizedApiPath}": ` +
|
|
188
|
+
`existing value at final key "${finalKey}" is type "${typeof existing}", cannot merge into primitives.`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (Object.prototype.hasOwnProperty.call(currentBoundTarget, finalKey)) {
|
|
193
|
+
const existingBound = currentBoundTarget[finalKey];
|
|
194
|
+
if (existingBound !== null && typeof existingBound !== "object" && typeof existingBound !== "function") {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`[slothlet] Cannot merge bound API at "${normalizedApiPath}": ` +
|
|
197
|
+
`existing value at final key "${finalKey}" is type "${typeof existingBound}", cannot merge into primitives.`
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if (!currentTarget[finalKey]) {
|
|
204
|
+
currentTarget[finalKey] = {};
|
|
205
|
+
}
|
|
206
|
+
if (!currentBoundTarget[finalKey]) {
|
|
207
|
+
currentBoundTarget[finalKey] = {};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
Object.assign(currentTarget[finalKey], newModules);
|
|
216
|
+
Object.assign(currentBoundTarget[finalKey], newModules);
|
|
217
|
+
} else if (newModules === null || newModules === undefined) {
|
|
218
|
+
|
|
219
|
+
const receivedType = newModules === null ? "null" : "undefined";
|
|
220
|
+
console.warn(
|
|
221
|
+
`[slothlet] addApi: No modules loaded from folder at API path "${normalizedApiPath}". ` +
|
|
222
|
+
`Loaded modules resulted in ${receivedType}. Check that the folder contains valid module files.`
|
|
223
|
+
);
|
|
224
|
+
} else {
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
currentTarget[finalKey] = newModules;
|
|
228
|
+
currentBoundTarget[finalKey] = newModules;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
instance.updateBindings(instance.context, instance.reference, instance.boundapi);
|
|
233
|
+
|
|
234
|
+
if (instance.config.debug) {
|
|
235
|
+
console.log(`[DEBUG] addApi: Successfully added modules at ${normalizedApiPath}`);
|
|
236
|
+
}
|
|
237
|
+
}
|