@cldmv/slothlet-types 3.15.2 → 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.
- package/lib/builders/api-assignment.d.mts +125 -4
- package/lib/builders/api_builder.d.mts +104 -7
- package/lib/builders/builder.d.mts +82 -1
- package/lib/builders/modes-processor.d.mts +66 -3
- package/lib/errors.d.mts +114 -19
- package/lib/factories/component-base.d.mts +171 -8
- package/lib/factories/context.d.mts +22 -4
- package/lib/handlers/api-cache-manager.d.mts +209 -20
- package/lib/handlers/api-manager.d.mts +539 -38
- package/lib/handlers/context-async.d.mts +92 -25
- package/lib/handlers/context-live.d.mts +117 -30
- package/lib/handlers/framework-internals.d.mts +33 -2
- package/lib/handlers/hook-manager.d.mts +306 -73
- package/lib/handlers/lifecycle-token.d.mts +48 -3
- package/lib/handlers/lifecycle.d.mts +86 -5
- package/lib/handlers/materialize-manager.d.mts +76 -8
- package/lib/handlers/metadata.d.mts +238 -18
- package/lib/handlers/module-manager.d.mts +169 -21
- package/lib/handlers/ownership.d.mts +376 -45
- package/lib/handlers/permission-manager.d.mts +283 -46
- package/lib/handlers/routine-manager.d.mts +425 -0
- package/lib/handlers/trusted-root.d.mts +45 -4
- package/lib/handlers/unified-wrapper.d.mts +287 -26
- package/lib/handlers/version-manager.d.mts +236 -29
- package/lib/helpers/caller-pinning.d.mts +21 -2
- package/lib/helpers/class-instance-wrapper.d.mts +56 -2
- package/lib/helpers/config.d.mts +311 -161
- package/lib/helpers/defaults.d.mts +40 -0
- package/lib/helpers/eventemitter-context.d.mts +29 -3
- package/lib/helpers/eventtarget-context.d.mts +19 -1
- package/lib/helpers/eventtarget-property-context.d.mts +21 -0
- package/lib/helpers/generate-manifest.d.mts +174 -7
- package/lib/helpers/hint-detector.d.mts +22 -2
- package/lib/helpers/manifest-resolver.d.mts +100 -1
- package/lib/helpers/modes-utils.d.mts +30 -3
- package/lib/helpers/module-discovery.d.mts +80 -7
- package/lib/helpers/module-manifest-validator.d.mts +36 -13
- package/lib/helpers/module-sort.d.mts +64 -1
- package/lib/helpers/observer-context.d.mts +21 -0
- package/lib/helpers/pattern-matcher.d.mts +43 -3
- package/lib/helpers/platform.d.mts +109 -10
- package/lib/helpers/resolve-from-caller.d.mts +27 -3
- package/lib/helpers/sanitize.d.mts +92 -4
- package/lib/helpers/scheduler-context.d.mts +21 -1
- package/lib/helpers/utilities.d.mts +52 -4
- package/lib/i18n/translations.d.mts +50 -5
- package/lib/modes/eager.d.mts +46 -8
- package/lib/modes/lazy.d.mts +57 -10
- package/lib/processors/flatten.d.mts +116 -56
- package/lib/processors/loader.d.mts +77 -10
- package/lib/processors/type-generator.d.mts +16 -2
- package/lib/processors/typescript.d.mts +169 -13
- package/lib/runtime/runtime-asynclocalstorage.d.mts +71 -3
- package/lib/runtime/runtime-livebindings.d.mts +37 -2
- package/lib/runtime/runtime.d.mts +39 -3
- package/lib/typegen/typegen.d.mts +34 -2
- package/package.json +5 -23
- 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:
|
|
5
|
-
initialConfig:
|
|
6
|
-
operationHistory:
|
|
7
|
-
replaceShadows: Map<
|
|
33
|
+
addHistory: object[];
|
|
34
|
+
initialConfig: object | null;
|
|
35
|
+
operationHistory: object[];
|
|
36
|
+
replaceShadows: Map<string, object[]>;
|
|
8
37
|
};
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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:
|
|
33
|
-
effectivePath:
|
|
34
|
-
normalizedPath:
|
|
403
|
+
moduleID: string;
|
|
404
|
+
effectivePath: string;
|
|
405
|
+
normalizedPath: string;
|
|
35
406
|
}): Promise<void>;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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";
|