@cldmv/slothlet-types 3.15.3 → 3.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/lib/builders/api-assignment.d.mts +125 -4
  2. package/lib/builders/api_builder.d.mts +104 -7
  3. package/lib/builders/builder.d.mts +82 -1
  4. package/lib/builders/modes-processor.d.mts +66 -3
  5. package/lib/errors.d.mts +114 -19
  6. package/lib/factories/component-base.d.mts +171 -8
  7. package/lib/factories/context.d.mts +22 -4
  8. package/lib/handlers/api-cache-manager.d.mts +209 -20
  9. package/lib/handlers/api-manager.d.mts +539 -38
  10. package/lib/handlers/context-async.d.mts +92 -25
  11. package/lib/handlers/context-live.d.mts +117 -30
  12. package/lib/handlers/framework-internals.d.mts +33 -2
  13. package/lib/handlers/hook-manager.d.mts +306 -73
  14. package/lib/handlers/lifecycle-token.d.mts +48 -3
  15. package/lib/handlers/lifecycle.d.mts +86 -5
  16. package/lib/handlers/materialize-manager.d.mts +76 -8
  17. package/lib/handlers/metadata.d.mts +238 -18
  18. package/lib/handlers/module-manager.d.mts +169 -21
  19. package/lib/handlers/ownership.d.mts +376 -45
  20. package/lib/handlers/permission-manager.d.mts +283 -46
  21. package/lib/handlers/routine-manager.d.mts +425 -0
  22. package/lib/handlers/trusted-root.d.mts +45 -4
  23. package/lib/handlers/unified-wrapper.d.mts +287 -26
  24. package/lib/handlers/version-manager.d.mts +236 -29
  25. package/lib/helpers/caller-pinning.d.mts +21 -2
  26. package/lib/helpers/class-instance-wrapper.d.mts +56 -2
  27. package/lib/helpers/config.d.mts +311 -161
  28. package/lib/helpers/defaults.d.mts +39 -0
  29. package/lib/helpers/eventemitter-context.d.mts +29 -3
  30. package/lib/helpers/eventtarget-context.d.mts +19 -1
  31. package/lib/helpers/eventtarget-property-context.d.mts +21 -0
  32. package/lib/helpers/generate-manifest.d.mts +174 -7
  33. package/lib/helpers/hint-detector.d.mts +22 -2
  34. package/lib/helpers/manifest-resolver.d.mts +100 -1
  35. package/lib/helpers/modes-utils.d.mts +30 -3
  36. package/lib/helpers/module-discovery.d.mts +80 -7
  37. package/lib/helpers/module-manifest-validator.d.mts +36 -13
  38. package/lib/helpers/module-sort.d.mts +64 -1
  39. package/lib/helpers/observer-context.d.mts +21 -0
  40. package/lib/helpers/pattern-matcher.d.mts +43 -3
  41. package/lib/helpers/platform.d.mts +109 -10
  42. package/lib/helpers/resolve-from-caller.d.mts +27 -3
  43. package/lib/helpers/sanitize.d.mts +92 -4
  44. package/lib/helpers/scheduler-context.d.mts +21 -1
  45. package/lib/helpers/utilities.d.mts +52 -4
  46. package/lib/i18n/translations.d.mts +50 -5
  47. package/lib/modes/eager.d.mts +46 -8
  48. package/lib/modes/lazy.d.mts +57 -10
  49. package/lib/processors/flatten.d.mts +116 -56
  50. package/lib/processors/loader.d.mts +77 -10
  51. package/lib/processors/type-generator.d.mts +16 -2
  52. package/lib/processors/typescript.d.mts +169 -13
  53. package/lib/runtime/runtime-asynclocalstorage.d.mts +71 -3
  54. package/lib/runtime/runtime-livebindings.d.mts +37 -2
  55. package/lib/runtime/runtime.d.mts +39 -3
  56. package/lib/typegen/typegen.d.mts +34 -2
  57. package/package.json +5 -23
  58. package/slothlet.d.mts +428 -3
