@cldmv/slothlet 3.3.0 → 3.3.2
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 +6 -8
- package/REFERENCE.md +23 -0
- package/dist/lib/builders/api-assignment.mjs +1 -589
- package/dist/lib/builders/api_builder.mjs +1 -1385
- package/dist/lib/builders/builder.mjs +1 -78
- package/dist/lib/builders/modes-processor.mjs +1 -1800
- package/dist/lib/errors.mjs +9 -211
- package/dist/lib/factories/component-base.mjs +1 -80
- package/dist/lib/factories/context.mjs +1 -22
- package/dist/lib/handlers/api-cache-manager.mjs +1 -200
- package/dist/lib/handlers/api-manager.mjs +1 -2536
- package/dist/lib/handlers/context-async.mjs +1 -172
- package/dist/lib/handlers/context-live.mjs +1 -173
- package/dist/lib/handlers/hook-manager.mjs +1 -667
- package/dist/lib/handlers/lifecycle-token.mjs +1 -28
- package/dist/lib/handlers/lifecycle.mjs +1 -115
- package/dist/lib/handlers/materialize-manager.mjs +1 -48
- package/dist/lib/handlers/metadata.mjs +1 -501
- package/dist/lib/handlers/ownership.mjs +1 -322
- package/dist/lib/handlers/permission-manager.mjs +1 -392
- package/dist/lib/handlers/unified-wrapper.mjs +1 -3110
- package/dist/lib/handlers/version-manager.mjs +1 -885
- package/dist/lib/helpers/class-instance-wrapper.mjs +1 -109
- package/dist/lib/helpers/config.mjs +1 -439
- package/dist/lib/helpers/eventemitter-context.mjs +1 -349
- package/dist/lib/helpers/hint-detector.mjs +1 -47
- package/dist/lib/helpers/modes-utils.mjs +1 -37
- package/dist/lib/helpers/pattern-matcher.mjs +1 -125
- package/dist/lib/helpers/resolve-from-caller.mjs +1 -169
- package/dist/lib/helpers/sanitize.mjs +1 -340
- package/dist/lib/helpers/utilities.mjs +1 -70
- package/dist/lib/i18n/translations.mjs +1 -126
- package/dist/lib/modes/eager.mjs +1 -59
- package/dist/lib/modes/lazy.mjs +1 -81
- package/dist/lib/processors/flatten.mjs +1 -437
- package/dist/lib/processors/loader.mjs +1 -339
- package/dist/lib/processors/type-generator.mjs +1 -275
- package/dist/lib/processors/typescript.mjs +1 -172
- package/dist/lib/runtime/runtime-asynclocalstorage.mjs +1 -113
- package/dist/lib/runtime/runtime-livebindings.mjs +1 -78
- package/dist/lib/runtime/runtime.mjs +1 -102
- package/dist/slothlet.mjs +1 -817
- package/package.json +34 -31
- package/types/dist/lib/builders/api-assignment.d.mts +3 -92
- package/types/dist/lib/builders/api-assignment.d.mts.map +1 -1
- package/types/dist/lib/builders/api_builder.d.mts +102 -91
- package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
- package/types/dist/lib/builders/builder.d.mts +1 -55
- package/types/dist/lib/builders/builder.d.mts.map +1 -1
- package/types/dist/lib/builders/modes-processor.d.mts +3 -27
- package/types/dist/lib/builders/modes-processor.d.mts.map +1 -1
- package/types/dist/lib/errors.d.mts +19 -109
- package/types/dist/lib/errors.d.mts.map +1 -1
- package/types/dist/lib/factories/component-base.d.mts +7 -177
- package/types/dist/lib/factories/component-base.d.mts.map +1 -1
- package/types/dist/lib/factories/context.d.mts +4 -22
- package/types/dist/lib/factories/context.d.mts.map +1 -1
- package/types/dist/lib/handlers/api-cache-manager.d.mts +20 -203
- package/types/dist/lib/handlers/api-cache-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/api-manager.d.mts +33 -408
- package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/context-async.d.mts +23 -61
- package/types/dist/lib/handlers/context-async.d.mts.map +1 -1
- package/types/dist/lib/handlers/context-live.d.mts +22 -59
- package/types/dist/lib/handlers/context-live.d.mts.map +1 -1
- package/types/dist/lib/handlers/hook-manager.d.mts +46 -185
- package/types/dist/lib/handlers/hook-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/lifecycle-token.d.mts +3 -48
- package/types/dist/lib/handlers/lifecycle-token.d.mts.map +1 -1
- package/types/dist/lib/handlers/lifecycle.d.mts +5 -82
- package/types/dist/lib/handlers/lifecycle.d.mts.map +1 -1
- package/types/dist/lib/handlers/materialize-manager.d.mts +8 -70
- package/types/dist/lib/handlers/materialize-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/metadata.d.mts +17 -221
- package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
- package/types/dist/lib/handlers/ownership.d.mts +44 -160
- package/types/dist/lib/handlers/ownership.d.mts.map +1 -1
- package/types/dist/lib/handlers/permission-manager.d.mts +37 -141
- package/types/dist/lib/handlers/permission-manager.d.mts.map +1 -1
- package/types/dist/lib/handlers/unified-wrapper.d.mts +26 -239
- package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
- package/types/dist/lib/handlers/version-manager.d.mts +28 -225
- package/types/dist/lib/handlers/version-manager.d.mts.map +1 -1
- package/types/dist/lib/helpers/class-instance-wrapper.d.mts +2 -52
- package/types/dist/lib/helpers/class-instance-wrapper.d.mts.map +1 -1
- package/types/dist/lib/helpers/config.d.mts +125 -139
- package/types/dist/lib/helpers/config.d.mts.map +1 -1
- package/types/dist/lib/helpers/eventemitter-context.d.mts +3 -29
- package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -1
- package/types/dist/lib/helpers/hint-detector.d.mts +2 -15
- package/types/dist/lib/helpers/hint-detector.d.mts.map +1 -1
- package/types/dist/lib/helpers/modes-utils.d.mts +3 -30
- package/types/dist/lib/helpers/modes-utils.d.mts.map +1 -1
- package/types/dist/lib/helpers/pattern-matcher.d.mts +3 -43
- package/types/dist/lib/helpers/pattern-matcher.d.mts.map +1 -1
- package/types/dist/lib/helpers/resolve-from-caller.d.mts +3 -27
- package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
- package/types/dist/lib/helpers/sanitize.d.mts +4 -92
- package/types/dist/lib/helpers/sanitize.d.mts.map +1 -1
- package/types/dist/lib/helpers/utilities.d.mts +4 -52
- package/types/dist/lib/helpers/utilities.d.mts.map +1 -1
- package/types/dist/lib/i18n/translations.d.mts +4 -37
- package/types/dist/lib/i18n/translations.d.mts.map +1 -1
- package/types/dist/lib/modes/eager.d.mts +8 -30
- package/types/dist/lib/modes/eager.d.mts.map +1 -1
- package/types/dist/lib/modes/lazy.d.mts +10 -43
- package/types/dist/lib/modes/lazy.d.mts.map +1 -1
- package/types/dist/lib/processors/flatten.d.mts +56 -107
- package/types/dist/lib/processors/flatten.d.mts.map +1 -1
- package/types/dist/lib/processors/loader.d.mts +6 -41
- package/types/dist/lib/processors/loader.d.mts.map +1 -1
- package/types/dist/lib/processors/type-generator.d.mts +2 -16
- package/types/dist/lib/processors/type-generator.d.mts.map +1 -1
- package/types/dist/lib/processors/typescript.d.mts +6 -53
- package/types/dist/lib/processors/typescript.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +3 -71
- package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime-livebindings.d.mts +2 -37
- package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
- package/types/dist/lib/runtime/runtime.d.mts +3 -39
- package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
- package/types/dist/slothlet.d.mts +3 -249
- package/types/dist/slothlet.d.mts.map +1 -1
- package/types/index.d.mts +36 -16
- package/types/index.d.mts.map +1 -0
- package/AGENT-USAGE.md +0 -736
- package/docs/API-RULES.md +0 -712
|
@@ -14,2539 +14,4 @@
|
|
|
14
14
|
limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
import fs from "node:fs/promises";
|
|
21
|
-
import path from "node:path";
|
|
22
|
-
import { translate } from "@cldmv/slothlet/i18n";
|
|
23
|
-
import { ComponentBase } from "@cldmv/slothlet/factories/component-base";
|
|
24
|
-
import { UnifiedWrapper, resolveWrapper } from "@cldmv/slothlet/handlers/unified-wrapper";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export class ApiManager extends ComponentBase {
|
|
28
|
-
static slothletProperty = "apiManager";
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
constructor(slothlet) {
|
|
32
|
-
super(slothlet);
|
|
33
|
-
this.state = {
|
|
34
|
-
addHistory: [],
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
initialConfig: slothlet?.config || null,
|
|
38
|
-
operationHistory: []
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
normalizeApiPath(apiPath) {
|
|
44
|
-
|
|
45
|
-
if (apiPath === "" || apiPath === null || apiPath === undefined) {
|
|
46
|
-
return { apiPath: "", parts: [] };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (Array.isArray(apiPath)) {
|
|
51
|
-
|
|
52
|
-
if (apiPath.length === 0) {
|
|
53
|
-
return { apiPath: "", parts: [] };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
for (let i = 0; i < apiPath.length; i++) {
|
|
57
|
-
if (typeof apiPath[i] !== "string") {
|
|
58
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
59
|
-
apiPath,
|
|
60
|
-
segment: apiPath[i],
|
|
61
|
-
index: i,
|
|
62
|
-
reason: translate("API_PATH_REASON_ARRAY_ELEMENTS"),
|
|
63
|
-
validationError: true
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
if (apiPath[i].trim() === "") {
|
|
67
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
68
|
-
apiPath,
|
|
69
|
-
segment: apiPath[i],
|
|
70
|
-
index: i,
|
|
71
|
-
reason: translate("API_PATH_REASON_ARRAY_EMPTY_SEGMENTS"),
|
|
72
|
-
validationError: true
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (apiPath[0] === "slothlet" || (apiPath.length === 1 && (apiPath[0] === "shutdown" || apiPath[0] === "destroy"))) {
|
|
79
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
80
|
-
apiPath,
|
|
81
|
-
reason: translate("API_PATH_REASON_RESERVED_NAME"),
|
|
82
|
-
index: undefined,
|
|
83
|
-
segment: undefined,
|
|
84
|
-
validationError: true
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return { apiPath: apiPath.join("."), parts: apiPath };
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (typeof apiPath !== "string") {
|
|
92
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
93
|
-
apiPath,
|
|
94
|
-
reason: translate("API_PATH_REASON_INVALID_TYPE"),
|
|
95
|
-
index: undefined,
|
|
96
|
-
segment: undefined,
|
|
97
|
-
validationError: true
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const normalized = apiPath.trim();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (normalized === "") {
|
|
105
|
-
return { apiPath: "", parts: [] };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const parts = normalized.split(".");
|
|
109
|
-
if (parts.length === 0 || parts.some((part) => part.trim() === "")) {
|
|
110
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
111
|
-
apiPath: normalized,
|
|
112
|
-
reason: translate("API_PATH_REASON_EMPTY_SEGMENTS"),
|
|
113
|
-
index: undefined,
|
|
114
|
-
segment: undefined,
|
|
115
|
-
validationError: true
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (parts[0] === "slothlet" || normalized === "shutdown" || normalized === "destroy") {
|
|
120
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
121
|
-
apiPath: normalized,
|
|
122
|
-
reason: translate("API_PATH_REASON_RESERVED_NAME"),
|
|
123
|
-
index: undefined,
|
|
124
|
-
segment: undefined,
|
|
125
|
-
validationError: true
|
|
126
|
-
});
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return { apiPath: normalized, parts };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
async resolvePath(inputPath) {
|
|
134
|
-
if (!inputPath || typeof inputPath !== "string") {
|
|
135
|
-
throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID", {
|
|
136
|
-
dir: inputPath,
|
|
137
|
-
validationError: true
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const resolvedPath = this.slothlet.helpers.resolver.resolvePathFromCaller(inputPath);
|
|
142
|
-
try {
|
|
143
|
-
const stats = await fs.stat(resolvedPath);
|
|
144
|
-
return {
|
|
145
|
-
resolvedPath,
|
|
146
|
-
isDirectory: stats.isDirectory(),
|
|
147
|
-
isFile: stats.isFile()
|
|
148
|
-
};
|
|
149
|
-
} catch (error) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (error instanceof this.SlothletError) {
|
|
155
|
-
throw error;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID", {
|
|
160
|
-
dir: resolvedPath,
|
|
161
|
-
validationError: true
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
async resolveFolderPath(folderPath) {
|
|
168
|
-
if (!folderPath || typeof folderPath !== "string") {
|
|
169
|
-
throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID", {
|
|
170
|
-
dir: folderPath,
|
|
171
|
-
validationError: true
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const resolvedPath = this.slothlet.helpers.resolver.resolvePathFromCaller(folderPath);
|
|
176
|
-
try {
|
|
177
|
-
const stats = await fs.stat(resolvedPath);
|
|
178
|
-
if (!stats.isDirectory()) {
|
|
179
|
-
throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID", {
|
|
180
|
-
dir: resolvedPath,
|
|
181
|
-
validationError: true
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
} catch (error) {
|
|
185
|
-
if (error instanceof this.SlothletError) {
|
|
186
|
-
throw error;
|
|
187
|
-
}
|
|
188
|
-
throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID", {
|
|
189
|
-
dir: resolvedPath,
|
|
190
|
-
validationError: true
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return resolvedPath;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
buildDefaultModuleId(apiPath, ____resolvedFolderPath) {
|
|
199
|
-
const randomSuffix = Math.random().toString(36).substring(2, 8);
|
|
200
|
-
const prefix = apiPath || "auto";
|
|
201
|
-
return `${prefix}_${randomSuffix}`;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
getValueAtPath(root, parts) {
|
|
206
|
-
let current = root;
|
|
207
|
-
for (const part of parts) {
|
|
208
|
-
if (!current || (typeof current !== "object" && typeof current !== "function")) {
|
|
209
|
-
return undefined;
|
|
210
|
-
}
|
|
211
|
-
current = current[part];
|
|
212
|
-
}
|
|
213
|
-
return current;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
ensureParentPath(root, parts, options = {}) {
|
|
218
|
-
const { moduleID, sourceFolder } = options;
|
|
219
|
-
let current = root;
|
|
220
|
-
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
221
|
-
const part = parts[i];
|
|
222
|
-
const next = current[part];
|
|
223
|
-
if (next === undefined) {
|
|
224
|
-
|
|
225
|
-
const containerPath = parts.slice(0, i + 1).join(".");
|
|
226
|
-
const containerWrapper = new UnifiedWrapper(this.slothlet, {
|
|
227
|
-
mode: this.____config.mode,
|
|
228
|
-
apiPath: containerPath,
|
|
229
|
-
moduleID: moduleID,
|
|
230
|
-
sourceFolder: sourceFolder
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
containerWrapper.___setImpl({}, moduleID);
|
|
234
|
-
current[part] = containerWrapper.createProxy();
|
|
235
|
-
current = current[part];
|
|
236
|
-
continue;
|
|
237
|
-
}
|
|
238
|
-
if (next && (typeof next === "object" || typeof next === "function")) {
|
|
239
|
-
current = next;
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
243
|
-
apiPath: parts.slice(0, i + 1).join("."),
|
|
244
|
-
reason: translate("API_PATH_REASON_NOT_TRAVERSABLE"),
|
|
245
|
-
index: undefined,
|
|
246
|
-
segment: undefined,
|
|
247
|
-
validationError: true
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
return current;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
isWrapperProxy(value) {
|
|
255
|
-
return !!(value && (typeof value === "object" || typeof value === "function") && resolveWrapper(value) !== null);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
async syncWrapper(existingProxy, nextProxy, config, collisionMode = "replace", moduleID = null) {
|
|
260
|
-
if (config?.debug?.api) {
|
|
261
|
-
this.slothlet.debug("api", {
|
|
262
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_ENTRY_EXISTING",
|
|
263
|
-
apiPath: resolveWrapper(existingProxy)?.apiPath
|
|
264
|
-
});
|
|
265
|
-
this.slothlet.debug("api", {
|
|
266
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_ENTRY_NEXT",
|
|
267
|
-
apiPath: resolveWrapper(nextProxy)?.apiPath
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
if (!this.isWrapperProxy(existingProxy) || !this.isWrapperProxy(nextProxy)) {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
const existingWrapper = resolveWrapper(existingProxy) ?? existingProxy;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const nextWrapper = resolveWrapper(nextProxy) ?? nextProxy;
|
|
283
|
-
|
|
284
|
-
if (config?.debug?.api) {
|
|
285
|
-
this.slothlet.debug("api", {
|
|
286
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_EXISTING",
|
|
287
|
-
apiPath: existingWrapper.apiPath
|
|
288
|
-
});
|
|
289
|
-
this.slothlet.debug("api", {
|
|
290
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_NEXT",
|
|
291
|
-
apiPath: nextWrapper.apiPath
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (nextWrapper.____slothletInternal.materializeFunc && collisionMode !== "merge") {
|
|
300
|
-
existingWrapper.____slothletInternal.materializeFunc = nextWrapper.____slothletInternal.materializeFunc;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const existingChildKeys = Object.keys(existingWrapper).filter((k) => !k.startsWith("_") && !k.startsWith("__"));
|
|
308
|
-
const nextChildKeys = Object.keys(nextWrapper).filter((k) => !k.startsWith("_") && !k.startsWith("__"));
|
|
309
|
-
|
|
310
|
-
if (config?.debug?.api) {
|
|
311
|
-
this.slothlet.debug("api", {
|
|
312
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_BEFORE_MERGE",
|
|
313
|
-
existingCacheSize: existingChildKeys.length,
|
|
314
|
-
nextCacheSize: nextChildKeys.length
|
|
315
|
-
});
|
|
316
|
-
this.slothlet.debug("api", {
|
|
317
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_NEXT_IMPL_KEYS",
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
implKeys: Object.keys(nextWrapper.____slothletInternal.impl || {})
|
|
321
|
-
});
|
|
322
|
-
this.slothlet.debug("api", {
|
|
323
|
-
key: "DEBUG_MODE_SYNC_WRAPPER_NEXT_CHILDCACHE_KEYS",
|
|
324
|
-
childCacheKeys: nextChildKeys
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (collisionMode === "replace") {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (existingWrapper.___setImpl && nextWrapper.____slothletInternal.impl !== undefined) {
|
|
338
|
-
|
|
339
|
-
existingWrapper.___setImpl(nextWrapper.____slothletInternal.impl, moduleID);
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
} else if (nextWrapper.____slothletInternal.impl === undefined) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
existingWrapper.____slothletInternal.impl = null;
|
|
349
|
-
} else {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (nextWrapper.____slothletInternal.impl !== undefined) {
|
|
355
|
-
existingWrapper.____slothletInternal.impl = nextWrapper.____slothletInternal.impl;
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
typeof nextWrapper.____slothletInternal.impl === "function" ||
|
|
359
|
-
(nextWrapper.____slothletInternal.impl && typeof nextWrapper.____slothletInternal.impl.default === "function")
|
|
360
|
-
) {
|
|
361
|
-
existingWrapper.isCallable = true;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for (const key of existingChildKeys) {
|
|
369
|
-
delete existingWrapper[key];
|
|
370
|
-
}
|
|
371
|
-
existingWrapper.___adoptImplChildren();
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
for (const key of nextChildKeys) {
|
|
376
|
-
const childValue = nextWrapper[key];
|
|
377
|
-
Object.defineProperty(existingWrapper, key, {
|
|
378
|
-
value: childValue,
|
|
379
|
-
writable: false,
|
|
380
|
-
enumerable: true,
|
|
381
|
-
configurable: true
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
} else if (collisionMode === "merge") {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
for (const key of nextChildKeys) {
|
|
390
|
-
const isInternal = typeof key === "string" && (key.startsWith("_") || key.startsWith("__"));
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
if (!isInternal && !Object.prototype.hasOwnProperty.call(existingWrapper, key)) {
|
|
394
|
-
const childValue = nextWrapper[key];
|
|
395
|
-
Object.defineProperty(existingWrapper, key, {
|
|
396
|
-
value: childValue,
|
|
397
|
-
writable: false,
|
|
398
|
-
enumerable: true,
|
|
399
|
-
configurable: true
|
|
400
|
-
});
|
|
401
|
-
} else if (!isInternal) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const existingChild = existingWrapper[key];
|
|
405
|
-
const nextChild = nextWrapper[key];
|
|
406
|
-
if (this.isWrapperProxy(existingChild) && this.isWrapperProxy(nextChild)) {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const syncWrapper_nextChildWrapper = resolveWrapper(nextChild) ?? nextChild;
|
|
410
|
-
const syncWrapper_hasGrandChildren = Object.keys(syncWrapper_nextChildWrapper).some(
|
|
411
|
-
(k) => !k.startsWith("_") && !k.startsWith("__")
|
|
412
|
-
);
|
|
413
|
-
if (syncWrapper_hasGrandChildren) {
|
|
414
|
-
await this.syncWrapper(existingChild, nextChild, config, collisionMode, moduleID);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
} else if (collisionMode === "merge-replace") {
|
|
423
|
-
|
|
424
|
-
for (const key of nextChildKeys) {
|
|
425
|
-
const childValue = nextWrapper[key];
|
|
426
|
-
const isInternal = typeof key === "string" && (key.startsWith("_") || key.startsWith("__"));
|
|
427
|
-
|
|
428
|
-
if (!isInternal && Object.prototype.hasOwnProperty.call(existingWrapper, key)) {
|
|
429
|
-
|
|
430
|
-
const existingChild = existingWrapper[key];
|
|
431
|
-
if (this.isWrapperProxy(existingChild) && this.isWrapperProxy(childValue)) {
|
|
432
|
-
await this.syncWrapper(existingChild, childValue, config, collisionMode, moduleID);
|
|
433
|
-
} else {
|
|
434
|
-
|
|
435
|
-
delete existingWrapper[key];
|
|
436
|
-
Object.defineProperty(existingWrapper, key, {
|
|
437
|
-
value: childValue,
|
|
438
|
-
writable: false,
|
|
439
|
-
enumerable: true,
|
|
440
|
-
configurable: true
|
|
441
|
-
});
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
} else if (!isInternal) {
|
|
446
|
-
|
|
447
|
-
Object.defineProperty(existingWrapper, key, {
|
|
448
|
-
value: childValue,
|
|
449
|
-
writable: false,
|
|
450
|
-
enumerable: true,
|
|
451
|
-
configurable: true
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (existingWrapper.____slothletInternal.state) {
|
|
462
|
-
|
|
463
|
-
const isActuallyMaterialized =
|
|
464
|
-
existingWrapper.____slothletInternal.impl && typeof existingWrapper.____slothletInternal.impl !== "function";
|
|
465
|
-
existingWrapper.____slothletInternal.state.materialized = isActuallyMaterialized;
|
|
466
|
-
existingWrapper.____slothletInternal.state.inFlight = false;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return true;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
async mutateApiValue(existingValue, nextValue, options, config) {
|
|
474
|
-
if (config?.debug?.api) {
|
|
475
|
-
this.slothlet.debug("api", {
|
|
476
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_CALLED",
|
|
477
|
-
existingType: typeof existingValue,
|
|
478
|
-
nextType: typeof nextValue
|
|
479
|
-
});
|
|
480
|
-
this.slothlet.debug("api", {
|
|
481
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_WRAPPER_STATUS",
|
|
482
|
-
existingIsWrapper: this.isWrapperProxy(existingValue),
|
|
483
|
-
nextIsWrapper: this.isWrapperProxy(nextValue)
|
|
484
|
-
});
|
|
485
|
-
this.slothlet.debug("api", {
|
|
486
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE",
|
|
487
|
-
nextValue
|
|
488
|
-
});
|
|
489
|
-
this.slothlet.debug("api", {
|
|
490
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE_KEYS",
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
nextValueKeys: nextValue ? Object.keys(nextValue) : []
|
|
494
|
-
});
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (existingValue === nextValue) {
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (this.isWrapperProxy(existingValue) && this.isWrapperProxy(nextValue)) {
|
|
503
|
-
if (config?.debug?.api) {
|
|
504
|
-
this.slothlet.debug("api", {
|
|
505
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_SYNC_WRAPPERS"
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
await this.syncWrapper(existingValue, nextValue, config, options.collisionMode, options.moduleID);
|
|
509
|
-
return;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
if (this.isWrapperProxy(existingValue) && !this.isWrapperProxy(nextValue)) {
|
|
515
|
-
|
|
516
|
-
const nextIsObjectLike = nextValue && (typeof nextValue === "object" || typeof nextValue === "function");
|
|
517
|
-
const nextHasKeys = nextIsObjectLike && Object.keys(nextValue).length > 0;
|
|
518
|
-
|
|
519
|
-
if (nextHasKeys) {
|
|
520
|
-
if (config?.debug?.api) {
|
|
521
|
-
this.slothlet.debug("api", {
|
|
522
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_MERGE_INTO_WRAPPER"
|
|
523
|
-
});
|
|
524
|
-
this.slothlet.debug("api", {
|
|
525
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_MERGE_KEYS",
|
|
526
|
-
keys: Object.keys(nextValue)
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue, nextValue, {
|
|
531
|
-
removeMissing: options.removeMissing,
|
|
532
|
-
mutateExisting: true,
|
|
533
|
-
allowOverwrite: true,
|
|
534
|
-
syncWrapper: this.syncWrapper.bind(this),
|
|
535
|
-
collisionMode: options.collisionMode,
|
|
536
|
-
moduleID: options.moduleID
|
|
537
|
-
});
|
|
538
|
-
return;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const existingValueRaw = resolveWrapper(existingValue);
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
if (existingValueRaw !== null) {
|
|
546
|
-
if (config?.debug?.api) {
|
|
547
|
-
this.slothlet.debug("api", {
|
|
548
|
-
key: "DEBUG_MODE_MUTATE_API_VALUE_SETIMPL_FALLBACK"
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
existingValueRaw.___setImpl(resolveWrapper(nextValue)?.__impl ?? nextValue);
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
if (existingValue && typeof existingValue === "object" && nextValue && typeof nextValue === "object") {
|
|
558
|
-
await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue, nextValue, {
|
|
559
|
-
removeMissing: options.removeMissing,
|
|
560
|
-
mutateExisting: true,
|
|
561
|
-
allowOverwrite: true,
|
|
562
|
-
syncWrapper: this.syncWrapper.bind(this),
|
|
563
|
-
collisionMode: options.collisionMode,
|
|
564
|
-
moduleID: options.moduleID
|
|
565
|
-
});
|
|
566
|
-
return existingValue;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
return nextValue;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
async setValueAtPath(root, parts, value, options) {
|
|
575
|
-
const parent = this.ensureParentPath(root, parts, {
|
|
576
|
-
moduleID: options.moduleID,
|
|
577
|
-
sourceFolder: options.sourceFolder
|
|
578
|
-
});
|
|
579
|
-
const finalKey = parts[parts.length - 1];
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
const existing = parent ? parent[finalKey] : undefined;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const collisionMode = options.collisionMode || "merge";
|
|
586
|
-
const moduleID = options.moduleID;
|
|
587
|
-
|
|
588
|
-
this.slothlet.debug("api", {
|
|
589
|
-
key: "DEBUG_MODE_SET_VALUE_AT_PATH",
|
|
590
|
-
finalKey,
|
|
591
|
-
existingType: typeof existing,
|
|
592
|
-
valueType: typeof value,
|
|
593
|
-
collisionMode,
|
|
594
|
-
options
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
if (existing !== undefined) {
|
|
599
|
-
if (collisionMode === "error") {
|
|
600
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
601
|
-
apiPath: parts.join("."),
|
|
602
|
-
reason: translate("API_PATH_REASON_COLLISION_ERROR"),
|
|
603
|
-
index: undefined,
|
|
604
|
-
segment: undefined,
|
|
605
|
-
validationError: true
|
|
606
|
-
});
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
if (collisionMode === "skip") {
|
|
610
|
-
this.slothlet.debug("api", {
|
|
611
|
-
key: "DEBUG_MODE_SET_VALUE_AT_PATH_SKIP_COLLISION",
|
|
612
|
-
path: parts.join("."),
|
|
613
|
-
mode: "skip"
|
|
614
|
-
});
|
|
615
|
-
return false;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
if (collisionMode === "warn") {
|
|
619
|
-
if (this.slothlet && !this.____config?.silent) {
|
|
620
|
-
new this.SlothletWarning("WARNING_HOT_RELOAD_PATH_COLLISION", {
|
|
621
|
-
apiPath: parts.join(".")
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
return false;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
if (collisionMode === "replace") {
|
|
628
|
-
const existingIsObject = typeof existing === "object" || typeof existing === "function";
|
|
629
|
-
const valueIsObject = typeof value === "object" || typeof value === "function";
|
|
630
|
-
|
|
631
|
-
if (existingIsObject && valueIsObject) {
|
|
632
|
-
this.slothlet.debug("api", {
|
|
633
|
-
key: "DEBUG_MODE_SET_VALUE_AT_PATH_REPLACE_MERGE",
|
|
634
|
-
path: parts.join("."),
|
|
635
|
-
mode: "replace"
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
await this.mutateApiValue(
|
|
639
|
-
existing,
|
|
640
|
-
value,
|
|
641
|
-
{ removeMissing: false, allowOverwrite: true, collisionMode: "replace", moduleID },
|
|
642
|
-
this.____config
|
|
643
|
-
);
|
|
644
|
-
return true;
|
|
645
|
-
} else {
|
|
646
|
-
|
|
647
|
-
parent[finalKey] = value;
|
|
648
|
-
return true;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (collisionMode === "merge" || collisionMode === "merge-replace") {
|
|
655
|
-
const existingIsObject = typeof existing === "object" || typeof existing === "function";
|
|
656
|
-
const valueIsObject = typeof value === "object" || typeof value === "function";
|
|
657
|
-
|
|
658
|
-
if (existingIsObject && valueIsObject) {
|
|
659
|
-
this.slothlet.debug("api", {
|
|
660
|
-
key: "DEBUG_MODE_SET_VALUE_AT_PATH_MERGE_PROPS",
|
|
661
|
-
mode: collisionMode
|
|
662
|
-
});
|
|
663
|
-
await this.mutateApiValue(existing, value, { removeMissing: false, allowOverwrite: true, collisionMode }, this.____config);
|
|
664
|
-
return true;
|
|
665
|
-
} else {
|
|
666
|
-
|
|
667
|
-
if (this.slothlet && !this.____config?.silent) {
|
|
668
|
-
new this.SlothletWarning("WARNING_HOT_RELOAD_MERGE_PRIMITIVES", {
|
|
669
|
-
apiPath: parts.join(".")
|
|
670
|
-
});
|
|
671
|
-
}
|
|
672
|
-
return false;
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
this.slothlet.debug("api", {
|
|
679
|
-
key: "DEBUG_MODE_SET_VALUE_AT_PATH_ASSIGN",
|
|
680
|
-
finalKey
|
|
681
|
-
});
|
|
682
|
-
parent[finalKey] = value;
|
|
683
|
-
return true;
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
async deletePath(root, parts) {
|
|
688
|
-
let current = root;
|
|
689
|
-
const stack = [];
|
|
690
|
-
for (const part of parts.slice(0, -1)) {
|
|
691
|
-
if (!current || (typeof current !== "object" && typeof current !== "function")) {
|
|
692
|
-
return false;
|
|
693
|
-
}
|
|
694
|
-
stack.push({ parent: current, key: part });
|
|
695
|
-
current = current[part];
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
const finalKey = parts[parts.length - 1];
|
|
699
|
-
if (!current || (typeof current !== "object" && typeof current !== "function")) {
|
|
700
|
-
return false;
|
|
701
|
-
}
|
|
702
|
-
if (!Object.prototype.hasOwnProperty.call(current, finalKey)) {
|
|
703
|
-
return false;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const removedImpl = current[finalKey];
|
|
708
|
-
const apiPath = parts.join(".");
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
if (removedImpl && this.slothlet.handlers?.lifecycle) {
|
|
712
|
-
const metadata = this.slothlet.handlers.metadata?.getMetadata?.(removedImpl);
|
|
713
|
-
await this.slothlet.handlers.lifecycle.emit("impl:removed", {
|
|
714
|
-
apiPath,
|
|
715
|
-
impl: removedImpl,
|
|
716
|
-
source: "removal",
|
|
717
|
-
moduleID: metadata?.moduleID,
|
|
718
|
-
filePath: metadata?.filePath,
|
|
719
|
-
sourceFolder: metadata?.sourceFolder
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (resolveWrapper(current)) {
|
|
726
|
-
const wrapper = resolveWrapper(current);
|
|
727
|
-
|
|
728
|
-
const isInternal = typeof finalKey === "string" && (finalKey.startsWith("_") || finalKey.startsWith("__"));
|
|
729
|
-
if (!isInternal && finalKey in wrapper) {
|
|
730
|
-
delete wrapper[finalKey];
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if (wrapper.____slothletInternal.impl && typeof wrapper.____slothletInternal.impl === "object") {
|
|
734
|
-
delete wrapper.____slothletInternal.impl[finalKey];
|
|
735
|
-
}
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
delete current[finalKey];
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
if (removedImpl && (typeof removedImpl === "object" || typeof removedImpl === "function")) {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if (resolveWrapper(removedImpl)) {
|
|
749
|
-
const wrapper = resolveWrapper(removedImpl);
|
|
750
|
-
|
|
751
|
-
if (wrapper.____slothletInternal.impl !== undefined) {
|
|
752
|
-
wrapper.____slothletInternal.impl = null;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
const childKeys = Object.keys(wrapper).filter((k) => !k.startsWith("_") && !k.startsWith("__"));
|
|
756
|
-
for (const key of childKeys) {
|
|
757
|
-
delete wrapper[key];
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
if (wrapper.____slothletInternal.state) {
|
|
763
|
-
wrapper.____slothletInternal.state.materialized = false;
|
|
764
|
-
wrapper.____slothletInternal.state.inFlight = false;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
if (this.slothlet.handlers?.metadata) {
|
|
773
|
-
const rootSegment = apiPath.split(".")[0];
|
|
774
|
-
this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment);
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
for (let i = stack.length - 1; i >= 0; i -= 1) {
|
|
778
|
-
const { parent, key } = stack[i];
|
|
779
|
-
const value = parent[key];
|
|
780
|
-
if (value && (typeof value === "object" || typeof value === "function") && Object.keys(value).length === 0) {
|
|
781
|
-
delete parent[key];
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
return true;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
async restoreApiPath(apiPath, moduleID) {
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
const normalizedModuleId = moduleID || null;
|
|
792
|
-
const historyEntry = this.state.addHistory
|
|
793
|
-
.slice()
|
|
794
|
-
.reverse()
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
.find((entry) => entry.apiPath === apiPath && (normalizedModuleId ? entry.moduleID === normalizedModuleId : true));
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
if (historyEntry) {
|
|
803
|
-
await this.addApiComponent({
|
|
804
|
-
apiPath: historyEntry.apiPath,
|
|
805
|
-
folderPath: historyEntry.folderPath,
|
|
806
|
-
options: {
|
|
807
|
-
...historyEntry.options,
|
|
808
|
-
metadata: historyEntry.metadata,
|
|
809
|
-
mutateExisting: true,
|
|
810
|
-
forceOverwrite: true,
|
|
811
|
-
collisionMode: "replace",
|
|
812
|
-
recordHistory: false
|
|
813
|
-
}
|
|
814
|
-
});
|
|
815
|
-
return;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
if (normalizedModuleId === "base" || normalizedModuleId === "core") {
|
|
822
|
-
const baseApi = await this.slothlet.builders.builder.buildAPI({
|
|
823
|
-
dir: this.____config.dir,
|
|
824
|
-
mode: this.____config.mode,
|
|
825
|
-
moduleID: "base"
|
|
826
|
-
});
|
|
827
|
-
|
|
828
|
-
const { parts } = this.normalizeApiPath(apiPath);
|
|
829
|
-
let baseValue = this.getValueAtPath(baseApi, parts);
|
|
830
|
-
if (baseValue === undefined) {
|
|
831
|
-
await this.deletePath(this.slothlet.api, parts);
|
|
832
|
-
await this.deletePath(this.slothlet.boundApi, parts);
|
|
833
|
-
return;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
const baseValueRaw = resolveWrapper(baseValue);
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (baseValue && baseValueRaw !== null) {
|
|
844
|
-
baseValue = baseValueRaw.__impl;
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
await this.setValueAtPath(this.slothlet.api, parts, baseValue, {
|
|
848
|
-
mutateExisting: true,
|
|
849
|
-
allowOverwrite: true,
|
|
850
|
-
collisionMode: "replace",
|
|
851
|
-
moduleID: normalizedModuleId
|
|
852
|
-
});
|
|
853
|
-
await this.setValueAtPath(this.slothlet.boundApi, parts, baseValue, {
|
|
854
|
-
mutateExisting: true,
|
|
855
|
-
allowOverwrite: true,
|
|
856
|
-
collisionMode: "replace",
|
|
857
|
-
moduleID: normalizedModuleId
|
|
858
|
-
});
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
async addApiComponent(params) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const { apiPath, folderPath, options = {}, versionConfig = null } = params || {};
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (Array.isArray(folderPath)) {
|
|
870
|
-
const moduleIDs = [];
|
|
871
|
-
for (const singlePath of folderPath) {
|
|
872
|
-
const moduleID = await this.addApiComponent({
|
|
873
|
-
apiPath,
|
|
874
|
-
folderPath: singlePath,
|
|
875
|
-
options,
|
|
876
|
-
versionConfig
|
|
877
|
-
});
|
|
878
|
-
moduleIDs.push(moduleID);
|
|
879
|
-
}
|
|
880
|
-
return moduleIDs;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
const { metadata = {}, ...restOptions } = options;
|
|
884
|
-
if (!this.slothlet || !this.slothlet.isLoaded) {
|
|
885
|
-
throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED", {
|
|
886
|
-
operation: "addApi",
|
|
887
|
-
validationError: true
|
|
888
|
-
});
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const { apiPath: normalizedPath, parts } = this.normalizeApiPath(apiPath);
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
let effectivePath = normalizedPath;
|
|
895
|
-
let effectiveParts = parts;
|
|
896
|
-
if (versionConfig?.version !== undefined && versionConfig?.version !== null) {
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
if (normalizedPath === "") {
|
|
901
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
902
|
-
apiPath,
|
|
903
|
-
reason: translate("API_PATH_REASON_VERSIONED_ROOT"),
|
|
904
|
-
index: undefined,
|
|
905
|
-
segment: undefined,
|
|
906
|
-
validationError: true
|
|
907
|
-
});
|
|
908
|
-
}
|
|
909
|
-
if (typeof versionConfig.version !== "string" || !String(versionConfig.version).trim()) {
|
|
910
|
-
throw new this.SlothletError("INVALID_CONFIG_VERSION_TAG", {
|
|
911
|
-
received: versionConfig.version,
|
|
912
|
-
validationError: true
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
const versionTag = String(versionConfig.version).trim();
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
effectiveParts = [versionTag, ...parts];
|
|
921
|
-
effectivePath = effectiveParts.join(".");
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
const { resolvedPath, isDirectory, isFile } = await this.resolvePath(folderPath);
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
if (!isDirectory && !isFile) {
|
|
929
|
-
throw new this.SlothletError("INVALID_CONFIG_PATH_TYPE", {
|
|
930
|
-
path: resolvedPath,
|
|
931
|
-
validationError: true
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
if (isFile) {
|
|
936
|
-
|
|
937
|
-
const ext = path.extname(resolvedPath);
|
|
938
|
-
if (![".mjs", ".cjs", ".js"].includes(ext)) {
|
|
939
|
-
throw new this.SlothletError("INVALID_CONFIG_FILE_TYPE", {
|
|
940
|
-
path: resolvedPath,
|
|
941
|
-
extension: ext,
|
|
942
|
-
validationError: true
|
|
943
|
-
});
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
const resolvedFolderPath = resolvedPath;
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
let collisionMode;
|
|
954
|
-
if (restOptions.forceOverwrite) {
|
|
955
|
-
collisionMode = "replace";
|
|
956
|
-
} else {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
collisionMode = restOptions.collisionMode || this.____config.api?.collision?.api || "error";
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
const mutateExisting = !!(restOptions.mutateExisting || collisionMode === "merge");
|
|
963
|
-
|
|
964
|
-
const moduleID = restOptions.moduleID ? String(restOptions.moduleID) : this.buildDefaultModuleId(normalizedPath, resolvedFolderPath);
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
if (restOptions.forceOverwrite && !moduleID) {
|
|
970
|
-
throw new this.SlothletError("INVALID_CONFIG_FORCE_OVERWRITE_REQUIRES_MODULE_ID", {
|
|
971
|
-
apiPath: normalizedPath,
|
|
972
|
-
validationError: true
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
let dirForBuild = resolvedFolderPath;
|
|
980
|
-
let fileFilter = null;
|
|
981
|
-
|
|
982
|
-
if (isFile) {
|
|
983
|
-
dirForBuild = path.dirname(resolvedFolderPath);
|
|
984
|
-
const fileName = path.basename(resolvedFolderPath);
|
|
985
|
-
fileFilter = (file) => file === fileName;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
const newApi = await this.slothlet.builders.builder.buildAPI({
|
|
989
|
-
dir: dirForBuild,
|
|
990
|
-
mode: this.____config.mode,
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
apiPathPrefix: effectivePath,
|
|
996
|
-
collisionContext: "addApi",
|
|
997
|
-
moduleID: moduleID,
|
|
998
|
-
|
|
999
|
-
collisionMode: collisionMode,
|
|
1000
|
-
|
|
1001
|
-
fileFilter: fileFilter
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
if (this.slothlet.handlers.apiCacheManager) {
|
|
1008
|
-
this.slothlet.handlers.apiCacheManager.set(moduleID, {
|
|
1009
|
-
endpoint: effectivePath,
|
|
1010
|
-
moduleID: moduleID,
|
|
1011
|
-
api: newApi,
|
|
1012
|
-
folderPath: resolvedFolderPath,
|
|
1013
|
-
mode: this.____config.mode,
|
|
1014
|
-
sanitizeOptions: this.____config.sanitize || {},
|
|
1015
|
-
collisionMode: collisionMode,
|
|
1016
|
-
config: { ...this.____config },
|
|
1017
|
-
timestamp: Date.now()
|
|
1018
|
-
});
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
this.slothlet.debug("api", {
|
|
1022
|
-
key: "DEBUG_MODE_ADD_API_COMPONENT_BUILD_RETURN",
|
|
1023
|
-
topLevelKeys: Object.keys(newApi),
|
|
1024
|
-
dottedKeys: Object.keys(newApi).filter((k) => k.includes(".")),
|
|
1025
|
-
wrappers: Object.keys(newApi)
|
|
1026
|
-
.filter((k) => resolveWrapper(newApi[k]) !== null)
|
|
1027
|
-
.map((k) => {
|
|
1028
|
-
const _w = resolveWrapper(newApi[k]);
|
|
1029
|
-
return {
|
|
1030
|
-
key: k,
|
|
1031
|
-
apiPath: _w.apiPath,
|
|
1032
|
-
implKeys: Object.keys(_w.____slothletInternal.impl || {}),
|
|
1033
|
-
childCacheSize: Object.keys(_w).filter((k) => !k.startsWith("_") && !k.startsWith("__")).length,
|
|
1034
|
-
childCacheKeys: Object.keys(_w).filter((k) => !k.startsWith("_") && !k.startsWith("__"))
|
|
1035
|
-
};
|
|
1036
|
-
}),
|
|
1037
|
-
nonWrappers: Object.keys(newApi)
|
|
1038
|
-
.filter((k) => resolveWrapper(newApi[k]) === null)
|
|
1039
|
-
.map((k) => ({ key: k, type: typeof newApi[k] }))
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
let apiToMerge = newApi;
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
if (isFile && Object.keys(newApi).length === 1) {
|
|
1052
|
-
const fileName = Object.keys(newApi)[0];
|
|
1053
|
-
apiToMerge = newApi[fileName];
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (!isFile && normalizedPath) {
|
|
1069
|
-
const lastPart = normalizedPath.includes(".") ? normalizedPath.split(".").pop() : normalizedPath;
|
|
1070
|
-
if (lastPart && Object.prototype.hasOwnProperty.call(apiToMerge, lastPart)) {
|
|
1071
|
-
const dupValue = apiToMerge[lastPart];
|
|
1072
|
-
const dupType = typeof dupValue;
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
if (dupValue !== null && (dupType === "object" || dupType === "function")) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
const dupWrapper = resolveWrapper(dupValue);
|
|
1081
|
-
const dupFilePath = dupWrapper?.____slothletInternal?.filePath;
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
const dupFileDir = dupFilePath ? dupFilePath.replace(/\\/g, "/").split("/").slice(0, -1).join("/") : null;
|
|
1085
|
-
const normalizedFolderPath = resolvedFolderPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
1086
|
-
const expectedDir = normalizedFolderPath + "/" + lastPart;
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
const isDirectChild = dupFileDir === expectedDir || dupFileDir === normalizedFolderPath;
|
|
1095
|
-
|
|
1096
|
-
if (isDirectChild) {
|
|
1097
|
-
|
|
1098
|
-
const hoisted = {};
|
|
1099
|
-
for (const k of Object.keys(apiToMerge)) {
|
|
1100
|
-
if (k !== lastPart) hoisted[k] = apiToMerge[k];
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
if (dupWrapper) {
|
|
1106
|
-
|
|
1107
|
-
for (const k of Object.keys(dupWrapper).filter((k) => !k.startsWith("_") && !k.startsWith("__"))) {
|
|
1108
|
-
hoisted[k] = dupWrapper[k];
|
|
1109
|
-
}
|
|
1110
|
-
} else {
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
for (const k of Object.keys(dupValue)) {
|
|
1116
|
-
hoisted[k] = dupValue[k];
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
apiToMerge = hoisted;
|
|
1121
|
-
this.slothlet.debug("api", {
|
|
1122
|
-
key: "DEBUG_MODE_RULE_13_DEDUP_HOISTED_KEY",
|
|
1123
|
-
lastPart,
|
|
1124
|
-
newKeys: Object.keys(apiToMerge)
|
|
1125
|
-
});
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
if (this.____config.debug?.api) {
|
|
1132
|
-
this.slothlet.debug("api", {
|
|
1133
|
-
key: "DEBUG_MODE_ADD_API_COMPONENT_MERGE_KEYS",
|
|
1134
|
-
keys: Object.keys(apiToMerge),
|
|
1135
|
-
isRootLevel: parts.length === 0
|
|
1136
|
-
});
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
let anyAssignmentSucceeded = false;
|
|
1143
|
-
|
|
1144
|
-
if (parts.length === 0) {
|
|
1145
|
-
|
|
1146
|
-
for (const key of Object.keys(newApi)) {
|
|
1147
|
-
const result1 = await this.setValueAtPath(this.slothlet.api, [key], newApi[key], {
|
|
1148
|
-
mutateExisting,
|
|
1149
|
-
collisionMode,
|
|
1150
|
-
moduleID,
|
|
1151
|
-
sourceFolder: resolvedFolderPath
|
|
1152
|
-
});
|
|
1153
|
-
|
|
1154
|
-
const result2 = await this.setValueAtPath(this.slothlet.boundApi, [key], newApi[key], {
|
|
1155
|
-
mutateExisting,
|
|
1156
|
-
collisionMode,
|
|
1157
|
-
moduleID,
|
|
1158
|
-
sourceFolder: resolvedFolderPath
|
|
1159
|
-
});
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
if (result1 || result2) {
|
|
1163
|
-
anyAssignmentSucceeded = true;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
} else {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
if (resolveWrapper(apiToMerge) === null) {
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
const isCallableNamespace = typeof apiToMerge === "function";
|
|
1175
|
-
|
|
1176
|
-
const containerWrapper = new UnifiedWrapper(this.slothlet, {
|
|
1177
|
-
apiPath: effectivePath,
|
|
1178
|
-
mode: this.____config.mode,
|
|
1179
|
-
isCallable: isCallableNamespace,
|
|
1180
|
-
moduleID: moduleID,
|
|
1181
|
-
filePath: resolvedFolderPath,
|
|
1182
|
-
sourceFolder: resolvedFolderPath
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
containerWrapper.___setImpl(apiToMerge, moduleID);
|
|
1186
|
-
|
|
1187
|
-
apiToMerge = containerWrapper.createProxy();
|
|
1188
|
-
}
|
|
1189
|
-
|
|
1190
|
-
const result1 = await this.setValueAtPath(this.slothlet.api, effectiveParts, apiToMerge, {
|
|
1191
|
-
mutateExisting,
|
|
1192
|
-
collisionMode,
|
|
1193
|
-
moduleID,
|
|
1194
|
-
sourceFolder: resolvedFolderPath
|
|
1195
|
-
});
|
|
1196
|
-
|
|
1197
|
-
const result2 = await this.setValueAtPath(this.slothlet.boundApi, effectiveParts, apiToMerge, {
|
|
1198
|
-
mutateExisting,
|
|
1199
|
-
collisionMode,
|
|
1200
|
-
moduleID,
|
|
1201
|
-
sourceFolder: resolvedFolderPath
|
|
1202
|
-
});
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (result1 || result2) {
|
|
1206
|
-
anyAssignmentSucceeded = true;
|
|
1207
|
-
}
|
|
1208
|
-
}
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
if (anyAssignmentSucceeded) {
|
|
1217
|
-
const pendingMaterializations = [];
|
|
1218
|
-
const seenWrappers = new Set();
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
const collectPendingMaterializations = (obj, depth = 0) => {
|
|
1222
|
-
if (!obj || typeof obj !== "object" || depth > 10) return;
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
if (obj.__isVersionDispatcher === true) return;
|
|
1228
|
-
|
|
1229
|
-
const wrapper = resolveWrapper(obj);
|
|
1230
|
-
if (wrapper) {
|
|
1231
|
-
|
|
1232
|
-
if (seenWrappers.has(wrapper)) return;
|
|
1233
|
-
seenWrappers.add(wrapper);
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
if (wrapper.____slothletInternal.materializationPromise) {
|
|
1239
|
-
pendingMaterializations.push(wrapper.____slothletInternal.materializationPromise);
|
|
1240
|
-
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
const childKeys = Object.keys(wrapper).filter((k) => !k.startsWith("_") && !k.startsWith("__"));
|
|
1244
|
-
for (const key of childKeys) {
|
|
1245
|
-
collectPendingMaterializations(wrapper[key], depth + 1);
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
for (const key of Object.keys(obj)) {
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
if (key !== "____slothletInternal") {
|
|
1254
|
-
collectPendingMaterializations(obj[key], depth + 1);
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
};
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
if (effectiveParts.length === 0) {
|
|
1264
|
-
|
|
1265
|
-
for (const key of Object.keys(newApi)) {
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
if (this.slothlet.api[key]) {
|
|
1269
|
-
collectPendingMaterializations(this.slothlet.api[key]);
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
} else {
|
|
1273
|
-
|
|
1274
|
-
let current = this.slothlet.api;
|
|
1275
|
-
for (const part of effectiveParts) {
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
if (current && current[part]) {
|
|
1279
|
-
current = current[part];
|
|
1280
|
-
} else {
|
|
1281
|
-
break;
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
if (current) {
|
|
1288
|
-
collectPendingMaterializations(current);
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
if (pendingMaterializations.length > 0) {
|
|
1296
|
-
if (this.____config.debug?.api) {
|
|
1297
|
-
this.slothlet.debug("api", {
|
|
1298
|
-
key: "DEBUG_MODE_AWAITING_PENDING_MATERIALIZATIONS",
|
|
1299
|
-
count: pendingMaterializations.length,
|
|
1300
|
-
apiPath: normalizedPath
|
|
1301
|
-
});
|
|
1302
|
-
}
|
|
1303
|
-
await Promise.all(pendingMaterializations);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
if (anyAssignmentSucceeded && metadata && Object.keys(metadata).length > 0 && this.slothlet.handlers.metadata) {
|
|
1314
|
-
if (parts.length === 0) {
|
|
1315
|
-
|
|
1316
|
-
for (const key of Object.keys(newApi)) {
|
|
1317
|
-
this.slothlet.handlers.metadata.registerUserMetadata(key, metadata);
|
|
1318
|
-
}
|
|
1319
|
-
} else {
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
const rootSegment = effectiveParts[0];
|
|
1325
|
-
this.slothlet.handlers.metadata.registerUserMetadata(rootSegment, metadata);
|
|
1326
|
-
}
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
if (this.slothlet.handlers.ownership && moduleID) {
|
|
1335
|
-
this.slothlet.handlers.ownership.registerSubtree(apiToMerge, moduleID, effectivePath);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if (this.slothlet.handlers.ownership) {
|
|
1341
|
-
if (restOptions.recordHistory !== false) {
|
|
1342
|
-
this.state.addHistory.push({
|
|
1343
|
-
apiPath: normalizedPath,
|
|
1344
|
-
folderPath: resolvedFolderPath,
|
|
1345
|
-
options: { ...restOptions, metadata, moduleID },
|
|
1346
|
-
moduleID,
|
|
1347
|
-
versionConfig: versionConfig || null
|
|
1348
|
-
});
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
this.state.operationHistory.push({
|
|
1353
|
-
type: "add",
|
|
1354
|
-
apiPath: normalizedPath,
|
|
1355
|
-
folderPath: resolvedFolderPath,
|
|
1356
|
-
options: { ...restOptions, metadata, moduleID },
|
|
1357
|
-
moduleID,
|
|
1358
|
-
versionConfig: versionConfig || null
|
|
1359
|
-
});
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
if (versionConfig?.version && this.slothlet.handlers.versionManager) {
|
|
1368
|
-
const versionTag = String(versionConfig.version).trim();
|
|
1369
|
-
try {
|
|
1370
|
-
this.slothlet.handlers.versionManager.registerVersion(
|
|
1371
|
-
normalizedPath,
|
|
1372
|
-
versionTag,
|
|
1373
|
-
moduleID,
|
|
1374
|
-
versionConfig.metadata ?? {},
|
|
1375
|
-
versionConfig.default ?? false
|
|
1376
|
-
);
|
|
1377
|
-
} catch (error) {
|
|
1378
|
-
await this._rollbackFailedVersionedAdd({ moduleID, effectivePath, normalizedPath });
|
|
1379
|
-
throw error;
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
if (restOptions.permissions && this.slothlet.handlers?.permissionManager) {
|
|
1385
|
-
const perms = restOptions.permissions;
|
|
1386
|
-
const callerPattern = `${normalizedPath}.**`;
|
|
1387
|
-
|
|
1388
|
-
if (Array.isArray(perms.deny)) {
|
|
1389
|
-
for (const target of perms.deny) {
|
|
1390
|
-
this.slothlet.handlers.permissionManager.addRule(
|
|
1391
|
-
{ caller: callerPattern, target, effect: "deny" },
|
|
1392
|
-
moduleID
|
|
1393
|
-
);
|
|
1394
|
-
}
|
|
1395
|
-
}
|
|
1396
|
-
if (Array.isArray(perms.allow)) {
|
|
1397
|
-
for (const target of perms.allow) {
|
|
1398
|
-
this.slothlet.handlers.permissionManager.addRule(
|
|
1399
|
-
{ caller: callerPattern, target, effect: "allow" },
|
|
1400
|
-
moduleID
|
|
1401
|
-
);
|
|
1402
|
-
}
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
return moduleID;
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
async _rollbackFailedVersionedAdd({ moduleID, effectivePath, normalizedPath }) {
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
let addIndex = -1;
|
|
1417
|
-
for (let i = this.state.operationHistory.length - 1; i >= 0; i--) {
|
|
1418
|
-
const entry = this.state.operationHistory[i];
|
|
1419
|
-
if (entry?.type === "add" && entry?.apiPath === normalizedPath && entry?.moduleID === moduleID) {
|
|
1420
|
-
addIndex = i;
|
|
1421
|
-
break;
|
|
1422
|
-
}
|
|
1423
|
-
}
|
|
1424
|
-
if (addIndex !== -1) {
|
|
1425
|
-
this.state.operationHistory.splice(addIndex, 1);
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
this.state.addHistory = this.state.addHistory.filter((entry) => entry?.moduleID !== moduleID);
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
try {
|
|
1438
|
-
await this.removeApiComponent(moduleID || effectivePath, { recordHistory: false });
|
|
1439
|
-
} catch {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
async removeApiComponent(pathOrModuleId, options = {}) {
|
|
1447
|
-
const recordHistory = options.recordHistory !== false;
|
|
1448
|
-
if (typeof pathOrModuleId !== "string" || !pathOrModuleId) {
|
|
1449
|
-
throw new this.SlothletError("INVALID_ARGUMENT", {
|
|
1450
|
-
argument: "pathOrModuleId",
|
|
1451
|
-
expected: "non-empty string",
|
|
1452
|
-
received: typeof pathOrModuleId,
|
|
1453
|
-
validationError: true
|
|
1454
|
-
});
|
|
1455
|
-
}
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
let apiPath = null;
|
|
1460
|
-
let moduleID = null;
|
|
1461
|
-
|
|
1462
|
-
if (this.slothlet.handlers.ownership) {
|
|
1463
|
-
|
|
1464
|
-
const candidateModuleID = pathOrModuleId.split(":")[0];
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
const registeredModules = Array.from(this.slothlet.handlers.ownership.moduleToPath.keys());
|
|
1471
|
-
let matchingModule = null;
|
|
1472
|
-
for (let i = registeredModules.length - 1; i >= 0; i--) {
|
|
1473
|
-
const candidate = registeredModules[i];
|
|
1474
|
-
if (candidate === candidateModuleID || candidate.startsWith(`${candidateModuleID}_`)) {
|
|
1475
|
-
matchingModule = candidate;
|
|
1476
|
-
break;
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
if (matchingModule) {
|
|
1481
|
-
|
|
1482
|
-
moduleID = matchingModule;
|
|
1483
|
-
} else {
|
|
1484
|
-
|
|
1485
|
-
const owner = this.slothlet.handlers.ownership.getCurrentOwner(pathOrModuleId);
|
|
1486
|
-
if (owner) {
|
|
1487
|
-
|
|
1488
|
-
apiPath = pathOrModuleId;
|
|
1489
|
-
moduleID = owner.moduleID;
|
|
1490
|
-
} else {
|
|
1491
|
-
|
|
1492
|
-
return false;
|
|
1493
|
-
}
|
|
1494
|
-
}
|
|
1495
|
-
} else {
|
|
1496
|
-
|
|
1497
|
-
const isModuleId = !pathOrModuleId.includes(".");
|
|
1498
|
-
apiPath = isModuleId ? null : pathOrModuleId;
|
|
1499
|
-
moduleID = isModuleId ? pathOrModuleId.split(":")[0] : null;
|
|
1500
|
-
}
|
|
1501
|
-
if (!this.slothlet || !this.slothlet.isLoaded) {
|
|
1502
|
-
throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED", {
|
|
1503
|
-
operation: "removeApi",
|
|
1504
|
-
validationError: true
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
|
|
1508
|
-
if (apiPath && moduleID) {
|
|
1509
|
-
const normalizedPath = this.normalizeApiPath(apiPath).apiPath;
|
|
1510
|
-
const moduleIDKey = String(moduleID);
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
const history = this.slothlet.handlers.ownership?.getPathHistory?.(normalizedPath) || [];
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
const ownershipResult = this.slothlet.handlers.ownership?.removePath?.(normalizedPath, moduleIDKey) || {
|
|
1517
|
-
action: "none",
|
|
1518
|
-
removedModuleId: null,
|
|
1519
|
-
restoreModuleId: null
|
|
1520
|
-
};
|
|
1521
|
-
const pathParts = this.normalizeApiPath(apiPath).parts;
|
|
1522
|
-
if (ownershipResult.action === "delete") {
|
|
1523
|
-
await this.deletePath(this.slothlet.api, pathParts);
|
|
1524
|
-
await this.deletePath(this.slothlet.boundApi, pathParts);
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
if (this.slothlet.handlers.metadata) {
|
|
1529
|
-
const rootSegment = normalizedPath.split(".")[0];
|
|
1530
|
-
this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment);
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
if (this.slothlet.handlers.versionManager) {
|
|
1534
|
-
const versionKey = this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);
|
|
1535
|
-
if (versionKey) {
|
|
1536
|
-
this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath, versionKey.versionTag);
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
if (this.slothlet.handlers.versionManager.hasDispatcher(normalizedPath)) {
|
|
1542
|
-
this.slothlet.handlers.versionManager.teardownDispatcher(normalizedPath);
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
this.state.operationHistory.push({
|
|
1547
|
-
type: "remove",
|
|
1548
|
-
apiPath: normalizedPath
|
|
1549
|
-
});
|
|
1550
|
-
return true;
|
|
1551
|
-
}
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
if (ownershipResult.action === "restore") {
|
|
1556
|
-
const restoredValue = this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath);
|
|
1557
|
-
const restoredModuleId = this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath)?.moduleID;
|
|
1558
|
-
if (restoredValue !== undefined && restoredModuleId) {
|
|
1559
|
-
await this.setValueAtPath(this.slothlet.api, pathParts, restoredValue, {
|
|
1560
|
-
mutateExisting: true,
|
|
1561
|
-
allowOverwrite: true,
|
|
1562
|
-
collisionMode: "replace",
|
|
1563
|
-
moduleID: restoredModuleId
|
|
1564
|
-
});
|
|
1565
|
-
await this.setValueAtPath(this.slothlet.boundApi, pathParts, restoredValue, {
|
|
1566
|
-
mutateExisting: true,
|
|
1567
|
-
allowOverwrite: true,
|
|
1568
|
-
collisionMode: "replace",
|
|
1569
|
-
moduleID: restoredModuleId
|
|
1570
|
-
});
|
|
1571
|
-
|
|
1572
|
-
this.state.operationHistory.push({
|
|
1573
|
-
type: "remove",
|
|
1574
|
-
apiPath: normalizedPath
|
|
1575
|
-
});
|
|
1576
|
-
return true;
|
|
1577
|
-
}
|
|
1578
|
-
await this.restoreApiPath(normalizedPath, ownershipResult.restoreModuleId);
|
|
1579
|
-
|
|
1580
|
-
this.state.operationHistory.push({
|
|
1581
|
-
type: "remove",
|
|
1582
|
-
apiPath: normalizedPath
|
|
1583
|
-
});
|
|
1584
|
-
return true;
|
|
1585
|
-
}
|
|
1586
|
-
if (ownershipResult.action === "none" && history.length === 0) {
|
|
1587
|
-
await this.deletePath(this.slothlet.api, pathParts);
|
|
1588
|
-
await this.deletePath(this.slothlet.boundApi, pathParts);
|
|
1589
|
-
return true;
|
|
1590
|
-
}
|
|
1591
|
-
return false;
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
if (moduleID) {
|
|
1596
|
-
const moduleIDKey = String(moduleID);
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
const result = this.slothlet.handlers.ownership?.unregister?.(moduleIDKey) || { removed: [], rolledBack: [] };
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
if (this.slothlet.handlers.versionManager) {
|
|
1605
|
-
const versionKey = this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);
|
|
1606
|
-
if (versionKey) {
|
|
1607
|
-
this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath, versionKey.versionTag);
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
const allPaths = [...result.removed, ...result.rolledBack.map((r) => r.apiPath)];
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
const uniquePaths = [...new Set(allPaths)];
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
const pathsToDelete = [];
|
|
1619
|
-
const pathsToRollback = [];
|
|
1620
|
-
|
|
1621
|
-
for (const path of uniquePaths) {
|
|
1622
|
-
const currentOwner = this.slothlet.handlers.ownership?.getCurrentOwner?.(path);
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
const hasChildrenWithOtherOwners = uniquePaths.some((p) => {
|
|
1627
|
-
if (p === path || !p.startsWith(path + ".")) return false;
|
|
1628
|
-
const childOwner = this.slothlet.handlers.ownership?.getCurrentOwner?.(p);
|
|
1629
|
-
return childOwner && childOwner.moduleID !== moduleIDKey;
|
|
1630
|
-
});
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
if (currentOwner && currentOwner.moduleID !== moduleIDKey) {
|
|
1634
|
-
|
|
1635
|
-
pathsToRollback.push({ apiPath: path, restoredTo: currentOwner.moduleID });
|
|
1636
|
-
} else if (!hasChildrenWithOtherOwners) {
|
|
1637
|
-
|
|
1638
|
-
pathsToDelete.push(path);
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
pathsToDelete.sort((a, b) => {
|
|
1646
|
-
const depthA = (a.match(/\./g) || []).length;
|
|
1647
|
-
const depthB = (b.match(/\./g) || []).length;
|
|
1648
|
-
return depthB - depthA;
|
|
1649
|
-
});
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
for (const removedPath of pathsToDelete) {
|
|
1653
|
-
const { parts } = this.normalizeApiPath(removedPath);
|
|
1654
|
-
await this.deletePath(this.slothlet.api, parts);
|
|
1655
|
-
await this.deletePath(this.slothlet.boundApi, parts);
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
if (this.slothlet.handlers.metadata) {
|
|
1660
|
-
const rootSegment = removedPath.split(".")[0];
|
|
1661
|
-
this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment);
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
if (pathsToDelete.length > 0) {
|
|
1670
|
-
const rootSegment = pathsToDelete[0].split(".")[0];
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
if (rootSegment in this.slothlet.api) {
|
|
1676
|
-
delete this.slothlet.api[rootSegment];
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
if (rootSegment in this.slothlet.boundApi) {
|
|
1682
|
-
delete this.slothlet.boundApi[rootSegment];
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
for (const rollback of pathsToRollback) {
|
|
1688
|
-
|
|
1689
|
-
const { parts } = this.normalizeApiPath(rollback.apiPath);
|
|
1690
|
-
const previousImpl = this.slothlet.handlers.ownership?.getCurrentValue?.(rollback.apiPath);
|
|
1691
|
-
|
|
1692
|
-
if (previousImpl !== undefined) {
|
|
1693
|
-
|
|
1694
|
-
const existingWrapper = this.getValueAtPath(this.slothlet.api, parts);
|
|
1695
|
-
const existingWrapperRaw = resolveWrapper(existingWrapper);
|
|
1696
|
-
if (existingWrapperRaw) {
|
|
1697
|
-
|
|
1698
|
-
existingWrapperRaw.___setImpl(previousImpl, rollback.restoredTo);
|
|
1699
|
-
}
|
|
1700
|
-
|
|
1701
|
-
const existingBoundWrapper = this.getValueAtPath(this.slothlet.boundApi, parts);
|
|
1702
|
-
const existingBoundWrapperRaw = resolveWrapper(existingBoundWrapper);
|
|
1703
|
-
if (existingBoundWrapperRaw) {
|
|
1704
|
-
existingBoundWrapperRaw.___setImpl(previousImpl, rollback.restoredTo);
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
this.state.addHistory = this.state.addHistory.filter((entry) => String(entry.moduleID) !== moduleIDKey);
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
if (this.slothlet.handlers.apiCacheManager) {
|
|
1715
|
-
const deleted = this.slothlet.handlers.apiCacheManager.delete(moduleIDKey);
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
if (deleted) {
|
|
1719
|
-
this.slothlet.debug("cache", {
|
|
1720
|
-
key: "DEBUG_MODE_CACHE_DELETED_MODULE_REMOVED",
|
|
1721
|
-
moduleID: moduleIDKey
|
|
1722
|
-
});
|
|
1723
|
-
}
|
|
1724
|
-
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
if (recordHistory && pathsToDelete.length > 0) {
|
|
1730
|
-
const rootSegment = pathsToDelete[0].split(".")[0];
|
|
1731
|
-
this.state.operationHistory.push({
|
|
1732
|
-
type: "remove",
|
|
1733
|
-
apiPath: rootSegment
|
|
1734
|
-
});
|
|
1735
|
-
}
|
|
1736
|
-
|
|
1737
|
-
return pathsToDelete.length > 0 || pathsToRollback.length > 0;
|
|
1738
|
-
}
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
if (!apiPath) {
|
|
1743
|
-
throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID", {
|
|
1744
|
-
apiPath,
|
|
1745
|
-
reason: translate("API_PATH_REASON_REQUIRED"),
|
|
1746
|
-
index: undefined,
|
|
1747
|
-
segment: undefined,
|
|
1748
|
-
validationError: true
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
|
|
1752
|
-
const { apiPath: normalizedPath, parts } = this.normalizeApiPath(apiPath);
|
|
1753
|
-
const ownershipResult = this.slothlet.handlers.ownership?.removePath?.(normalizedPath, null) || {
|
|
1754
|
-
action: "none",
|
|
1755
|
-
removedModuleId: null,
|
|
1756
|
-
restoreModuleId: null
|
|
1757
|
-
};
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
const pathExists = this.getValueAtPath(this.slothlet.api, parts) !== undefined;
|
|
1761
|
-
|
|
1762
|
-
if (ownershipResult.action === "none") {
|
|
1763
|
-
if (pathExists) {
|
|
1764
|
-
await this.deletePath(this.slothlet.api, parts);
|
|
1765
|
-
await this.deletePath(this.slothlet.boundApi, parts);
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
if (this.slothlet.handlers.metadata) {
|
|
1770
|
-
this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath);
|
|
1771
|
-
}
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
if (recordHistory) {
|
|
1776
|
-
this.state.operationHistory.push({
|
|
1777
|
-
type: "remove",
|
|
1778
|
-
apiPath: normalizedPath
|
|
1779
|
-
});
|
|
1780
|
-
}
|
|
1781
|
-
return true;
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
return false;
|
|
1785
|
-
}
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
if (ownershipResult.action === "delete") {
|
|
1789
|
-
await this.deletePath(this.slothlet.api, parts);
|
|
1790
|
-
await this.deletePath(this.slothlet.boundApi, parts);
|
|
1791
|
-
|
|
1792
|
-
if (this.slothlet.handlers.metadata) {
|
|
1793
|
-
this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath);
|
|
1794
|
-
}
|
|
1795
|
-
|
|
1796
|
-
if (recordHistory) {
|
|
1797
|
-
this.state.operationHistory.push({
|
|
1798
|
-
type: "remove",
|
|
1799
|
-
apiPath: normalizedPath
|
|
1800
|
-
});
|
|
1801
|
-
}
|
|
1802
|
-
return true;
|
|
1803
|
-
}
|
|
1804
|
-
if (ownershipResult.action === "restore") {
|
|
1805
|
-
const restoredValue = this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath);
|
|
1806
|
-
const restoredModuleId = this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath)?.moduleID;
|
|
1807
|
-
if (restoredValue !== undefined && restoredModuleId) {
|
|
1808
|
-
await this.setValueAtPath(this.slothlet.api, parts, restoredValue, {
|
|
1809
|
-
mutateExisting: true,
|
|
1810
|
-
allowOverwrite: true,
|
|
1811
|
-
collisionMode: "replace",
|
|
1812
|
-
moduleID: restoredModuleId
|
|
1813
|
-
});
|
|
1814
|
-
await this.setValueAtPath(this.slothlet.boundApi, parts, restoredValue, {
|
|
1815
|
-
mutateExisting: true,
|
|
1816
|
-
allowOverwrite: true,
|
|
1817
|
-
collisionMode: "replace",
|
|
1818
|
-
moduleID: restoredModuleId
|
|
1819
|
-
});
|
|
1820
|
-
|
|
1821
|
-
if (recordHistory) {
|
|
1822
|
-
this.state.operationHistory.push({
|
|
1823
|
-
type: "remove",
|
|
1824
|
-
apiPath: normalizedPath
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
return true;
|
|
1828
|
-
}
|
|
1829
|
-
await this.restoreApiPath(normalizedPath, ownershipResult.restoreModuleId);
|
|
1830
|
-
|
|
1831
|
-
if (recordHistory) {
|
|
1832
|
-
this.state.operationHistory.push({
|
|
1833
|
-
type: "remove",
|
|
1834
|
-
apiPath: normalizedPath
|
|
1835
|
-
});
|
|
1836
|
-
}
|
|
1837
|
-
return true;
|
|
1838
|
-
}
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
return false;
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
async reloadApiComponent(params) {
|
|
1847
|
-
const { apiPath, moduleID, options } = params || {};
|
|
1848
|
-
if (!this.slothlet || !this.slothlet.isLoaded) {
|
|
1849
|
-
throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED", {
|
|
1850
|
-
operation: "reloadApi",
|
|
1851
|
-
validationError: true
|
|
1852
|
-
});
|
|
1853
|
-
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
if (moduleID) {
|
|
1857
|
-
await this._reloadByModuleID(moduleID);
|
|
1858
|
-
return;
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
if (apiPath) {
|
|
1863
|
-
await this._reloadByApiPath(apiPath, options);
|
|
1864
|
-
return;
|
|
1865
|
-
}
|
|
1866
|
-
|
|
1867
|
-
throw new this.SlothletError("INVALID_ARGUMENT", {
|
|
1868
|
-
argument: "params",
|
|
1869
|
-
expected: "{ moduleID } or { apiPath }",
|
|
1870
|
-
received: params,
|
|
1871
|
-
validationError: true
|
|
1872
|
-
});
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
async _reloadByModuleID(moduleID, { forceReplace = true } = {}) {
|
|
1877
|
-
const cacheManager = this.slothlet.handlers.apiCacheManager;
|
|
1878
|
-
if (!cacheManager) {
|
|
1879
|
-
throw new this.SlothletError("CACHE_MANAGER_NOT_AVAILABLE", {
|
|
1880
|
-
operation: "reload",
|
|
1881
|
-
validationError: true
|
|
1882
|
-
});
|
|
1883
|
-
}
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
if (!cacheManager.has(moduleID)) {
|
|
1887
|
-
throw new this.SlothletError("CACHE_NOT_FOUND", {
|
|
1888
|
-
moduleID,
|
|
1889
|
-
operation: "reload",
|
|
1890
|
-
validationError: true
|
|
1891
|
-
});
|
|
1892
|
-
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
const oldEntry = cacheManager.get(moduleID);
|
|
1896
|
-
|
|
1897
|
-
this.slothlet.debug("reload", {
|
|
1898
|
-
key: "DEBUG_MODE_RELOADING_MODULE_BY_ID",
|
|
1899
|
-
moduleID,
|
|
1900
|
-
endpoint: oldEntry.endpoint,
|
|
1901
|
-
folderPath: oldEntry.folderPath
|
|
1902
|
-
});
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
const freshApi = await cacheManager.rebuildCache(moduleID);
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
cacheManager.set(moduleID, {
|
|
1909
|
-
...oldEntry,
|
|
1910
|
-
api: freshApi,
|
|
1911
|
-
timestamp: Date.now()
|
|
1912
|
-
});
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
this.slothlet.debug("reload", {
|
|
1916
|
-
key: "DEBUG_MODE_FRESH_API_KEYS_BEFORE_RESTORE",
|
|
1917
|
-
moduleID,
|
|
1918
|
-
endpoint: oldEntry.endpoint,
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
freshApiKeys: Object.keys(freshApi || {})
|
|
1922
|
-
});
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
await this._restoreApiTree(freshApi, oldEntry.endpoint, moduleID, oldEntry.collisionMode, forceReplace);
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
this.slothlet.debug("reload", {
|
|
1929
|
-
key: "DEBUG_MODE_FRESH_API_KEYS_AFTER_RESTORE",
|
|
1930
|
-
moduleID,
|
|
1931
|
-
endpoint: oldEntry.endpoint,
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
freshApiKeys: Object.keys(freshApi || {})
|
|
1935
|
-
});
|
|
1936
|
-
|
|
1937
|
-
this.slothlet.debug("reload", {
|
|
1938
|
-
key: "DEBUG_MODE_MODULE_RELOAD_COMPLETE",
|
|
1939
|
-
moduleID
|
|
1940
|
-
});
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
if (this.slothlet.handlers.versionManager) {
|
|
1944
|
-
this.slothlet.handlers.versionManager.onVersionedModuleReload(moduleID);
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
async _reloadByApiPath(apiPath, options = {}) {
|
|
1950
|
-
this.slothlet.debug("reload", {
|
|
1951
|
-
key: "DEBUG_MODE_RELOADING_BY_API_PATH",
|
|
1952
|
-
apiPath
|
|
1953
|
-
});
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
const moduleIDsToReload = this._findAffectedCaches(apiPath);
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
if (moduleIDsToReload.length === 0) {
|
|
1962
|
-
this.slothlet.debug("reload", {
|
|
1963
|
-
key: "DEBUG_MODE_NO_CACHES_ATTEMPTING_RESTORE",
|
|
1964
|
-
apiPath
|
|
1965
|
-
});
|
|
1966
|
-
|
|
1967
|
-
if (apiPath !== "." && apiPath !== "") {
|
|
1968
|
-
await this.restoreApiPath(apiPath, "base");
|
|
1969
|
-
}
|
|
1970
|
-
return;
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
const cacheManager = this.slothlet.handlers.apiCacheManager;
|
|
1978
|
-
moduleIDsToReload.sort((a, b) => {
|
|
1979
|
-
const entryA = cacheManager.get(a);
|
|
1980
|
-
const entryB = cacheManager.get(b);
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
if (entryA?.endpoint === "." && entryB?.endpoint !== ".") return -1;
|
|
1986
|
-
if (entryB?.endpoint === "." && entryA?.endpoint !== ".") return 1;
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
const indexA = this.state.addHistory.findIndex((h) => h.moduleID === a);
|
|
1990
|
-
const indexB = this.state.addHistory.findIndex((h) => h.moduleID === b);
|
|
1991
|
-
return indexA - indexB;
|
|
1992
|
-
});
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
const endpointOrder = new Map();
|
|
1999
|
-
for (const moduleID of moduleIDsToReload) {
|
|
2000
|
-
const entry = cacheManager.get(moduleID);
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
const ep = entry?.endpoint ?? ".";
|
|
2004
|
-
if (!endpointOrder.has(ep)) endpointOrder.set(ep, []);
|
|
2005
|
-
endpointOrder.get(ep).push(moduleID);
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
for (const [, moduleIDs] of endpointOrder) {
|
|
2009
|
-
for (let i = 0; i < moduleIDs.length; i++) {
|
|
2010
|
-
await this._reloadByModuleID(moduleIDs[i], { forceReplace: i === 0 });
|
|
2011
|
-
}
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
const reloadMetadata = options?.metadata;
|
|
2018
|
-
if (reloadMetadata && typeof reloadMetadata === "object" && Object.keys(reloadMetadata).length > 0) {
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
if (this.slothlet.handlers.metadata) {
|
|
2022
|
-
const targetPath = apiPath === "." ? null : apiPath.split(".")[0];
|
|
2023
|
-
if (targetPath) {
|
|
2024
|
-
this.slothlet.handlers.metadata.registerUserMetadata(targetPath, reloadMetadata);
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
this.slothlet.debug("reload", {
|
|
2030
|
-
key: "DEBUG_MODE_API_PATH_RELOAD_COMPLETE",
|
|
2031
|
-
apiPath,
|
|
2032
|
-
reloadedModules: moduleIDsToReload.length,
|
|
2033
|
-
loadOrder: moduleIDsToReload
|
|
2034
|
-
});
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
_findAffectedCaches(apiPath) {
|
|
2039
|
-
const cacheManager = this.slothlet.handlers.apiCacheManager;
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
if (!cacheManager) return [];
|
|
2043
|
-
|
|
2044
|
-
const allModuleIDs = cacheManager.getAllModuleIDs();
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
if (apiPath === "." || apiPath === "" || apiPath == null) {
|
|
2048
|
-
const baseModules = [];
|
|
2049
|
-
for (const moduleID of allModuleIDs) {
|
|
2050
|
-
const entry = cacheManager.get(moduleID);
|
|
2051
|
-
if (entry && entry.endpoint === ".") {
|
|
2052
|
-
baseModules.push(moduleID);
|
|
2053
|
-
}
|
|
2054
|
-
}
|
|
2055
|
-
return baseModules;
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
const exactMatches = [];
|
|
2060
|
-
for (const moduleID of allModuleIDs) {
|
|
2061
|
-
const entry = cacheManager.get(moduleID);
|
|
2062
|
-
if (entry && entry.endpoint === apiPath) {
|
|
2063
|
-
exactMatches.push(moduleID);
|
|
2064
|
-
}
|
|
2065
|
-
}
|
|
2066
|
-
if (exactMatches.length > 0) return exactMatches;
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
const children = [];
|
|
2070
|
-
const pathPrefix = apiPath + ".";
|
|
2071
|
-
for (const moduleID of allModuleIDs) {
|
|
2072
|
-
const entry = cacheManager.get(moduleID);
|
|
2073
|
-
if (entry?.endpoint?.startsWith(pathPrefix)) {
|
|
2074
|
-
children.push(moduleID);
|
|
2075
|
-
}
|
|
2076
|
-
}
|
|
2077
|
-
if (children.length > 0) return children;
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
const ownership = this.slothlet.handlers.ownership;
|
|
2081
|
-
const history = ownership?.getPathHistory?.(apiPath);
|
|
2082
|
-
if (history && history.length > 0) {
|
|
2083
|
-
const owned = [];
|
|
2084
|
-
for (const { moduleID } of history) {
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
if (cacheManager.has(moduleID)) {
|
|
2088
|
-
owned.push(moduleID);
|
|
2089
|
-
}
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
if (owned.length > 0) return owned;
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
let bestMatch = null;
|
|
2100
|
-
let bestLength = -1;
|
|
2101
|
-
for (const moduleID of allModuleIDs) {
|
|
2102
|
-
const entry = cacheManager.get(moduleID);
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
if (!entry?.endpoint) continue;
|
|
2106
|
-
|
|
2107
|
-
const ep = entry.endpoint;
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
if (ep === "." || apiPath.startsWith(ep + ".")) {
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
if (ep.length > bestLength) {
|
|
2116
|
-
bestLength = ep.length;
|
|
2117
|
-
bestMatch = moduleID;
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
if (bestMatch) return [bestMatch];
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
return [];
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
_collectCustomProperties(existingProxy, freshApi) {
|
|
2132
|
-
const customProps = {};
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
if (!existingProxy || (typeof existingProxy !== "object" && typeof existingProxy !== "function")) {
|
|
2137
|
-
return customProps;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
const wrapper = resolveWrapper(existingProxy);
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
if (!wrapper) {
|
|
2145
|
-
return customProps;
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
const freshKeys = new Set(freshApi ? Object.keys(freshApi) : []);
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
const ownKeys = Object.keys(wrapper).filter((k) => !ComponentBase.INTERNAL_KEYS.has(k));
|
|
2158
|
-
|
|
2159
|
-
for (const key of ownKeys) {
|
|
2160
|
-
try {
|
|
2161
|
-
|
|
2162
|
-
const val = wrapper[key];
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
if (val && (typeof val === "object" || typeof val === "function") && resolveWrapper(val)) {
|
|
2167
|
-
continue;
|
|
2168
|
-
}
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
if (!freshKeys.has(key)) {
|
|
2173
|
-
|
|
2174
|
-
customProps[key] = val;
|
|
2175
|
-
} else {
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
customProps[key] = val;
|
|
2179
|
-
}
|
|
2180
|
-
} catch {
|
|
2181
|
-
|
|
2182
|
-
}
|
|
2183
|
-
}
|
|
2184
|
-
|
|
2185
|
-
return customProps;
|
|
2186
|
-
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
_restoreCustomProperties(proxy, customProps) {
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
if (!proxy || !customProps || typeof customProps !== "object") {
|
|
2194
|
-
return;
|
|
2195
|
-
}
|
|
2196
|
-
|
|
2197
|
-
for (const [key, value] of Object.entries(customProps)) {
|
|
2198
|
-
try {
|
|
2199
|
-
proxy[key] = value;
|
|
2200
|
-
} catch {
|
|
2201
|
-
|
|
2202
|
-
}
|
|
2203
|
-
}
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
async _restoreApiTree(freshApi, endpoint, moduleID, collisionMode, forceReplace = true) {
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
if (!freshApi || (typeof freshApi !== "object" && typeof freshApi !== "function")) {
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
const parts = endpoint === "." ? [] : endpoint.split(".");
|
|
2217
|
-
|
|
2218
|
-
if (parts.length === 0) {
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
for (const key of Object.keys(freshApi)) {
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
if (typeof key === "string" && (key.startsWith("_") || key.startsWith("__"))) continue;
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
if (key === "slothlet" || key === "shutdown" || key === "destroy") continue;
|
|
2231
|
-
|
|
2232
|
-
const existingAtKey = this.slothlet.api[key];
|
|
2233
|
-
const freshValue = freshApi[key];
|
|
2234
|
-
|
|
2235
|
-
if (existingAtKey && resolveWrapper(existingAtKey) !== null) {
|
|
2236
|
-
|
|
2237
|
-
const customProps = this._collectCustomProperties(existingAtKey, freshValue);
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
const freshWrapper = resolveWrapper(freshValue);
|
|
2242
|
-
const isLazyFresh =
|
|
2243
|
-
freshWrapper &&
|
|
2244
|
-
freshWrapper.____slothletInternal.mode === "lazy" &&
|
|
2245
|
-
!freshWrapper.____slothletInternal.state.materialized &&
|
|
2246
|
-
typeof freshWrapper.____slothletInternal.materializeFunc === "function";
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
this.slothlet.debug("reload", {
|
|
2250
|
-
key: "DEBUG_MODE_RESTORE_ROOT_KEY_INSPECT",
|
|
2251
|
-
rootKey: key,
|
|
2252
|
-
hasFreshWrapper: !!freshWrapper,
|
|
2253
|
-
freshMode: freshWrapper?.____slothletInternal.mode,
|
|
2254
|
-
freshMaterialized: freshWrapper?.____slothletInternal.state?.materialized,
|
|
2255
|
-
hasMaterializeFunc: typeof freshWrapper?.____slothletInternal.materializeFunc === "function",
|
|
2256
|
-
isLazyFresh,
|
|
2257
|
-
existingMaterialized: resolveWrapper(existingAtKey)?.____slothletInternal?.state?.materialized
|
|
2258
|
-
});
|
|
2259
|
-
|
|
2260
|
-
if (isLazyFresh) {
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
resolveWrapper(existingAtKey).___resetLazy(freshWrapper.____slothletInternal.materializeFunc);
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
this._restoreCustomProperties(existingAtKey, customProps);
|
|
2269
|
-
|
|
2270
|
-
this.slothlet.debug("reload", {
|
|
2271
|
-
key: "DEBUG_MODE_ROOT_KEY_RESET_LAZY",
|
|
2272
|
-
rootKey: key,
|
|
2273
|
-
restoredCustomProps: Object.keys(customProps)
|
|
2274
|
-
});
|
|
2275
|
-
} else {
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
let implForReload;
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
if (freshValue && resolveWrapper(freshValue) !== null) {
|
|
2289
|
-
implForReload = freshWrapper ? UnifiedWrapper._extractFullImpl(freshWrapper) : freshValue;
|
|
2290
|
-
} else {
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
implForReload = freshValue;
|
|
2296
|
-
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
if (typeof implForReload === "function") {
|
|
2300
|
-
const extracted = {};
|
|
2301
|
-
for (const k of Object.keys(implForReload)) {
|
|
2302
|
-
extracted[k] = implForReload[k];
|
|
2303
|
-
}
|
|
2304
|
-
implForReload = extracted;
|
|
2305
|
-
}
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
const wrapper = resolveWrapper(existingAtKey);
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
const originalCollisionMode = wrapper ? wrapper.____slothletInternal.state.collisionMode : null;
|
|
2314
|
-
if (forceReplace && wrapper) {
|
|
2315
|
-
wrapper.____slothletInternal.state.collisionMode = "replace";
|
|
2316
|
-
}
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
if (wrapper && originalCollisionMode !== null) {
|
|
2322
|
-
wrapper.____slothletInternal.state.collisionMode = originalCollisionMode;
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
this._restoreCustomProperties(existingAtKey, customProps);
|
|
2327
|
-
|
|
2328
|
-
this.slothlet.debug("reload", {
|
|
2329
|
-
key: "DEBUG_MODE_ROOT_KEY_UPDATED_SETIMPL",
|
|
2330
|
-
rootKey: key,
|
|
2331
|
-
restoredCustomProps: Object.keys(customProps)
|
|
2332
|
-
});
|
|
2333
|
-
}
|
|
2334
|
-
} else if (existingAtKey === undefined) {
|
|
2335
|
-
|
|
2336
|
-
const cacheManager = this.slothlet.handlers.apiCacheManager;
|
|
2337
|
-
const cacheEntry = cacheManager.get(moduleID);
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
const resolvedFolderPath = cacheEntry?.folderPath || "";
|
|
2341
|
-
|
|
2342
|
-
await this.setValueAtPath(this.slothlet.api, [key], freshValue, {
|
|
2343
|
-
mutateExisting: true,
|
|
2344
|
-
collisionMode,
|
|
2345
|
-
moduleID,
|
|
2346
|
-
sourceFolder: resolvedFolderPath
|
|
2347
|
-
});
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
if (this.slothlet.boundApi) {
|
|
2352
|
-
await this.setValueAtPath(this.slothlet.boundApi, [key], freshValue, {
|
|
2353
|
-
mutateExisting: true,
|
|
2354
|
-
collisionMode,
|
|
2355
|
-
moduleID,
|
|
2356
|
-
sourceFolder: resolvedFolderPath
|
|
2357
|
-
});
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
}
|
|
2362
|
-
} else {
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
const existing = this.getValueAtPath(this.slothlet.api, parts);
|
|
2366
|
-
|
|
2367
|
-
this.slothlet.debug("reload", {
|
|
2368
|
-
key: "DEBUG_MODE_RESTORE_NESTED_PATH",
|
|
2369
|
-
endpoint,
|
|
2370
|
-
moduleID,
|
|
2371
|
-
partsPath: parts.join("."),
|
|
2372
|
-
existingFound: !!existing,
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
hasSetImpl: existing ? resolveWrapper(existing) !== null : false,
|
|
2377
|
-
freshApiKeys: Object.keys(freshApi || {})
|
|
2378
|
-
|
|
2379
|
-
});
|
|
2380
|
-
|
|
2381
|
-
if (existing && resolveWrapper(existing) !== null) {
|
|
2382
|
-
|
|
2383
|
-
const customProps = this._collectCustomProperties(existing, freshApi);
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
const wrapper = resolveWrapper(existing);
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
const originalCollisionMode = wrapper ? wrapper.____slothletInternal.state.collisionMode : null;
|
|
2392
|
-
|
|
2393
|
-
if (forceReplace && wrapper) {
|
|
2394
|
-
wrapper.____slothletInternal.state.collisionMode = "replace";
|
|
2395
|
-
this.slothlet.debug("reload", {
|
|
2396
|
-
key: "DEBUG_MODE_RESTORE_FORCING_REPLACE",
|
|
2397
|
-
endpoint,
|
|
2398
|
-
originalCollisionMode,
|
|
2399
|
-
wrapperApiPath: wrapper.____slothletInternal.apiPath
|
|
2400
|
-
});
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
let implForReload;
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
if (resolveWrapper(freshApi) !== null) {
|
|
2416
|
-
const freshWrapper = resolveWrapper(freshApi);
|
|
2417
|
-
implForReload = freshWrapper ? UnifiedWrapper._extractFullImpl(freshWrapper) : freshApi;
|
|
2418
|
-
} else if (typeof freshApi === "function") {
|
|
2419
|
-
implForReload = {};
|
|
2420
|
-
for (const key of Object.keys(freshApi)) {
|
|
2421
|
-
implForReload[key] = freshApi[key];
|
|
2422
|
-
}
|
|
2423
|
-
} else {
|
|
2424
|
-
implForReload = freshApi;
|
|
2425
|
-
}
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
if (parts.length > 0 && implForReload && typeof implForReload === "object") {
|
|
2435
|
-
const lastEndpointPart = parts[parts.length - 1];
|
|
2436
|
-
if (lastEndpointPart && Object.prototype.hasOwnProperty.call(implForReload, lastEndpointPart)) {
|
|
2437
|
-
const dupValue = implForReload[lastEndpointPart];
|
|
2438
|
-
const dupWrapperForDedup = resolveWrapper(dupValue);
|
|
2439
|
-
if (dupWrapperForDedup) {
|
|
2440
|
-
const hoisted = {};
|
|
2441
|
-
for (const k of Object.keys(implForReload)) {
|
|
2442
|
-
if (k !== lastEndpointPart) hoisted[k] = implForReload[k];
|
|
2443
|
-
}
|
|
2444
|
-
for (const k of Object.keys(dupWrapperForDedup).filter((k) => !k.startsWith("_") && !k.startsWith("__"))) {
|
|
2445
|
-
hoisted[k] = dupWrapperForDedup[k];
|
|
2446
|
-
}
|
|
2447
|
-
implForReload = hoisted;
|
|
2448
|
-
}
|
|
2449
|
-
}
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
if (implForReload && typeof implForReload === "object") {
|
|
2461
|
-
for (const key of Object.keys(implForReload)) {
|
|
2462
|
-
const val = implForReload[key];
|
|
2463
|
-
if (resolveWrapper(val) !== null) {
|
|
2464
|
-
const childWrapper = resolveWrapper(val);
|
|
2465
|
-
if (childWrapper.____slothletInternal.state.materialized) {
|
|
2466
|
-
implForReload[key] = UnifiedWrapper._extractFullImpl(childWrapper);
|
|
2467
|
-
}
|
|
2468
|
-
}
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
|
-
resolveWrapper(existing).___setImpl(implForReload, moduleID);
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
if (wrapper && originalCollisionMode !== null) {
|
|
2479
|
-
wrapper.____slothletInternal.state.collisionMode = originalCollisionMode;
|
|
2480
|
-
}
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
this._restoreCustomProperties(existing, customProps);
|
|
2484
|
-
|
|
2485
|
-
this.slothlet.debug("reload", {
|
|
2486
|
-
key: "DEBUG_MODE_UPDATED_WRAPPER_IMPL",
|
|
2487
|
-
endpoint,
|
|
2488
|
-
moduleID,
|
|
2489
|
-
forcedReplaceMode: true,
|
|
2490
|
-
restoredCustomProps: Object.keys(customProps)
|
|
2491
|
-
});
|
|
2492
|
-
} else {
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
const cacheManager = this.slothlet.handlers.apiCacheManager;
|
|
2496
|
-
const cacheEntry = cacheManager.get(moduleID);
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
const resolvedFolderPath = cacheEntry?.folderPath || "";
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
let implForContainer = freshApi;
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
if (typeof freshApi === "function") {
|
|
2510
|
-
implForContainer = {};
|
|
2511
|
-
for (const key of Object.keys(freshApi)) {
|
|
2512
|
-
implForContainer[key] = freshApi[key];
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
const containerWrapper = new UnifiedWrapper(this.slothlet, {
|
|
2516
|
-
apiPath: endpoint,
|
|
2517
|
-
mode: this.____config.mode,
|
|
2518
|
-
moduleID: moduleID,
|
|
2519
|
-
filePath: resolvedFolderPath,
|
|
2520
|
-
sourceFolder: resolvedFolderPath
|
|
2521
|
-
});
|
|
2522
|
-
containerWrapper.___setImpl(implForContainer, moduleID);
|
|
2523
|
-
const apiToSet = containerWrapper.createProxy();
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
await this.setValueAtPath(this.slothlet.api, parts, apiToSet, {
|
|
2527
|
-
mutateExisting: true,
|
|
2528
|
-
collisionMode,
|
|
2529
|
-
moduleID,
|
|
2530
|
-
sourceFolder: resolvedFolderPath
|
|
2531
|
-
});
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
if (this.slothlet.boundApi) {
|
|
2536
|
-
await this.setValueAtPath(this.slothlet.boundApi, parts, apiToSet, {
|
|
2537
|
-
mutateExisting: true,
|
|
2538
|
-
collisionMode,
|
|
2539
|
-
moduleID,
|
|
2540
|
-
sourceFolder: resolvedFolderPath
|
|
2541
|
-
});
|
|
2542
|
-
}
|
|
2543
|
-
|
|
2544
|
-
this.slothlet.debug("reload", {
|
|
2545
|
-
key: "DEBUG_MODE_CREATED_NEW_WRAPPER_UNEXPECTED",
|
|
2546
|
-
endpoint,
|
|
2547
|
-
moduleID
|
|
2548
|
-
});
|
|
2549
|
-
}
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
}
|
|
17
|
+
import fs from"node:fs/promises";import path from"node:path";import{translate}from"@cldmv/slothlet/i18n";import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{UnifiedWrapper,resolveWrapper}from"@cldmv/slothlet/handlers/unified-wrapper";class ApiManager extends ComponentBase{static slothletProperty="apiManager";constructor(slothlet){super(slothlet);this.state={addHistory:[],initialConfig:slothlet?.config||null,operationHistory:[]}}normalizeApiPath(apiPath){if(apiPath===""||apiPath===null||apiPath===void 0){return{apiPath:"",parts:[]}}if(Array.isArray(apiPath)){if(apiPath.length===0){return{apiPath:"",parts:[]}}for(let i=0;i<apiPath.length;i++){if(typeof apiPath[i]!=="string"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,segment:apiPath[i],index:i,reason:translate("API_PATH_REASON_ARRAY_ELEMENTS"),validationError:true})}if(apiPath[i].trim()===""){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,segment:apiPath[i],index:i,reason:translate("API_PATH_REASON_ARRAY_EMPTY_SEGMENTS"),validationError:true})}}if(apiPath[0]==="slothlet"||apiPath.length===1&&(apiPath[0]==="shutdown"||apiPath[0]==="destroy")){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_RESERVED_NAME"),index:void 0,segment:void 0,validationError:true})}return{apiPath:apiPath.join("."),parts:apiPath}}if(typeof apiPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_INVALID_TYPE"),index:void 0,segment:void 0,validationError:true})}const normalized=apiPath.trim();if(normalized===""){return{apiPath:"",parts:[]}}const parts=normalized.split(".");if(parts.length===0||parts.some(part=>part.trim()==="")){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_EMPTY_SEGMENTS"),index:void 0,segment:void 0,validationError:true})}if(parts[0]==="slothlet"||normalized==="shutdown"||normalized==="destroy"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:normalized,reason:translate("API_PATH_REASON_RESERVED_NAME"),index:void 0,segment:void 0,validationError:true})}return{apiPath:normalized,parts}}async resolvePath(inputPath){if(!inputPath||typeof inputPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:inputPath,validationError:true})}const resolvedPath=this.slothlet.helpers.resolver.resolvePathFromCaller(inputPath);try{const stats=await fs.stat(resolvedPath);return{resolvedPath,isDirectory:stats.isDirectory(),isFile:stats.isFile()}}catch(error){if(error instanceof this.SlothletError){throw error}throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}}async resolveFolderPath(folderPath){if(!folderPath||typeof folderPath!=="string"){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:folderPath,validationError:true})}const resolvedPath=this.slothlet.helpers.resolver.resolvePathFromCaller(folderPath);try{const stats=await fs.stat(resolvedPath);if(!stats.isDirectory()){throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}}catch(error){if(error instanceof this.SlothletError){throw error}throw new this.SlothletError("INVALID_CONFIG_DIR_INVALID",{dir:resolvedPath,validationError:true})}return resolvedPath}buildDefaultModuleId(apiPath,____resolvedFolderPath){const randomSuffix=Math.random().toString(36).substring(2,8);const prefix=apiPath||"auto";return`${prefix}_${randomSuffix}`}getValueAtPath(root,parts){let current=root;for(const part of parts){if(!current||typeof current!=="object"&&typeof current!=="function"){return void 0}current=current[part]}return current}ensureParentPath(root,parts,options={}){const{moduleID,sourceFolder}=options;let current=root;for(let i=0;i<parts.length-1;i+=1){const part=parts[i];const next=current[part];if(next===void 0){const containerPath=parts.slice(0,i+1).join(".");const containerWrapper=new UnifiedWrapper(this.slothlet,{mode:this.____config.mode,apiPath:containerPath,moduleID,sourceFolder});containerWrapper.___setImpl({},moduleID);current[part]=containerWrapper.createProxy();current=current[part];continue}if(next&&(typeof next==="object"||typeof next==="function")){current=next;continue}throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:parts.slice(0,i+1).join("."),reason:translate("API_PATH_REASON_NOT_TRAVERSABLE"),index:void 0,segment:void 0,validationError:true})}return current}isWrapperProxy(value){return!!(value&&(typeof value==="object"||typeof value==="function")&&resolveWrapper(value)!==null)}async syncWrapper(existingProxy,nextProxy,config,collisionMode="replace",moduleID=null){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_ENTRY_EXISTING",apiPath:resolveWrapper(existingProxy)?.apiPath});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_ENTRY_NEXT",apiPath:resolveWrapper(nextProxy)?.apiPath})}if(!this.isWrapperProxy(existingProxy)||!this.isWrapperProxy(nextProxy)){return false}const existingWrapper=resolveWrapper(existingProxy)??existingProxy;const nextWrapper=resolveWrapper(nextProxy)??nextProxy;if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_EXISTING",apiPath:existingWrapper.apiPath});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT",apiPath:nextWrapper.apiPath})}if(nextWrapper.____slothletInternal.materializeFunc&&collisionMode!=="merge"){existingWrapper.____slothletInternal.materializeFunc=nextWrapper.____slothletInternal.materializeFunc}const existingChildKeys=Object.keys(existingWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));const nextChildKeys=Object.keys(nextWrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_BEFORE_MERGE",existingCacheSize:existingChildKeys.length,nextCacheSize:nextChildKeys.length});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT_IMPL_KEYS",implKeys:Object.keys(nextWrapper.____slothletInternal.impl||{})});this.slothlet.debug("api",{key:"DEBUG_MODE_SYNC_WRAPPER_NEXT_CHILDCACHE_KEYS",childCacheKeys:nextChildKeys})}if(collisionMode==="replace"){if(existingWrapper.___setImpl&&nextWrapper.____slothletInternal.impl!==void 0){existingWrapper.___setImpl(nextWrapper.____slothletInternal.impl,moduleID)}else if(nextWrapper.____slothletInternal.impl===void 0){existingWrapper.____slothletInternal.impl=null}else{if(nextWrapper.____slothletInternal.impl!==void 0){existingWrapper.____slothletInternal.impl=nextWrapper.____slothletInternal.impl;if(typeof nextWrapper.____slothletInternal.impl==="function"||nextWrapper.____slothletInternal.impl&&typeof nextWrapper.____slothletInternal.impl.default==="function"){existingWrapper.isCallable=true}}}for(const key of existingChildKeys){delete existingWrapper[key]}existingWrapper.___adoptImplChildren();for(const key of nextChildKeys){const childValue=nextWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else if(collisionMode==="merge"){for(const key of nextChildKeys){const isInternal=typeof key==="string"&&(key.startsWith("_")||key.startsWith("__"));if(!isInternal&&!Object.prototype.hasOwnProperty.call(existingWrapper,key)){const childValue=nextWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}else if(!isInternal){const existingChild=existingWrapper[key];const nextChild=nextWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(nextChild)){const syncWrapper_nextChildWrapper=resolveWrapper(nextChild)??nextChild;const syncWrapper_hasGrandChildren=Object.keys(syncWrapper_nextChildWrapper).some(k=>!k.startsWith("_")&&!k.startsWith("__"));if(syncWrapper_hasGrandChildren){await this.syncWrapper(existingChild,nextChild,config,collisionMode,moduleID)}}}}}else if(collisionMode==="merge-replace"){for(const key of nextChildKeys){const childValue=nextWrapper[key];const isInternal=typeof key==="string"&&(key.startsWith("_")||key.startsWith("__"));if(!isInternal&&Object.prototype.hasOwnProperty.call(existingWrapper,key)){const existingChild=existingWrapper[key];if(this.isWrapperProxy(existingChild)&&this.isWrapperProxy(childValue)){await this.syncWrapper(existingChild,childValue,config,collisionMode,moduleID)}else{delete existingWrapper[key];Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}else if(!isInternal){Object.defineProperty(existingWrapper,key,{value:childValue,writable:false,enumerable:true,configurable:true})}}}if(existingWrapper.____slothletInternal.state){const isActuallyMaterialized=existingWrapper.____slothletInternal.impl&&typeof existingWrapper.____slothletInternal.impl!=="function";existingWrapper.____slothletInternal.state.materialized=isActuallyMaterialized;existingWrapper.____slothletInternal.state.inFlight=false}return true}async mutateApiValue(existingValue,nextValue,options,config){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_CALLED",existingType:typeof existingValue,nextType:typeof nextValue});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_WRAPPER_STATUS",existingIsWrapper:this.isWrapperProxy(existingValue),nextIsWrapper:this.isWrapperProxy(nextValue)});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE",nextValue});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_NEXT_VALUE_KEYS",nextValueKeys:nextValue?Object.keys(nextValue):[]})}if(existingValue===nextValue){return}if(this.isWrapperProxy(existingValue)&&this.isWrapperProxy(nextValue)){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_SYNC_WRAPPERS"})}await this.syncWrapper(existingValue,nextValue,config,options.collisionMode,options.moduleID);return}if(this.isWrapperProxy(existingValue)&&!this.isWrapperProxy(nextValue)){const nextIsObjectLike=nextValue&&(typeof nextValue==="object"||typeof nextValue==="function");const nextHasKeys=nextIsObjectLike&&Object.keys(nextValue).length>0;if(nextHasKeys){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_MERGE_INTO_WRAPPER"});this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_MERGE_KEYS",keys:Object.keys(nextValue)})}await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue,nextValue,{removeMissing:options.removeMissing,mutateExisting:true,allowOverwrite:true,syncWrapper:this.syncWrapper.bind(this),collisionMode:options.collisionMode,moduleID:options.moduleID});return}const existingValueRaw=resolveWrapper(existingValue);if(existingValueRaw!==null){if(config?.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_MUTATE_API_VALUE_SETIMPL_FALLBACK"})}existingValueRaw.___setImpl(resolveWrapper(nextValue)?.__impl??nextValue);return}}if(existingValue&&typeof existingValue==="object"&&nextValue&&typeof nextValue==="object"){await this.slothlet.builders.apiAssignment.mergeApiObjects(existingValue,nextValue,{removeMissing:options.removeMissing,mutateExisting:true,allowOverwrite:true,syncWrapper:this.syncWrapper.bind(this),collisionMode:options.collisionMode,moduleID:options.moduleID});return existingValue}return nextValue}async setValueAtPath(root,parts,value,options){const parent=this.ensureParentPath(root,parts,{moduleID:options.moduleID,sourceFolder:options.sourceFolder});const finalKey=parts[parts.length-1];const existing=parent?parent[finalKey]:void 0;const collisionMode=options.collisionMode||"merge";const moduleID=options.moduleID;this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH",finalKey,existingType:typeof existing,valueType:typeof value,collisionMode,options});if(existing!==void 0){if(collisionMode==="error"){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath:parts.join("."),reason:translate("API_PATH_REASON_COLLISION_ERROR"),index:void 0,segment:void 0,validationError:true})}if(collisionMode==="skip"){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_SKIP_COLLISION",path:parts.join("."),mode:"skip"});return false}if(collisionMode==="warn"){if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_PATH_COLLISION",{apiPath:parts.join(".")})}return false}if(collisionMode==="replace"){const existingIsObject=typeof existing==="object"||typeof existing==="function";const valueIsObject=typeof value==="object"||typeof value==="function";if(existingIsObject&&valueIsObject){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_REPLACE_MERGE",path:parts.join("."),mode:"replace"});await this.mutateApiValue(existing,value,{removeMissing:false,allowOverwrite:true,collisionMode:"replace",moduleID},this.____config);return true}else{parent[finalKey]=value;return true}}if(collisionMode==="merge"||collisionMode==="merge-replace"){const existingIsObject=typeof existing==="object"||typeof existing==="function";const valueIsObject=typeof value==="object"||typeof value==="function";if(existingIsObject&&valueIsObject){this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_MERGE_PROPS",mode:collisionMode});await this.mutateApiValue(existing,value,{removeMissing:false,allowOverwrite:true,collisionMode},this.____config);return true}else{if(this.slothlet&&!this.____config?.silent){new this.SlothletWarning("WARNING_HOT_RELOAD_MERGE_PRIMITIVES",{apiPath:parts.join(".")})}return false}}}this.slothlet.debug("api",{key:"DEBUG_MODE_SET_VALUE_AT_PATH_ASSIGN",finalKey});parent[finalKey]=value;return true}async deletePath(root,parts){let current=root;const stack=[];for(const part of parts.slice(0,-1)){if(!current||typeof current!=="object"&&typeof current!=="function"){return false}stack.push({parent:current,key:part});current=current[part]}const finalKey=parts[parts.length-1];if(!current||typeof current!=="object"&&typeof current!=="function"){return false}if(!Object.prototype.hasOwnProperty.call(current,finalKey)){return false}const removedImpl=current[finalKey];const apiPath=parts.join(".");if(removedImpl&&this.slothlet.handlers?.lifecycle){const metadata=this.slothlet.handlers.metadata?.getMetadata?.(removedImpl);await this.slothlet.handlers.lifecycle.emit("impl:removed",{apiPath,impl:removedImpl,source:"removal",moduleID:metadata?.moduleID,filePath:metadata?.filePath,sourceFolder:metadata?.sourceFolder})}if(resolveWrapper(current)){const wrapper=resolveWrapper(current);const isInternal=typeof finalKey==="string"&&(finalKey.startsWith("_")||finalKey.startsWith("__"));if(!isInternal&&finalKey in wrapper){delete wrapper[finalKey]}if(wrapper.____slothletInternal.impl&&typeof wrapper.____slothletInternal.impl==="object"){delete wrapper.____slothletInternal.impl[finalKey]}}delete current[finalKey];if(removedImpl&&(typeof removedImpl==="object"||typeof removedImpl==="function")){if(resolveWrapper(removedImpl)){const wrapper=resolveWrapper(removedImpl);if(wrapper.____slothletInternal.impl!==void 0){wrapper.____slothletInternal.impl=null}const childKeys=Object.keys(wrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const key of childKeys){delete wrapper[key]}if(wrapper.____slothletInternal.state){wrapper.____slothletInternal.state.materialized=false;wrapper.____slothletInternal.state.inFlight=false}}}if(this.slothlet.handlers?.metadata){const rootSegment=apiPath.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}for(let i=stack.length-1;i>=0;i-=1){const{parent,key}=stack[i];const value=parent[key];if(value&&(typeof value==="object"||typeof value==="function")&&Object.keys(value).length===0){delete parent[key]}}return true}async restoreApiPath(apiPath,moduleID){const normalizedModuleId=moduleID||null;const historyEntry=this.state.addHistory.slice().reverse().find(entry=>entry.apiPath===apiPath&&(normalizedModuleId?entry.moduleID===normalizedModuleId:true));if(historyEntry){await this.addApiComponent({apiPath:historyEntry.apiPath,folderPath:historyEntry.folderPath,options:{...historyEntry.options,metadata:historyEntry.metadata,mutateExisting:true,forceOverwrite:true,collisionMode:"replace",recordHistory:false}});return}if(normalizedModuleId==="base"||normalizedModuleId==="core"){const baseApi=await this.slothlet.builders.builder.buildAPI({dir:this.____config.dir,mode:this.____config.mode,moduleID:"base"});const{parts}=this.normalizeApiPath(apiPath);let baseValue=this.getValueAtPath(baseApi,parts);if(baseValue===void 0){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);return}const baseValueRaw=resolveWrapper(baseValue);if(baseValue&&baseValueRaw!==null){baseValue=baseValueRaw.__impl}await this.setValueAtPath(this.slothlet.api,parts,baseValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:normalizedModuleId});await this.setValueAtPath(this.slothlet.boundApi,parts,baseValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:normalizedModuleId})}}async addApiComponent(params){const{apiPath,folderPath,options={},versionConfig=null}=params||{};if(Array.isArray(folderPath)){const moduleIDs=[];for(const singlePath of folderPath){const moduleID2=await this.addApiComponent({apiPath,folderPath:singlePath,options,versionConfig});moduleIDs.push(moduleID2)}return moduleIDs}const{metadata={},...restOptions}=options;if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"addApi",validationError:true})}const{apiPath:normalizedPath,parts}=this.normalizeApiPath(apiPath);let effectivePath=normalizedPath;let effectiveParts=parts;if(versionConfig?.version!==void 0&&versionConfig?.version!==null){if(normalizedPath===""){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_VERSIONED_ROOT"),index:void 0,segment:void 0,validationError:true})}if(typeof versionConfig.version!=="string"||!String(versionConfig.version).trim()){throw new this.SlothletError("INVALID_CONFIG_VERSION_TAG",{received:versionConfig.version,validationError:true})}const versionTag=String(versionConfig.version).trim();effectiveParts=[versionTag,...parts];effectivePath=effectiveParts.join(".")}const{resolvedPath,isDirectory,isFile}=await this.resolvePath(folderPath);if(!isDirectory&&!isFile){throw new this.SlothletError("INVALID_CONFIG_PATH_TYPE",{path:resolvedPath,validationError:true})}if(isFile){const ext=path.extname(resolvedPath);if(![".mjs",".cjs",".js"].includes(ext)){throw new this.SlothletError("INVALID_CONFIG_FILE_TYPE",{path:resolvedPath,extension:ext,validationError:true})}}const resolvedFolderPath=resolvedPath;let collisionMode;if(restOptions.forceOverwrite){collisionMode="replace"}else{collisionMode=restOptions.collisionMode||this.____config.api?.collision?.api||"error"}const mutateExisting=!!(restOptions.mutateExisting||collisionMode==="merge");const moduleID=restOptions.moduleID?String(restOptions.moduleID):this.buildDefaultModuleId(normalizedPath,resolvedFolderPath);if(restOptions.forceOverwrite&&!moduleID){throw new this.SlothletError("INVALID_CONFIG_FORCE_OVERWRITE_REQUIRES_MODULE_ID",{apiPath:normalizedPath,validationError:true})}let dirForBuild=resolvedFolderPath;let fileFilter=null;if(isFile){dirForBuild=path.dirname(resolvedFolderPath);const fileName=path.basename(resolvedFolderPath);fileFilter=file=>file===fileName}const newApi=await this.slothlet.builders.builder.buildAPI({dir:dirForBuild,mode:this.____config.mode,apiPathPrefix:effectivePath,collisionContext:"addApi",moduleID,collisionMode,fileFilter});if(this.slothlet.handlers.apiCacheManager){this.slothlet.handlers.apiCacheManager.set(moduleID,{endpoint:effectivePath,moduleID,api:newApi,folderPath:resolvedFolderPath,mode:this.____config.mode,sanitizeOptions:this.____config.sanitize||{},collisionMode,config:{...this.____config},timestamp:Date.now()})}this.slothlet.debug("api",{key:"DEBUG_MODE_ADD_API_COMPONENT_BUILD_RETURN",topLevelKeys:Object.keys(newApi),dottedKeys:Object.keys(newApi).filter(k=>k.includes(".")),wrappers:Object.keys(newApi).filter(k=>resolveWrapper(newApi[k])!==null).map(k=>{const _w=resolveWrapper(newApi[k]);return{key:k,apiPath:_w.apiPath,implKeys:Object.keys(_w.____slothletInternal.impl||{}),childCacheSize:Object.keys(_w).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__")).length,childCacheKeys:Object.keys(_w).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))}}),nonWrappers:Object.keys(newApi).filter(k=>resolveWrapper(newApi[k])===null).map(k=>({key:k,type:typeof newApi[k]}))});let apiToMerge=newApi;if(isFile&&Object.keys(newApi).length===1){const fileName=Object.keys(newApi)[0];apiToMerge=newApi[fileName]}if(!isFile&&normalizedPath){const lastPart=normalizedPath.includes(".")?normalizedPath.split(".").pop():normalizedPath;if(lastPart&&Object.prototype.hasOwnProperty.call(apiToMerge,lastPart)){const dupValue=apiToMerge[lastPart];const dupType=typeof dupValue;if(dupValue!==null&&(dupType==="object"||dupType==="function")){const dupWrapper=resolveWrapper(dupValue);const dupFilePath=dupWrapper?.____slothletInternal?.filePath;const dupFileDir=dupFilePath?dupFilePath.replace(/\\/g,"/").split("/").slice(0,-1).join("/"):null;const normalizedFolderPath=resolvedFolderPath.replace(/\\/g,"/").replace(/\/$/,"");const expectedDir=normalizedFolderPath+"/"+lastPart;const isDirectChild=dupFileDir===expectedDir||dupFileDir===normalizedFolderPath;if(isDirectChild){const hoisted={};for(const k of Object.keys(apiToMerge)){if(k!==lastPart)hoisted[k]=apiToMerge[k]}if(dupWrapper){for(const k of Object.keys(dupWrapper).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))){hoisted[k]=dupWrapper[k]}}else{for(const k of Object.keys(dupValue)){hoisted[k]=dupValue[k]}}apiToMerge=hoisted;this.slothlet.debug("api",{key:"DEBUG_MODE_RULE_13_DEDUP_HOISTED_KEY",lastPart,newKeys:Object.keys(apiToMerge)})}}}}if(this.____config.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_ADD_API_COMPONENT_MERGE_KEYS",keys:Object.keys(apiToMerge),isRootLevel:parts.length===0})}let anyAssignmentSucceeded=false;if(parts.length===0){for(const key of Object.keys(newApi)){const result1=await this.setValueAtPath(this.slothlet.api,[key],newApi[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,[key],newApi[key],{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(result1||result2){anyAssignmentSucceeded=true}}}else{if(resolveWrapper(apiToMerge)===null){const isCallableNamespace=typeof apiToMerge==="function";const containerWrapper=new UnifiedWrapper(this.slothlet,{apiPath:effectivePath,mode:this.____config.mode,isCallable:isCallableNamespace,moduleID,filePath:resolvedFolderPath,sourceFolder:resolvedFolderPath});containerWrapper.___setImpl(apiToMerge,moduleID);apiToMerge=containerWrapper.createProxy()}const result1=await this.setValueAtPath(this.slothlet.api,effectiveParts,apiToMerge,{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});const result2=await this.setValueAtPath(this.slothlet.boundApi,effectiveParts,apiToMerge,{mutateExisting,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(result1||result2){anyAssignmentSucceeded=true}}if(anyAssignmentSucceeded){const pendingMaterializations=[];const seenWrappers=new Set;const collectPendingMaterializations=(obj,depth=0)=>{if(!obj||typeof obj!=="object"||depth>10)return;if(obj.__isVersionDispatcher===true)return;const wrapper=resolveWrapper(obj);if(wrapper){if(seenWrappers.has(wrapper))return;seenWrappers.add(wrapper);if(wrapper.____slothletInternal.materializationPromise){pendingMaterializations.push(wrapper.____slothletInternal.materializationPromise)}const childKeys=Object.keys(wrapper).filter(k=>!k.startsWith("_")&&!k.startsWith("__"));for(const key of childKeys){collectPendingMaterializations(wrapper[key],depth+1)}}for(const key of Object.keys(obj)){if(key!=="____slothletInternal"){collectPendingMaterializations(obj[key],depth+1)}}};if(effectiveParts.length===0){for(const key of Object.keys(newApi)){if(this.slothlet.api[key]){collectPendingMaterializations(this.slothlet.api[key])}}}else{let current=this.slothlet.api;for(const part of effectiveParts){if(current&¤t[part]){current=current[part]}else{break}}if(current){collectPendingMaterializations(current)}}if(pendingMaterializations.length>0){if(this.____config.debug?.api){this.slothlet.debug("api",{key:"DEBUG_MODE_AWAITING_PENDING_MATERIALIZATIONS",count:pendingMaterializations.length,apiPath:normalizedPath})}await Promise.all(pendingMaterializations)}}if(anyAssignmentSucceeded&&metadata&&Object.keys(metadata).length>0&&this.slothlet.handlers.metadata){if(parts.length===0){for(const key of Object.keys(newApi)){this.slothlet.handlers.metadata.registerUserMetadata(key,metadata)}}else{const rootSegment=effectiveParts[0];this.slothlet.handlers.metadata.registerUserMetadata(rootSegment,metadata)}}if(this.slothlet.handlers.ownership&&moduleID){this.slothlet.handlers.ownership.registerSubtree(apiToMerge,moduleID,effectivePath)}if(this.slothlet.handlers.ownership){if(restOptions.recordHistory!==false){this.state.addHistory.push({apiPath:normalizedPath,folderPath:resolvedFolderPath,options:{...restOptions,metadata,moduleID},moduleID,versionConfig:versionConfig||null});this.state.operationHistory.push({type:"add",apiPath:normalizedPath,folderPath:resolvedFolderPath,options:{...restOptions,metadata,moduleID},moduleID,versionConfig:versionConfig||null})}}if(versionConfig?.version&&this.slothlet.handlers.versionManager){const versionTag=String(versionConfig.version).trim();try{this.slothlet.handlers.versionManager.registerVersion(normalizedPath,versionTag,moduleID,versionConfig.metadata??{},versionConfig.default??false)}catch(error){await this._rollbackFailedVersionedAdd({moduleID,effectivePath,normalizedPath});throw error}}if(restOptions.permissions&&this.slothlet.handlers?.permissionManager){const perms=restOptions.permissions;const callerPattern=`${normalizedPath}.**`;if(Array.isArray(perms.deny)){for(const target of perms.deny){this.slothlet.handlers.permissionManager.addRule({caller:callerPattern,target,effect:"deny"},moduleID)}}if(Array.isArray(perms.allow)){for(const target of perms.allow){this.slothlet.handlers.permissionManager.addRule({caller:callerPattern,target,effect:"allow"},moduleID)}}}return moduleID}async _rollbackFailedVersionedAdd({moduleID,effectivePath,normalizedPath}){let addIndex=-1;for(let i=this.state.operationHistory.length-1;i>=0;i--){const entry=this.state.operationHistory[i];if(entry?.type==="add"&&entry?.apiPath===normalizedPath&&entry?.moduleID===moduleID){addIndex=i;break}}if(addIndex!==-1){this.state.operationHistory.splice(addIndex,1)}this.state.addHistory=this.state.addHistory.filter(entry=>entry?.moduleID!==moduleID);try{await this.removeApiComponent(moduleID||effectivePath,{recordHistory:false})}catch{}}async removeApiComponent(pathOrModuleId,options={}){const recordHistory=options.recordHistory!==false;if(typeof pathOrModuleId!=="string"||!pathOrModuleId){throw new this.SlothletError("INVALID_ARGUMENT",{argument:"pathOrModuleId",expected:"non-empty string",received:typeof pathOrModuleId,validationError:true})}let apiPath=null;let moduleID=null;if(this.slothlet.handlers.ownership){const candidateModuleID=pathOrModuleId.split(":")[0];const registeredModules=Array.from(this.slothlet.handlers.ownership.moduleToPath.keys());let matchingModule=null;for(let i=registeredModules.length-1;i>=0;i--){const candidate=registeredModules[i];if(candidate===candidateModuleID||candidate.startsWith(`${candidateModuleID}_`)){matchingModule=candidate;break}}if(matchingModule){moduleID=matchingModule}else{const owner=this.slothlet.handlers.ownership.getCurrentOwner(pathOrModuleId);if(owner){apiPath=pathOrModuleId;moduleID=owner.moduleID}else{return false}}}else{const isModuleId=!pathOrModuleId.includes(".");apiPath=isModuleId?null:pathOrModuleId;moduleID=isModuleId?pathOrModuleId.split(":")[0]:null}if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"removeApi",validationError:true})}if(apiPath&&moduleID){const normalizedPath2=this.normalizeApiPath(apiPath).apiPath;const moduleIDKey=String(moduleID);const history=this.slothlet.handlers.ownership?.getPathHistory?.(normalizedPath2)||[];const ownershipResult2=this.slothlet.handlers.ownership?.removePath?.(normalizedPath2,moduleIDKey)||{action:"none",removedModuleId:null,restoreModuleId:null};const pathParts=this.normalizeApiPath(apiPath).parts;if(ownershipResult2.action==="delete"){await this.deletePath(this.slothlet.api,pathParts);await this.deletePath(this.slothlet.boundApi,pathParts);if(this.slothlet.handlers.metadata){const rootSegment=normalizedPath2.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}if(this.slothlet.handlers.versionManager){const versionKey=this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);if(versionKey){this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath,versionKey.versionTag)}if(this.slothlet.handlers.versionManager.hasDispatcher(normalizedPath2)){this.slothlet.handlers.versionManager.teardownDispatcher(normalizedPath2)}}this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}if(ownershipResult2.action==="restore"){const restoredValue=this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath2);const restoredModuleId=this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath2)?.moduleID;if(restoredValue!==void 0&&restoredModuleId){await this.setValueAtPath(this.slothlet.api,pathParts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});await this.setValueAtPath(this.slothlet.boundApi,pathParts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}await this.restoreApiPath(normalizedPath2,ownershipResult2.restoreModuleId);this.state.operationHistory.push({type:"remove",apiPath:normalizedPath2});return true}if(ownershipResult2.action==="none"&&history.length===0){await this.deletePath(this.slothlet.api,pathParts);await this.deletePath(this.slothlet.boundApi,pathParts);return true}return false}if(moduleID){const moduleIDKey=String(moduleID);const result=this.slothlet.handlers.ownership?.unregister?.(moduleIDKey)||{removed:[],rolledBack:[]};if(this.slothlet.handlers.versionManager){const versionKey=this.slothlet.handlers.versionManager.getVersionKeyForModule(moduleIDKey);if(versionKey){this.slothlet.handlers.versionManager.unregisterVersion(versionKey.logicalPath,versionKey.versionTag)}}const allPaths=[...result.removed,...result.rolledBack.map(r=>r.apiPath)];const uniquePaths=[...new Set(allPaths)];const pathsToDelete=[];const pathsToRollback=[];for(const path2 of uniquePaths){const currentOwner=this.slothlet.handlers.ownership?.getCurrentOwner?.(path2);const hasChildrenWithOtherOwners=uniquePaths.some(p=>{if(p===path2||!p.startsWith(path2+"."))return false;const childOwner=this.slothlet.handlers.ownership?.getCurrentOwner?.(p);return childOwner&&childOwner.moduleID!==moduleIDKey});if(currentOwner&¤tOwner.moduleID!==moduleIDKey){pathsToRollback.push({apiPath:path2,restoredTo:currentOwner.moduleID})}else if(!hasChildrenWithOtherOwners){pathsToDelete.push(path2)}}pathsToDelete.sort((a,b)=>{const depthA=(a.match(/\./g)||[]).length;const depthB=(b.match(/\./g)||[]).length;return depthB-depthA});for(const removedPath of pathsToDelete){const{parts:parts2}=this.normalizeApiPath(removedPath);await this.deletePath(this.slothlet.api,parts2);await this.deletePath(this.slothlet.boundApi,parts2);if(this.slothlet.handlers.metadata){const rootSegment=removedPath.split(".")[0];this.slothlet.handlers.metadata.removeUserMetadataByApiPath(rootSegment)}}if(pathsToDelete.length>0){const rootSegment=pathsToDelete[0].split(".")[0];if(rootSegment in this.slothlet.api){delete this.slothlet.api[rootSegment]}if(rootSegment in this.slothlet.boundApi){delete this.slothlet.boundApi[rootSegment]}}for(const rollback of pathsToRollback){const{parts:parts2}=this.normalizeApiPath(rollback.apiPath);const previousImpl=this.slothlet.handlers.ownership?.getCurrentValue?.(rollback.apiPath);if(previousImpl!==void 0){const existingWrapper=this.getValueAtPath(this.slothlet.api,parts2);const existingWrapperRaw=resolveWrapper(existingWrapper);if(existingWrapperRaw){existingWrapperRaw.___setImpl(previousImpl,rollback.restoredTo)}const existingBoundWrapper=this.getValueAtPath(this.slothlet.boundApi,parts2);const existingBoundWrapperRaw=resolveWrapper(existingBoundWrapper);if(existingBoundWrapperRaw){existingBoundWrapperRaw.___setImpl(previousImpl,rollback.restoredTo)}}}this.state.addHistory=this.state.addHistory.filter(entry=>String(entry.moduleID)!==moduleIDKey);if(this.slothlet.handlers.apiCacheManager){const deleted=this.slothlet.handlers.apiCacheManager.delete(moduleIDKey);if(deleted){this.slothlet.debug("cache",{key:"DEBUG_MODE_CACHE_DELETED_MODULE_REMOVED",moduleID:moduleIDKey})}}if(recordHistory&&pathsToDelete.length>0){const rootSegment=pathsToDelete[0].split(".")[0];this.state.operationHistory.push({type:"remove",apiPath:rootSegment})}return pathsToDelete.length>0||pathsToRollback.length>0}if(!apiPath){throw new this.SlothletError("INVALID_CONFIG_API_PATH_INVALID",{apiPath,reason:translate("API_PATH_REASON_REQUIRED"),index:void 0,segment:void 0,validationError:true})}const{apiPath:normalizedPath,parts}=this.normalizeApiPath(apiPath);const ownershipResult=this.slothlet.handlers.ownership?.removePath?.(normalizedPath,null)||{action:"none",removedModuleId:null,restoreModuleId:null};const pathExists=this.getValueAtPath(this.slothlet.api,parts)!==void 0;if(ownershipResult.action==="none"){if(pathExists){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);if(this.slothlet.handlers.metadata){this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath)}if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}return false}if(ownershipResult.action==="delete"){await this.deletePath(this.slothlet.api,parts);await this.deletePath(this.slothlet.boundApi,parts);if(this.slothlet.handlers.metadata){this.slothlet.handlers.metadata.removeUserMetadataByApiPath(normalizedPath)}if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}if(ownershipResult.action==="restore"){const restoredValue=this.slothlet.handlers.ownership?.getCurrentValue?.(normalizedPath);const restoredModuleId=this.slothlet.handlers.ownership?.getCurrentOwner?.(normalizedPath)?.moduleID;if(restoredValue!==void 0&&restoredModuleId){await this.setValueAtPath(this.slothlet.api,parts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});await this.setValueAtPath(this.slothlet.boundApi,parts,restoredValue,{mutateExisting:true,allowOverwrite:true,collisionMode:"replace",moduleID:restoredModuleId});if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}await this.restoreApiPath(normalizedPath,ownershipResult.restoreModuleId);if(recordHistory){this.state.operationHistory.push({type:"remove",apiPath:normalizedPath})}return true}return false}async reloadApiComponent(params){const{apiPath,moduleID,options}=params||{};if(!this.slothlet||!this.slothlet.isLoaded){throw new this.SlothletError("INVALID_CONFIG_NOT_LOADED",{operation:"reloadApi",validationError:true})}if(moduleID){await this._reloadByModuleID(moduleID);return}if(apiPath){await this._reloadByApiPath(apiPath,options);return}throw new this.SlothletError("INVALID_ARGUMENT",{argument:"params",expected:"{ moduleID } or { apiPath }",received:params,validationError:true})}async _reloadByModuleID(moduleID,{forceReplace=true}={}){const cacheManager=this.slothlet.handlers.apiCacheManager;if(!cacheManager){throw new this.SlothletError("CACHE_MANAGER_NOT_AVAILABLE",{operation:"reload",validationError:true})}if(!cacheManager.has(moduleID)){throw new this.SlothletError("CACHE_NOT_FOUND",{moduleID,operation:"reload",validationError:true})}const oldEntry=cacheManager.get(moduleID);this.slothlet.debug("reload",{key:"DEBUG_MODE_RELOADING_MODULE_BY_ID",moduleID,endpoint:oldEntry.endpoint,folderPath:oldEntry.folderPath});const freshApi=await cacheManager.rebuildCache(moduleID);cacheManager.set(moduleID,{...oldEntry,api:freshApi,timestamp:Date.now()});this.slothlet.debug("reload",{key:"DEBUG_MODE_FRESH_API_KEYS_BEFORE_RESTORE",moduleID,endpoint:oldEntry.endpoint,freshApiKeys:Object.keys(freshApi||{})});await this._restoreApiTree(freshApi,oldEntry.endpoint,moduleID,oldEntry.collisionMode,forceReplace);this.slothlet.debug("reload",{key:"DEBUG_MODE_FRESH_API_KEYS_AFTER_RESTORE",moduleID,endpoint:oldEntry.endpoint,freshApiKeys:Object.keys(freshApi||{})});this.slothlet.debug("reload",{key:"DEBUG_MODE_MODULE_RELOAD_COMPLETE",moduleID});if(this.slothlet.handlers.versionManager){this.slothlet.handlers.versionManager.onVersionedModuleReload(moduleID)}}async _reloadByApiPath(apiPath,options={}){this.slothlet.debug("reload",{key:"DEBUG_MODE_RELOADING_BY_API_PATH",apiPath});const moduleIDsToReload=this._findAffectedCaches(apiPath);if(moduleIDsToReload.length===0){this.slothlet.debug("reload",{key:"DEBUG_MODE_NO_CACHES_ATTEMPTING_RESTORE",apiPath});if(apiPath!=="."&&apiPath!==""){await this.restoreApiPath(apiPath,"base")}return}const cacheManager=this.slothlet.handlers.apiCacheManager;moduleIDsToReload.sort((a,b)=>{const entryA=cacheManager.get(a);const entryB=cacheManager.get(b);if(entryA?.endpoint==="."&&entryB?.endpoint!==".")return-1;if(entryB?.endpoint==="."&&entryA?.endpoint!==".")return 1;const indexA=this.state.addHistory.findIndex(h=>h.moduleID===a);const indexB=this.state.addHistory.findIndex(h=>h.moduleID===b);return indexA-indexB});const endpointOrder=new Map;for(const moduleID of moduleIDsToReload){const entry=cacheManager.get(moduleID);const ep=entry?.endpoint??".";if(!endpointOrder.has(ep))endpointOrder.set(ep,[]);endpointOrder.get(ep).push(moduleID)}for(const[,moduleIDs]of endpointOrder){for(let i=0;i<moduleIDs.length;i++){await this._reloadByModuleID(moduleIDs[i],{forceReplace:i===0})}}const reloadMetadata=options?.metadata;if(reloadMetadata&&typeof reloadMetadata==="object"&&Object.keys(reloadMetadata).length>0){if(this.slothlet.handlers.metadata){const targetPath=apiPath==="."?null:apiPath.split(".")[0];if(targetPath){this.slothlet.handlers.metadata.registerUserMetadata(targetPath,reloadMetadata)}}}this.slothlet.debug("reload",{key:"DEBUG_MODE_API_PATH_RELOAD_COMPLETE",apiPath,reloadedModules:moduleIDsToReload.length,loadOrder:moduleIDsToReload})}_findAffectedCaches(apiPath){const cacheManager=this.slothlet.handlers.apiCacheManager;if(!cacheManager)return[];const allModuleIDs=cacheManager.getAllModuleIDs();if(apiPath==="."||apiPath===""||apiPath==null){const baseModules=[];for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry&&entry.endpoint==="."){baseModules.push(moduleID)}}return baseModules}const exactMatches=[];for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry&&entry.endpoint===apiPath){exactMatches.push(moduleID)}}if(exactMatches.length>0)return exactMatches;const children=[];const pathPrefix=apiPath+".";for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(entry?.endpoint?.startsWith(pathPrefix)){children.push(moduleID)}}if(children.length>0)return children;const ownership=this.slothlet.handlers.ownership;const history=ownership?.getPathHistory?.(apiPath);if(history&&history.length>0){const owned=[];for(const{moduleID}of history){if(cacheManager.has(moduleID)){owned.push(moduleID)}}if(owned.length>0)return owned}let bestMatch=null;let bestLength=-1;for(const moduleID of allModuleIDs){const entry=cacheManager.get(moduleID);if(!entry?.endpoint)continue;const ep=entry.endpoint;if(ep==="."||apiPath.startsWith(ep+".")){if(ep.length>bestLength){bestLength=ep.length;bestMatch=moduleID}}}if(bestMatch)return[bestMatch];return[]}_collectCustomProperties(existingProxy,freshApi){const customProps={};if(!existingProxy||typeof existingProxy!=="object"&&typeof existingProxy!=="function"){return customProps}const wrapper=resolveWrapper(existingProxy);if(!wrapper){return customProps}const freshKeys=new Set(freshApi?Object.keys(freshApi):[]);const ownKeys=Object.keys(wrapper).filter(k=>!ComponentBase.INTERNAL_KEYS.has(k));for(const key of ownKeys){try{const val=wrapper[key];if(val&&(typeof val==="object"||typeof val==="function")&&resolveWrapper(val)){continue}if(!freshKeys.has(key)){customProps[key]=val}else{customProps[key]=val}}catch{}}return customProps}_restoreCustomProperties(proxy,customProps){if(!proxy||!customProps||typeof customProps!=="object"){return}for(const[key,value]of Object.entries(customProps)){try{proxy[key]=value}catch{}}}async _restoreApiTree(freshApi,endpoint,moduleID,collisionMode,forceReplace=true){if(!freshApi||typeof freshApi!=="object"&&typeof freshApi!=="function"){return}const parts=endpoint==="."?[]:endpoint.split(".");if(parts.length===0){for(const key of Object.keys(freshApi)){if(typeof key==="string"&&(key.startsWith("_")||key.startsWith("__")))continue;if(key==="slothlet"||key==="shutdown"||key==="destroy")continue;const existingAtKey=this.slothlet.api[key];const freshValue=freshApi[key];if(existingAtKey&&resolveWrapper(existingAtKey)!==null){const customProps=this._collectCustomProperties(existingAtKey,freshValue);const freshWrapper=resolveWrapper(freshValue);const isLazyFresh=freshWrapper&&freshWrapper.____slothletInternal.mode==="lazy"&&!freshWrapper.____slothletInternal.state.materialized&&typeof freshWrapper.____slothletInternal.materializeFunc==="function";this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_ROOT_KEY_INSPECT",rootKey:key,hasFreshWrapper:!!freshWrapper,freshMode:freshWrapper?.____slothletInternal.mode,freshMaterialized:freshWrapper?.____slothletInternal.state?.materialized,hasMaterializeFunc:typeof freshWrapper?.____slothletInternal.materializeFunc==="function",isLazyFresh,existingMaterialized:resolveWrapper(existingAtKey)?.____slothletInternal?.state?.materialized});if(isLazyFresh){resolveWrapper(existingAtKey).___resetLazy(freshWrapper.____slothletInternal.materializeFunc);this._restoreCustomProperties(existingAtKey,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_ROOT_KEY_RESET_LAZY",rootKey:key,restoredCustomProps:Object.keys(customProps)})}else{let implForReload;if(freshValue&&resolveWrapper(freshValue)!==null){implForReload=freshWrapper?UnifiedWrapper._extractFullImpl(freshWrapper):freshValue}else{implForReload=freshValue}if(typeof implForReload==="function"){const extracted={};for(const k of Object.keys(implForReload)){extracted[k]=implForReload[k]}implForReload=extracted}const wrapper=resolveWrapper(existingAtKey);const originalCollisionMode=wrapper?wrapper.____slothletInternal.state.collisionMode:null;if(forceReplace&&wrapper){wrapper.____slothletInternal.state.collisionMode="replace"}if(wrapper&&originalCollisionMode!==null){wrapper.____slothletInternal.state.collisionMode=originalCollisionMode}this._restoreCustomProperties(existingAtKey,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_ROOT_KEY_UPDATED_SETIMPL",rootKey:key,restoredCustomProps:Object.keys(customProps)})}}else if(existingAtKey===void 0){const cacheManager=this.slothlet.handlers.apiCacheManager;const cacheEntry=cacheManager.get(moduleID);const resolvedFolderPath=cacheEntry?.folderPath||"";await this.setValueAtPath(this.slothlet.api,[key],freshValue,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(this.slothlet.boundApi){await this.setValueAtPath(this.slothlet.boundApi,[key],freshValue,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath})}}}}else{const existing=this.getValueAtPath(this.slothlet.api,parts);this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_NESTED_PATH",endpoint,moduleID,partsPath:parts.join("."),existingFound:!!existing,hasSetImpl:existing?resolveWrapper(existing)!==null:false,freshApiKeys:Object.keys(freshApi||{})});if(existing&&resolveWrapper(existing)!==null){const customProps=this._collectCustomProperties(existing,freshApi);const wrapper=resolveWrapper(existing);const originalCollisionMode=wrapper?wrapper.____slothletInternal.state.collisionMode:null;if(forceReplace&&wrapper){wrapper.____slothletInternal.state.collisionMode="replace";this.slothlet.debug("reload",{key:"DEBUG_MODE_RESTORE_FORCING_REPLACE",endpoint,originalCollisionMode,wrapperApiPath:wrapper.____slothletInternal.apiPath})}let implForReload;if(resolveWrapper(freshApi)!==null){const freshWrapper=resolveWrapper(freshApi);implForReload=freshWrapper?UnifiedWrapper._extractFullImpl(freshWrapper):freshApi}else if(typeof freshApi==="function"){implForReload={};for(const key of Object.keys(freshApi)){implForReload[key]=freshApi[key]}}else{implForReload=freshApi}if(parts.length>0&&implForReload&&typeof implForReload==="object"){const lastEndpointPart=parts[parts.length-1];if(lastEndpointPart&&Object.prototype.hasOwnProperty.call(implForReload,lastEndpointPart)){const dupValue=implForReload[lastEndpointPart];const dupWrapperForDedup=resolveWrapper(dupValue);if(dupWrapperForDedup){const hoisted={};for(const k of Object.keys(implForReload)){if(k!==lastEndpointPart)hoisted[k]=implForReload[k]}for(const k of Object.keys(dupWrapperForDedup).filter(k2=>!k2.startsWith("_")&&!k2.startsWith("__"))){hoisted[k]=dupWrapperForDedup[k]}implForReload=hoisted}}}if(implForReload&&typeof implForReload==="object"){for(const key of Object.keys(implForReload)){const val=implForReload[key];if(resolveWrapper(val)!==null){const childWrapper=resolveWrapper(val);if(childWrapper.____slothletInternal.state.materialized){implForReload[key]=UnifiedWrapper._extractFullImpl(childWrapper)}}}}resolveWrapper(existing).___setImpl(implForReload,moduleID);if(wrapper&&originalCollisionMode!==null){wrapper.____slothletInternal.state.collisionMode=originalCollisionMode}this._restoreCustomProperties(existing,customProps);this.slothlet.debug("reload",{key:"DEBUG_MODE_UPDATED_WRAPPER_IMPL",endpoint,moduleID,forcedReplaceMode:true,restoredCustomProps:Object.keys(customProps)})}else{const cacheManager=this.slothlet.handlers.apiCacheManager;const cacheEntry=cacheManager.get(moduleID);const resolvedFolderPath=cacheEntry?.folderPath||"";let implForContainer=freshApi;if(typeof freshApi==="function"){implForContainer={};for(const key of Object.keys(freshApi)){implForContainer[key]=freshApi[key]}}const containerWrapper=new UnifiedWrapper(this.slothlet,{apiPath:endpoint,mode:this.____config.mode,moduleID,filePath:resolvedFolderPath,sourceFolder:resolvedFolderPath});containerWrapper.___setImpl(implForContainer,moduleID);const apiToSet=containerWrapper.createProxy();await this.setValueAtPath(this.slothlet.api,parts,apiToSet,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath});if(this.slothlet.boundApi){await this.setValueAtPath(this.slothlet.boundApi,parts,apiToSet,{mutateExisting:true,collisionMode,moduleID,sourceFolder:resolvedFolderPath})}this.slothlet.debug("reload",{key:"DEBUG_MODE_CREATED_NEW_WRAPPER_UNEXPECTED",endpoint,moduleID})}}}}export{ApiManager};
|