@@ -1,49 +1,550 @@
1
+ /**
2
+ * Manages runtime API component lifecycle (add/remove/reload).
3
+ * @class ApiManager
4
+ * @extends ComponentBase
5
+ * @package
6
+ *
7
+ * @description
8
+ * Class-based handler for managing API components at runtime. Tracks add history,
9
+ * removed module IDs, and initial configuration per instance. Extends ComponentBase
10
+ * for common Slothlet property access (config, debug, api, error classes, etc.).
11
+ *
12
+ * @example
13
+ * const manager = new ApiManager(slothlet);
14
+ * await manager.addApiComponent({ apiPath: "plugins", folderPath: "./plugins" });
15
+ */
1
16
  export class ApiManager extends ComponentBase {
2
17
  static slothletProperty: string;
18
+ /**
19
+ * Create an ApiManager instance.
20
+ * @param {object} slothlet - Slothlet class instance.
21
+ * @package
22
+ *
23
+ * @description
24
+ * Initializes manager state with empty add history, removed module tracking,
25
+ * and stores the initial configuration.
26
+ *
27
+ * @example
28
+ * const manager = new ApiManager(slothlet);
29
+ */
30
+ constructor(slothlet: object);
31
+ /** @type {{ addHistory: object[], initialConfig: object|null, operationHistory: object[], replaceShadows: Map<string, object[]> }} */
3
32
  state: {
4
- addHistory: never[];
5
- initialConfig: any;
6
- operationHistory: never[];
7
- replaceShadows: Map<any, any>;
33
+ addHistory: object[];
34
+ initialConfig: object | null;
35
+ operationHistory: object[];
36
+ replaceShadows: Map<string, object[]>;
8
37
  };
9
- normalizeApiPath(apiPath: any): {
38
+ /**
39
+ * Normalize and validate an API path.
40
+ * @param {string|string[]} apiPath - Dot-delimited API path, array of path segments, or empty/null for root.
41
+ * @returns {{ apiPath: string, parts: string[] }} Normalized path data.
42
+ * @throws {SlothletError} When apiPath is invalid.
43
+ * @private
44
+ *
45
+ * @description
46
+ * Ensures the API path is valid. Accepts:
47
+ * - String: "some.path" → parts: ["some", "path"]
48
+ * - Array: ["some", "path"] → parts: ["some", "path"]
49
+ * - Empty string, null, or undefined → root level (parts: [])
50
+ * Non-empty paths must contain no empty segments.
51
+ *
52
+ * @example
53
+ * const { apiPath, parts } = this.normalizeApiPath("plugins.tools");
54
+ * const { apiPath, parts } = this.normalizeApiPath(["plugins", "tools"]);
55
+ * const { apiPath, parts } = this.normalizeApiPath(""); // Root level: parts = []
56
+ */
57
+ private normalizeApiPath;
58
+ /**
59
+ * Resolve and validate a path (file or directory) from caller context.
60
+ * @param {string} inputPath - File or folder path provided by caller.
61
+ * @returns {Promise<{resolvedPath: string, isDirectory: boolean, isFile: boolean}>} Path info.
62
+ * @throws {SlothletError} When the path does not exist.
63
+ * @private
64
+ *
65
+ * @description
66
+ * Resolves relative paths from the caller and verifies the path exists.
67
+ * Supports both files (.mjs, .cjs, .js) and directories.
68
+ *
69
+ * @example
70
+ * const { resolvedPath, isDirectory, isFile } = await this.resolvePath("./plugins");
71
+ * const { resolvedPath, isDirectory, isFile } = await this.resolvePath("./module.mjs");
72
+ */
73
+ private resolvePath;
74
+ /**
75
+ * Resolve and validate a folder path from caller context.
76
+ * @param {string} folderPath - Folder path provided by caller.
77
+ * @returns {Promise<string>} Absolute folder path.
78
+ * @throws {SlothletError} When the folder does not exist or is not a directory.
79
+ * @private
80
+ *
81
+ * @description
82
+ * Resolves relative paths from the caller and verifies the folder exists.
83
+ *
84
+ * @deprecated Use resolvePath() instead for file/directory support.
85
+ *
86
+ * @example
87
+ * const resolved = await this.resolveFolderPath("./plugins");
88
+ */
89
+ private resolveFolderPath;
90
+ /**
91
+ * Build a default moduleID when none is provided.
92
+ * @param {string} apiPath - API path for this module.
93
+ * @param {string} resolvedFolderPath - Absolute folder path.
94
+ * @returns {string} Stable module identifier.
95
+ * @private
96
+ *
97
+ * @description
98
+ * Generates a stable moduleID using the apiPath and resolved folder path.
99
+ *
100
+ * @example
101
+ * const moduleID = this.buildDefaultModuleId("plugins", "/abs/path/plugins");
102
+ */
103
+ private buildDefaultModuleId;
104
+ /**
105
+ * Read the current value at an API path.
106
+ * @param {function|object} root - API root object.
107
+ * @param {string[]} parts - Path segments.
108
+ * @returns {unknown} Current value or undefined.
109
+ * @private
110
+ *
111
+ * @description
112
+ * Traverses the API graph by path segments and returns the value if found.
113
+ *
114
+ * @example
115
+ * const value = this.getValueAtPath(api, ["plugins", "tools"]);
116
+ */
117
+ private getValueAtPath;
118
+ /**
119
+ * Ensure parent path exists and return the parent object.
120
+ * @param {function|object} root - API root object.
121
+ * @param {string[]} parts - Path segments.
122
+ * @returns {function|object} Parent container for the final segment.
123
+ * @throws {SlothletError} When a non-object path segment blocks creation.
124
+ * @private
125
+ *
126
+ * @description
127
+ * Walks through the path segments, creating missing objects as needed.
128
+ *
129
+ * @example
130
+ * const parent = this.ensureParentPath(api, ["plugins", "tools"]);
131
+ */
132
+ private ensureParentPath;
133
+ /**
134
+ * Persist a value at `apiPath` on the live API tree, validating that the
135
+ * caller owns (or is otherwise allowed to write to) the requested path.
136
+ *
137
+ * Called from the runtime `self` set traps when a module does
138
+ * `self.X = value` (TOP-LEVEL assignments only — deep-path writes like
139
+ * `self.X.Y = …` flow through the wrapper at `self.X` instead, not this
140
+ * method). Ownership rule: a caller whose own apiPath is `P` may only
141
+ * write under `P.*` (e.g. caller `lib.config` may write
142
+ * `self.lib.config.foo` or `self.lib.config.deep.nested.thing`, but NOT
143
+ * `self.lib.ssh.foo` or `self.lib.foo`). Callers with no apiPath
144
+ * (external user code outside any module) may write anywhere.
145
+ *
146
+ * Callable (`typeof value === "function"`) and object-shaped values are
147
+ * wrapped through `UnifiedWrapper` so they receive the same hook /
148
+ * permission / lifecycle treatment as `api.add()`-loaded modules.
149
+ * Primitives are stored verbatim. The write is applied to the live tree for
150
+ * the lifetime of the instance but is NOT recorded for reload replay — a
151
+ * reload rebuilds from disk + operation history, and a runtime write is not
152
+ * part of that history.
153
+ *
154
+ * @param {string} apiPath - Dotted path to write to (e.g. `"lib.config.foo"`).
155
+ * @param {unknown} value - Value to write. Functions/objects are wrapped via UnifiedWrapper; primitives stored as-is.
156
+ * @param {object|null} callerWrapper - Caller's wrapper from ALS (may be null for external code).
157
+ * @returns {void}
158
+ * @throws {SlothletError} `LOOSE_SET_NOT_OWNED` when a module-bound caller
159
+ * writes outside its own namespace.
160
+ * @throws {SlothletError} `LOOSE_SET_RESERVED_KEY` when any path segment is
161
+ * a prototype-pollution key (`__proto__`, `prototype`, `constructor`).
162
+ * @throws {SlothletError} `INVALID_CONFIG_API_PATH_INVALID` when the path is empty,
163
+ * contains empty segments (e.g. `"a..b"`), or targets a reserved root name
164
+ * (`slothlet`, `shutdown`, `destroy`).
165
+ * @public
166
+ */
167
+ public setOwnedProperty(apiPath: string, value: unknown, callerWrapper: object | null): void;
168
+ /**
169
+ * Determine whether a value is a UnifiedWrapper proxy.
170
+ * @param {unknown} value - Value to inspect.
171
+ * @returns {boolean} True when value looks like a wrapper proxy.
172
+ * @private
173
+ *
174
+ * @description
175
+ * Checks for wrapper markers that are exposed on UnifiedWrapper proxies.
176
+ *
177
+ * @example
178
+ * if (this.isWrapperProxy(api.plugins)) {
179
+ * // Update wrapper implementation
180
+ * }
181
+ */
182
+ private isWrapperProxy;
183
+ /**
184
+ * Synchronize an existing wrapper proxy with a new wrapper.
185
+ * @param {function|object} existingProxy - Existing wrapper proxy.
186
+ * @param {function|object} nextProxy - New wrapper proxy.
187
+ * @param {object} config - Configuration object for debug logging.
188
+ * @returns {Promise<boolean>} True when a wrapper update occurred.
189
+ * @private
190
+ *
191
+ * @description
192
+ * Copies materialization behavior and implementation from the new proxy into the existing
193
+ * proxy to preserve references during reload operations.
194
+ *
195
+ * @example
196
+ * await this.syncWrapper(existingProxy, nextProxy, this.____config);
197
+ */
198
+ private syncWrapper;
199
+ /**
200
+ * Record the first module's exclusive members that a cross-module `replace` shadows off the live
201
+ * surface, so a later remove of the overriding module can restore the first module's FULL mount. (#3)
202
+ * @param {object} existingWrapper - Raw container wrapper being overwritten (still holds the first module's children).
203
+ * @param {string[]} existingChildKeys - The container's current child keys.
204
+ * @param {string[]} nextChildKeys - The overriding module's child keys.
205
+ * @param {string|null} moduleID - The overriding module's id.
206
+ * @returns {void}
207
+ * @private
208
+ *
209
+ * @description
210
+ * `replace` wholesale-swaps the visible surface: the overriding module's keys win and the first
211
+ * module's EXCLUSIVE members (present on the existing container, absent from the incoming one) are
212
+ * deleted from the surface. Ownership still records the first module as their owner, but the live
213
+ * child wrappers are detached — and a namespace cannot be faithfully rebuilt from the ownership
214
+ * value alone (its impl is depleted once its children are adopted). So capture the actual detached
215
+ * child wrappers, keyed by the overriding module, and re-attach them on its removal.
216
+ *
217
+ * Only members owned by a DIFFERENT module are shadowed: a module replacing itself (an explicit
218
+ * same-moduleID re-add / reload) genuinely drops the export and must not resurrect it. Runs once
219
+ * per API tree (api + boundApi both flow through syncWrapper), capturing each tree's own container
220
+ * + child so the re-attach restores both.
221
+ */
222
+ private _recordReplaceShadows;
223
+ /**
224
+ * Invalidate every UnifiedWrapper found in a rejected candidate's subtree, so a still-in-flight
225
+ * `backgroundMaterialize: true` materialization cannot re-apply the rejected content later.
226
+ * @param {unknown} api - Candidate subtree (or leaf) to walk.
227
+ * @param {WeakSet} [visited] - Cycle guard for the recursive walk.
228
+ * @returns {void}
229
+ * @private
230
+ *
231
+ * @description
232
+ * `createProxy()` can kick off a wrapper's `_materialize()` in the background before
233
+ * `setValueAtPath()`'s collision decision is even known. When that decision rejects the
234
+ * candidate, `revertSpeculativeSubtree()` correctly undoes ownership/raw-capture state
235
+ * immediately — but the in-flight materialization is a separate, already-running async
236
+ * operation with no way to know it was rejected. Left alone, its eventual completion calls
237
+ * `___setImpl()`, which re-emits `impl:changed` and re-captures the never-mounted module,
238
+ * undoing the revert that already ran. `___invalidate()` (its own `invalid` flag) is checked by
239
+ * `___materialize()` both before starting and again after its async work resolves, so
240
+ * invalidating here — even after materialization has already begun — stops it from applying its
241
+ * result at all (#372 review, suppressed finding).
242
+ *
243
+ * @example
244
+ * this.invalidateSpeculativeWrappers(rootSource[key]);
245
+ */
246
+ private invalidateSpeculativeWrappers;
247
+ /**
248
+ * Recursively mutate an existing API value to match a new value.
249
+ * @param {function|object} existingValue - Existing value to mutate.
250
+ * @param {unknown} nextValue - New value to apply.
251
+ * @param {object} options - Mutation options.
252
+ * @param {boolean} options.removeMissing - Remove properties not present in nextValue.
253
+ * @param {object} config - Configuration object for debug logging.
254
+ * @returns {Promise<void>}
255
+ * @private
256
+ *
257
+ * @description
258
+ * Uses unified mergeApiObjects logic from api_assignment.mjs to ensure consistent
259
+ * merge behavior between initial build and hot reload.
260
+ *
261
+ * @example
262
+ * await this.mutateApiValue(existing, next, { removeMissing: true }, this.____config);
263
+ */
264
+ private mutateApiValue;
265
+ /**
266
+ * Set a value at a path within an API root.
267
+ * @param {function|object} root - API root object.
268
+ * @param {string[]} parts - Path segments.
269
+ * @param {unknown} value - New value to assign.
270
+ * @param {object} options - Assignment options.
271
+ * @param {boolean} options.mutateExisting - Mutate existing values in place.
272
+ * @param {boolean} options.allowOverwrite - Allow overwriting existing values.
273
+ * @param {string} [options.collisionMode] - Collision handling mode (skip/warn/replace/merge/error).
274
+ * @returns {Promise<boolean>} True if value was set, false if skipped due to collision.
275
+ * @throws {SlothletError} When overwrite is not allowed or collision mode is "error".
276
+ * @private
277
+ *
278
+ * @description
279
+ * Writes a new value at the requested path with configurable collision handling.
280
+ * Supports five collision modes:
281
+ * - skip: Silently ignore collision, keep existing
282
+ * - warn: Warn about collision, keep existing
283
+ * - replace: Replace existing value completely
284
+ * - merge: Merge properties (preserve original + add new)
285
+ * - error: Throw error on collision
286
+ *
287
+ * @example
288
+ * await this.setValueAtPath(api, ["plugins"], newApi, {
289
+ * mutateExisting: true,
290
+ * allowOverwrite: true,
291
+ * collisionMode: "merge"
292
+ * });
293
+ */
294
+ private setValueAtPath;
295
+ /**
296
+ * Delete a value at a path and prune empty parents.
297
+ * @param {function|object} root - API root object.
298
+ * @param {string[]} parts - Path segments.
299
+ * @returns {Promise<boolean>} True when a value was deleted.
300
+ * @private
301
+ *
302
+ * @description
303
+ * Removes the property at the provided path and cleans up any empty parent objects.
304
+ *
305
+ * @example
306
+ * const deleted = await await this.deletePath(api, ["plugins", "tools"]);
307
+ */
308
+ private deletePath;
309
+ /**
310
+ * Restore a path from api.slothlet.api.add history or core load.
311
+ * @param {string} apiPath - API path to restore.
312
+ * @param {?string} moduleID - ModuleId to restore.
313
+ * @returns {Promise<void>}
314
+ * @private
315
+ *
316
+ * @description
317
+ * Attempts to reapply a previous api.slothlet.api.add entry or rebuild the core API for the path.
318
+ *
319
+ * @example
320
+ * await this.restoreApiPath("plugins", "plugins-core");
321
+ */
322
+ private restoreApiPath;
323
+ /**
324
+ * Add new API modules at runtime.
325
+ * @param {object} params - Add parameters.
326
+ * @param {string} params.apiPath - API path to attach.
327
+ * @param {string|string[]|Function|Record<string, unknown>} params.folderPath - A path (file/folder), an array of paths, OR inline content for a synthetic / in-memory leaf (#117): a bare function (a single `default` leaf), a plain object (its keys mount as leaves — option-named keys are content, never options), or a `{ exports, ...options }` object (`exports` is the content; sibling keys are call options).
328
+ * @param {Record<string, unknown>} [params.options={}] - Add options (including optional metadata).
329
+ * @returns {Promise<string|string[]>} Module ID or array of module IDs.
330
+ * @throws {SlothletError} When the instance is not loaded or inputs are invalid.
331
+ * @package
332
+ *
333
+ * @description
334
+ * Loads modules from a folder, file, or array of files/folders using the instance configuration
335
+ * and merges the resulting API under the specified apiPath.
336
+ *
337
+ * Supports filesystem paths and inline (synthetic / in-memory, #117) content:
338
+ * 1. Single directory path (original behavior)
339
+ * 2. Single file path (.mjs, .cjs, .js)
340
+ * 3. Array of file and/or directory paths
341
+ * 4. A bare function — mounted as a single `default` leaf (no filesystem touched)
342
+ * 5. A plain object export map — its own keys mount as leaves, including keys that share an
343
+ * option name (options are never auto-extracted from this argument)
344
+ * 6. A `{ exports, ...options }` object — `exports` is the content (a `{ default?, ...named }`
345
+ * map); the sibling keys are applied as call options (an explicit 3rd-arg option wins)
346
+ *
347
+ * When an array is provided, each path is processed sequentially,
348
+ * honoring collision settings, metadata, and ownership for each.
349
+ *
350
+ * @example
351
+ * // Directory
352
+ * await manager.addApiComponent({
353
+ * apiPath: "plugins",
354
+ * folderPath: "./plugins",
355
+ * options: { moduleID: "plugins-core", metadata: { version: "1.0.0" } }
356
+ * });
357
+ *
358
+ * @example
359
+ * // Single file
360
+ * await manager.addApiComponent({
361
+ * apiPath: "utils",
362
+ * folderPath: "./helpers/string-utils.mjs",
363
+ * options: { metadata: { author: "team" } }
364
+ * });
365
+ *
366
+ * @example
367
+ * // Array of files and folders
368
+ * await manager.addApiComponent({
369
+ * apiPath: "extensions",
370
+ * folderPath: ["./ext/plugin1.mjs", "./ext/plugin2.mjs", "./ext/utils"],
371
+ * options: { collisionMode: "merge" }
372
+ * });
373
+ */
374
+ addApiComponent(params: {
10
375
  apiPath: string;
11
- parts: any[];
12
- };
13
- resolvePath(inputPath: any): Promise<{
14
- resolvedPath: any;
15
- isDirectory: any;
16
- isFile: any;
17
- }>;
18
- resolveFolderPath(folderPath: any): Promise<any>;
19
- buildDefaultModuleId(apiPath: any, ____resolvedFolderPath: any): string;
20
- getValueAtPath(root: any, parts: any): any;
21
- ensureParentPath(root: any, parts: any, options?: {}): any;
22
- setOwnedProperty(apiPath: any, value: any, callerWrapper: any): void;
23
- isWrapperProxy(value: any): boolean;
24
- syncWrapper(existingProxy: any, nextProxy: any, config: any, collisionMode?: string, moduleID?: null): Promise<boolean>;
25
- _recordReplaceShadows(existingWrapper: any, existingChildKeys: any, nextChildKeys: any, moduleID: any): void;
26
- mutateApiValue(existingValue: any, nextValue: any, options: any, config: any): Promise<any>;
27
- setValueAtPath(root: any, parts: any, value: any, options: any): Promise<boolean>;
28
- deletePath(root: any, parts: any): Promise<boolean>;
29
- restoreApiPath(apiPath: any, moduleID: any): Promise<void>;
30
- addApiComponent(params: any): any;
376
+ folderPath: string | string[] | Function | Record<string, unknown>;
377
+ options?: Record<string, unknown> | undefined;
378
+ }): Promise<string | string[]>;
379
+ /**
380
+ * Roll back a failed versioned add.
381
+ *
382
+ * @description
383
+ * Called when `versionManager.registerVersion()` throws after the API tree, cache,
384
+ * ownership, and history have already been mutated by `addApiComponent()`. Scrubs
385
+ * the orphaned "add" entry from `operationHistory`, then delegates tree/cache/ownership
386
+ * and `addHistory` cleanup to `removeApiComponent({ recordHistory: false })` so that
387
+ * no spurious "remove" entry is pushed into `operationHistory`.
388
+ *
389
+ * The rollback is best-effort: if `removeApiComponent` itself throws the error is
390
+ * swallowed and the caller re-throws the original registration error.
391
+ *
392
+ * @param {object} opts - Rollback context.
393
+ * @param {string} opts.moduleID - The moduleID of the just-added component.
394
+ * @param {string} opts.effectivePath - The effective (versioned) mount path, e.g. "v1.auth".
395
+ * @param {string} opts.normalizedPath - The logical path, e.g. "auth".
396
+ * @returns {Promise<void>}
397
+ * @package
398
+ *
399
+ * @example
400
+ * await this._rollbackFailedVersionedAdd({ moduleID, effectivePath, normalizedPath });
401
+ */
31
402
  _rollbackFailedVersionedAdd({ moduleID, effectivePath, normalizedPath }: {
32
- moduleID: any;
33
- effectivePath: any;
34
- normalizedPath: any;
403
+ moduleID: string;
404
+ effectivePath: string;
405
+ normalizedPath: string;
35
406
  }): Promise<void>;
36
- _hasForeignOwnedDescendant(apiPath: any, moduleIDKey: any): boolean;
37
- removeApiComponent(pathOrModuleId: any, options?: {}): Promise<boolean>;
38
- reloadApiComponent(params: any): Promise<void>;
39
- _reloadByModuleID(moduleID: any, { forceReplace }?: {
40
- forceReplace?: boolean | undefined;
407
+ /**
408
+ * Does `apiPath` have any descendant currently owned by a module other than `moduleIDKey`?
409
+ * Scans the full ownership registry (every registered path). Used only for ROOT-mount removals, so a
410
+ * container created by a root mount isn't deleted out from under a sibling another module added beneath
411
+ * it (e.g. `api.add("x.b", …)` after another module created `x`). Not used for normal component removals
412
+ * — those own their whole subtree, and the registry can hold stale post-reload entries. Returns false
413
+ * for an empty/root apiPath.
414
+ * @param {string} apiPath - Container path to test.
415
+ * @param {string} moduleIDKey - The module being removed.
416
+ * @returns {boolean} True if a descendant is owned by another module.
417
+ * @private
418
+ */
419
+ private _hasForeignOwnedDescendant;
420
+ /**
421
+ * Remove API modules at runtime.
422
+ * @param {string} pathOrModuleId - An apiPath (dotted), a moduleID, or the composite `__metadata.moduleID`.
423
+ * @param {object} [options={}] - Options.
424
+ * @param {string} [options.scopedApiPath] - When set, `pathOrModuleId` is resolved strictly as a
425
+ * moduleID and only that module's single node at `scopedApiPath` is removed (drives the public
426
+ * two-argument `api.remove(moduleID, apiPath)`); sibling modules and the module's other mounts stay.
427
+ * @param {boolean} [options.recordHistory=true] - Whether to record the removal in the add/operation history.
428
+ * @returns {Promise<boolean>} True if something was removed, false if nothing matched.
429
+ * @throws {SlothletError} When inputs are invalid.
430
+ * @package
431
+ *
432
+ * @description
433
+ * Removes an API subtree by apiPath, or every path owned by a moduleID. The argument is resolved by
434
+ * splitting on the reserved composite separator ({@link MODULE_ID_SEPARATOR}): a plain id (which can
435
+ * never contain the separator) passes through whole, while a composite `__metadata.moduleID` strips to
436
+ * its base. It matches a registered module verbatim or by its auto-generated `<base>_<hash>` form, and
437
+ * otherwise falls back to treating the argument as an apiPath.
438
+ *
439
+ * @example
440
+ * await manager.removeApiComponent("plugins.tools"); // Remove by API path
441
+ *
442
+ * @example
443
+ * await manager.removeApiComponent("plugins-core"); // Remove all paths owned by a module ID
444
+ *
445
+ * @example
446
+ * await manager.removeApiComponent("plugins-core", { scopedApiPath: "plugins.tools" }); // just that node
447
+ */
448
+ removeApiComponent(pathOrModuleId: string, options?: {
449
+ scopedApiPath?: string | undefined;
450
+ recordHistory?: boolean | undefined;
451
+ }): Promise<boolean>;
452
+ /**
453
+ * Reload API modules using cache system.
454
+ * @param {object} params - Reload parameters.
455
+ * @param {?string} params.apiPath - API path to reload.
456
+ * @param {?string} params.moduleID - ModuleId to reload.
457
+ * @returns {Promise<void>}
458
+ * @package
459
+ *
460
+ * @description
461
+ * Reloads modules from disk using cached parameters. For moduleID reload, rebuilds
462
+ * entire cache and restores all paths. For apiPath reload, rebuilds all contributing
463
+ * moduleID caches and merges implementations.
464
+ *
465
+ * @example
466
+ * await manager.reloadApiComponent({ moduleID: "plugins_abc123" });
467
+ * await manager.reloadApiComponent({ apiPath: "plugins" });
468
+ */
469
+ reloadApiComponent(params: {
470
+ apiPath: string | null;
471
+ moduleID: string | null;
41
472
  }): Promise<void>;
42
- _reloadByApiPath(apiPath: any, options?: {}): Promise<void>;
43
- _findAffectedCaches(apiPath: any): any[];
44
- _collectCustomProperties(existingProxy: any, freshApi: any): {};
45
- _restoreCustomProperties(proxy: any, customProps: any): void;
46
- _restoreApiTree(freshApi: any, endpoint: any, moduleID: any, collisionMode: any, forceReplace?: boolean): Promise<void>;
473
+ /**
474
+ * Reload by moduleID - rebuild cache and restore all paths
475
+ * @param {string} moduleID - Module identifier
476
+ * @param {Object} [options] - Reload options
477
+ * @param {boolean} [options.forceReplace=true] - Force replace mode on existing wrappers.
478
+ * When true, temporarily overrides collision mode to "replace" so the fresh impl
479
+ * fully replaces the old one. When false, the wrapper's original collision mode is
480
+ * preserved, allowing merge behavior for multi-cache rebuilds.
481
+ * @returns {Promise<void>}
482
+ * @private
483
+ */
484
+ private _reloadByModuleID;
485
+ /**
486
+ * Reload by API path - find affected caches, rebuild them, update impls.
487
+ *
488
+ * Accepts "." for base module. For other paths, the resolution order is:
489
+ * 1. Exact cache endpoint match
490
+ * 2. Child caches (endpoints under the path)
491
+ * 3. Ownership history (modules that registered the exact path)
492
+ * 4. Parent cache (most specific cache whose scope covers the path)
493
+ *
494
+ * @param {string} apiPath - API path or "." for base module
495
+ * @param {Object} [options] - Optional reload options
496
+ * @param {Object} [options.metadata] - Metadata to merge for the reloaded path after rebuild
497
+ * @returns {Promise<void>}
498
+ * @private
499
+ */
500
+ private _reloadByApiPath;
501
+ /**
502
+ * Find all cache entries that need to be rebuilt for a given API path.
503
+ *
504
+ * Resolution order:
505
+ * 1. "." or "" or null → base module cache(s) (endpoint ".")
506
+ * 2. Exact endpoint match → that specific cache
507
+ * 3. Child caches → caches whose endpoint is under the given path
508
+ * 4. Ownership history → modules that registered the exact path
509
+ * 5. Parent cache → most specific cache whose scope covers the path
510
+ *
511
+ * @param {string} apiPath - The API path to find caches for
512
+ * @returns {string[]} Array of moduleIDs to reload
513
+ * @private
514
+ */
515
+ private _findAffectedCaches;
516
+ /**
517
+ * Collect user-set custom properties from a proxy/wrapper that are NOT in the fresh API.
518
+ * Custom properties are those set by the user at runtime (e.g., api.custom.testFlag = true)
519
+ * that should survive a selective reload.
520
+ * @param {Object} existingProxy - The existing proxy/wrapper to collect from
521
+ * @param {Object} freshApi - The fresh API from rebuild (keys to exclude)
522
+ * @returns {Object} Map of custom property names to their values
523
+ * @private
524
+ */
525
+ private _collectCustomProperties;
526
+ /**
527
+ * Restore previously collected custom properties onto a proxy/wrapper after reload.
528
+ * @param {Object} proxy - The proxy to restore properties onto
529
+ * @param {Object} customProps - Map of property names to values from _collectCustomProperties
530
+ * @private
531
+ */
532
+ private _restoreCustomProperties;
533
+ /**
534
+ * Restore API from fresh rebuild by updating existing wrapper.
535
+ * For non-root endpoints, updates the wrapper's implementation without replacing structure.
536
+ * For root endpoints, merges keys directly as addApiComponent does.
537
+ * @param {object} freshApi - Fresh API from rebuild
538
+ * @param {string} endpoint - Original endpoint path
539
+ * @param {string} moduleID - Module identifier
540
+ * @param {string} collisionMode - Collision handling mode
541
+ * @param {boolean} [forceReplace=true] - When true, temporarily overrides wrapper collision
542
+ * mode to "replace" so fresh impl fully replaces old. When false, preserves original
543
+ * collision mode for proper merge behavior in multi-cache rebuilds.
544
+ * @returns {Promise<void>}
545
+ * @private
546
+ */
547
+ private _restoreApiTree;
47
548
  #private;
48
549
  }
49
550
  import { ComponentBase } from "#factories/component-base";