@cldmv/slothlet-types 3.15.3 → 3.16.1

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
@@ -0,0 +1,425 @@
1
+ /**
2
+ * Tracks, per Slothlet instance, every mounted module's raw contribution of a real function at any
3
+ * api path, and resolves — on demand, at rebuild/cascade time, never inside the event handler
4
+ * itself — which configured routine(s) each one matches.
5
+ *
6
+ * @description
7
+ * Interpretation is deliberately deferred: a module's ownership *endpoint* (its own mount root,
8
+ * needed for mount-relative name matching) is not recorded until AFTER all of that module's own
9
+ * `impl:created` events have already fired (see `api-manager.mjs`'s `addApiComponent` —
10
+ * `setModuleEndpoint` runs once, after `buildAPI` has already built and emitted for every leaf).
11
+ * Matching inside the event handler would therefore see `undefined` for a brand-new module's own
12
+ * endpoint on its very first events. Capturing the raw `{apiPath, moduleID, fn}` unconditionally and
13
+ * resolving matches later — once every relevant `setModuleEndpoint` call has definitely run —
14
+ * sidesteps that ordering hazard entirely.
15
+ *
16
+ * @class RoutineManager
17
+ * @extends ComponentBase
18
+ * @public
19
+ */
20
+ export class RoutineManager extends ComponentBase {
21
+ static slothletProperty: string;
22
+ /**
23
+ * Create a RoutineManager instance.
24
+ * @param {object} slothlet - Slothlet class instance.
25
+ */
26
+ constructor(slothlet: object);
27
+ /**
28
+ * Raw capture, in arrival (registration) order, deduplicated by `(apiPath, moduleID)`. A
29
+ * re-registration of the same pair (hot-reload, or an incidental re-touch from an unrelated
30
+ * later mount at the same parent path) replaces the existing entry in place — preserving its
31
+ * original position — rather than appending a duplicate.
32
+ * @type {Array<{apiPath: string, moduleID: string, fn: Function}>}
33
+ */
34
+ raw: Array<{
35
+ apiPath: string;
36
+ moduleID: string;
37
+ fn: Function;
38
+ }>;
39
+ /**
40
+ * Every `raw` entry's originating `UnifiedWrapper`, keyed `moduleID -> apiPath -> wrapper` —
41
+ * populated alongside `raw` in {@link RoutineManager#onImplCreated} purely so a
42
+ * build-attempt-wide revert ({@link RoutineManager#revertSpeculativeState}) can invalidate
43
+ * exactly the wrapper(s) IT speculatively created, without needing a concrete api-tree
44
+ * reference to walk. Never consulted by routine execution itself — `raw`'s own `fn` field
45
+ * stays the single source of truth there. Nested (not a single `` `${moduleID}:${apiPath}` ``
46
+ * string key) because `:` is a valid character in a user-supplied moduleID — a flat string
47
+ * key risked one module's prefix-scan (e.g. `pruneModule("a")`, matching `"a:"`) wrongly
48
+ * catching another module's entries (e.g. moduleID `"a:b"`'s own `"a:b:sub.path"` key)
49
+ * (#372/#373 review, suppressed finding).
50
+ * @type {Map<string, Map<string, object>>}
51
+ */
52
+ rawWrappers: Map<string, Map<string, object>>;
53
+ /**
54
+ * Recording guard. `rebuildStacks()` (and its narrower reactive counterpart,
55
+ * `#reactivelyPatchStack()` — #362) overwrite live api properties, which re-enters
56
+ * `onImplCreated` via the same `impl:created` event every other write goes through — this
57
+ * flag is turned off for the duration of that overwrite so the stacked callable just
58
+ * installed is never captured as a phantom contributor to its own chain.
59
+ * @type {boolean}
60
+ */
61
+ recording: boolean;
62
+ /**
63
+ * Compiled-glob-pattern cache, keyed by the raw pattern string (never includes a leading `^`
64
+ * — callers strip that themselves before compiling).
65
+ * @type {Map<string, function(string): boolean>}
66
+ */
67
+ patternCache: Map<string, (arg0: string) => boolean>;
68
+ /**
69
+ * Discard all captured state. Called at the start of every `load()` (including `reload()`,
70
+ * which re-invokes `load()` on the same instance) so a previous cycle's contributors never
71
+ * bleed into a fresh compose.
72
+ * @returns {void}
73
+ * @public
74
+ */
75
+ public reset(): void;
76
+ /**
77
+ * Lifecycle subscriber for BOTH `impl:created` and `impl:changed` (subscribed to both in
78
+ * src/slothlet.mjs's `_setupLifecycleSubscribers()`). Captures a contribution's raw
79
+ * `{apiPath, moduleID, fn}` unconditionally — routine-name interpretation happens later, at
80
+ * rebuild/cascade time (see the class-level description for why).
81
+ *
82
+ * @description
83
+ * Reads `data.wrapper.__impl` (present on every such event, in both eager and lazy mode) rather
84
+ * than `data.impl` — `impl:created` fires twice per leaf construction (once with `impl` set to
85
+ * the wrapper itself, once with the raw value for eager-known impls), and `wrapper.__impl` is
86
+ * the one consistent field across every variant. Fires BEFORE collision resolution decides
87
+ * which contributor's value survives onto the composed tree, so every contributor is captured —
88
+ * not just the merge winner.
89
+ *
90
+ * Also subscribed to `impl:changed` so a LATE, direct reassignment (`self.auth.shutdown = fn`,
91
+ * done after the module that owns `auth` finished loading) is captured too, not just the
92
+ * original module-load-time contribution — and, symmetrically, so a later reassignment AWAY
93
+ * from a function (to an object, `null`, or any other non-function value) removes the earlier
94
+ * capture rather than leaving a stale function contribution behind for a cascade to invoke.
95
+ * @param {object} data - `impl:created` / `impl:changed` event payload.
96
+ * @returns {void}
97
+ * @public
98
+ */
99
+ public onImplCreated(data: object): void;
100
+ /**
101
+ * Lifecycle subscriber: `impl:removed`. Prunes a removed module's raw contribution.
102
+ * @param {object} data - `impl:removed` event payload.
103
+ * @returns {void}
104
+ * @public
105
+ */
106
+ public onImplRemoved(data: object): void;
107
+ /**
108
+ * Prune every raw-captured contribution at or below a given api path for one moduleID — the
109
+ * scoped-removal analog of {@link RoutineManager#pruneModule}, for when a whole SUBTREE (not
110
+ * just its own top-level property) is deleted from the live tree.
111
+ * @param {string} apiPath - The removed subtree's own root path.
112
+ * @param {string} moduleID - Module identifier whose descendant contributions to prune.
113
+ * @returns {void}
114
+ * @public
115
+ *
116
+ * @description
117
+ * The scoped two-argument `api.remove(apiPath, moduleID)` deletes the ENTIRE live subtree
118
+ * rooted at `apiPath` (`ApiManager#deletePath`), but `impl:removed` only ever fires for the
119
+ * exact property that was deleted — never for descendants that were simply carried away with
120
+ * it. A nested routine capture like `auth.initialize` therefore survived indefinitely in `raw`
121
+ * after removing `auth`, still invoked by `stackRoutines: true`'s cascades even though its
122
+ * whole subtree is gone (#372/#373 review, suppressed finding).
123
+ *
124
+ * @example
125
+ * routineManager.pruneSubtree("auth", moduleID);
126
+ */
127
+ public pruneSubtree(apiPath: string, moduleID: string): void;
128
+ /**
129
+ * Prune every raw-captured contribution belonging to a module, regardless of whether it was
130
+ * ever the live property at its own path.
131
+ * @param {string} moduleID - Module identifier being fully removed.
132
+ * @returns {void}
133
+ * @public
134
+ *
135
+ * @description
136
+ * `onImplRemoved()` alone is not enough for a whole-module removal: it prunes by (apiPath,
137
+ * moduleID) on the `impl:removed` lifecycle event, which fires only when a property is actually
138
+ * DELETED from the live composed tree. A module that lost a collision (a merge-loser, recorded
139
+ * in ownership but never installed as the live property at its path) is never the live property,
140
+ * so removing it resolves as an ownership "restore" (the current owner's value is re-applied,
141
+ * unchanged) rather than a "delete" — `impl:removed` never fires for the loser's own entry, and
142
+ * its raw contribution would otherwise survive `api.remove()` indefinitely, still invoked under
143
+ * `stackRoutines: true` (#372). Call this alongside `OwnershipManager#unregister()` for a
144
+ * whole-module removal, which already discards every path the module owned regardless of
145
+ * whether the live tree changed for each one.
146
+ *
147
+ * @example
148
+ * ownership.unregister(moduleID);
149
+ * routineManager.pruneModule(moduleID);
150
+ */
151
+ public pruneModule(moduleID: string): void;
152
+ /**
153
+ * Snapshot the raw contributions moduleID currently has, keyed by apiPath, for later restoration
154
+ * @param {string} moduleID - Module identifier to snapshot.
155
+ * @returns {Map<string, {fn: Function, index: number, wrapper: object|undefined}>} One entry per
156
+ * apiPath the module currently contributes to.
157
+ * @public
158
+ *
159
+ * @description
160
+ * Call this BEFORE a candidate build's construction (buildAPI) runs, so a later revert can tell
161
+ * an apiPath moduleID already genuinely contributed to (whose entry must be restored, not
162
+ * dropped) from one the candidate build's own speculative `impl:created` capture fabricated
163
+ * (which must be discarded outright). Capturing `wrapper` alongside `fn`/`index` lets a restore
164
+ * put the ORIGINAL wrapper back into {@link RoutineManager#rawWrappers} tracking too — otherwise
165
+ * a candidate's own re-touch of the pair overwrites that tracking with its own (about to be
166
+ * invalidated) wrapper, and restoring only `raw` leaves nothing correctly tracked for the
167
+ * pre-candidate contribution (#372/#373 review, suppressed finding).
168
+ *
169
+ * @example
170
+ * const snapshot = routineManager.snapshotRawEntries("same-mod");
171
+ */
172
+ public snapshotRawEntries(moduleID: string): Map<string, {
173
+ fn: Function;
174
+ index: number;
175
+ wrapper: object | undefined;
176
+ }>;
177
+ /**
178
+ * Snapshot exactly one (apiPath, moduleID) raw entry's function and array position, for a single
179
+ * internal candidate's own revert — the single-path analog of
180
+ * {@link RoutineManager#snapshotRawEntries}.
181
+ * @param {string} apiPath - Full api path the candidate is about to (re-)contribute to.
182
+ * @param {string} moduleID - Module identifier making the contribution.
183
+ * @returns {{fn: Function, index: number, wrapper: object|undefined}|undefined} The prior
184
+ * function, its position in `raw`, and its tracked wrapper (if any) — or `undefined` if none.
185
+ * @public
186
+ *
187
+ * @description
188
+ * `processFiles()`'s internal collision branches each construct a `UnifiedWrapper` (firing
189
+ * `impl:created`, unconditionally capturing into `raw`) BEFORE calling `assignToApiPath()` to
190
+ * learn whether that specific candidate is actually accepted. Call this immediately before
191
+ * constructing the wrapper for one such branch, then {@link RoutineManager#revertRawEntry} after
192
+ * a `false` assignment result, so a skip/warn-rejected internal candidate's raw capture is
193
+ * corrected without needing a whole-module snapshot (#372/#373 review). Capturing `index`
194
+ * alongside `fn` lets a restore re-insert at the original registration position instead of
195
+ * appending, preserving `stackRoutines: true`'s registration-order execution semantics (#372/#373
196
+ * review, suppressed finding).
197
+ *
198
+ * @example
199
+ * const priorEntry = routineManager.snapshotRawEntry("thing.initialize", moduleID);
200
+ * const wrapper = new UnifiedWrapper(...);
201
+ * const assigned = assignToApiPath(targetApi, "thing", wrapper.createProxy(), {...});
202
+ * if (!assigned) routineManager.revertRawEntry("thing.initialize", moduleID, priorEntry);
203
+ */
204
+ public snapshotRawEntry(apiPath: string, moduleID: string): {
205
+ fn: Function;
206
+ index: number;
207
+ wrapper: object | undefined;
208
+ } | undefined;
209
+ /**
210
+ * Restore or drop exactly one (apiPath, moduleID) raw entry after an internal candidate at that
211
+ * path was rejected — the single-path analog of
212
+ * {@link RoutineManager#revertSpeculativeState}/{@link RoutineManager#revertSpeculativeSubtree}.
213
+ * @param {string} apiPath - Full api path the rejected candidate targeted.
214
+ * @param {string} moduleID - Module identifier the rejected candidate belongs to.
215
+ * @param {{fn: Function, index: number}|undefined} priorEntry - This pair's snapshot from BEFORE
216
+ * the candidate's own wrapper construction ran, from {@link RoutineManager#snapshotRawEntry} —
217
+ * `undefined` when there was no genuine prior contribution (the candidate's capture must be
218
+ * dropped outright).
219
+ * @returns {void}
220
+ * @public
221
+ *
222
+ * @example
223
+ * routineManager.revertRawEntry("thing.initialize", moduleID, priorEntry);
224
+ */
225
+ public revertRawEntry(apiPath: string, moduleID: string, priorEntry: {
226
+ fn: Function;
227
+ index: number;
228
+ } | undefined): void;
229
+ /**
230
+ * Revert a speculative API subtree's raw routine contributions
231
+ * @param {object} api - API object or subtree (the same candidate value addApiComponent built).
232
+ * @param {string} moduleID - Module identifier whose speculative contributions to revert.
233
+ * @param {string} path - Current API path.
234
+ * @param {Map<string, Function>} priorEntries - Snapshot from
235
+ * {@link RoutineManager#snapshotRawEntries}, taken before the candidate build ran, of what
236
+ * moduleID already genuinely contributed.
237
+ * @param {WeakSet} [visited] - Visited objects (prevents circular refs).
238
+ * @returns {void}
239
+ * @public
240
+ *
241
+ * @description
242
+ * Mirrors OwnershipManager#revertSpeculativeSubtree()'s reasoning for the same underlying cause:
243
+ * `onImplCreated` fires from the SAME `impl:created`/`impl:changed` events during a candidate
244
+ * build's construction, before addApiComponent's own collision decision runs — capturing every
245
+ * constructed wrapper's function into `this.raw` regardless of whether the build is later
246
+ * accepted. Under `stackRoutines: true` (which bypasses ownership filtering entirely), a
247
+ * skip/warn-rejected candidate's raw entry would otherwise still be invoked by root-anchored
248
+ * routines and the exact-path stacked callable, even though its module was never actually
249
+ * mounted. At each level: if `priorEntries` has this exact apiPath, moduleID already
250
+ * contributed to it before this build — restore that function (a later re-registration for the
251
+ * same pair replaces in place, so a rejected candidate's fn would otherwise silently overwrite a
252
+ * genuine, pre-existing contribution). Otherwise the entry is purely speculative — drop it.
253
+ *
254
+ * @example
255
+ * const priorEntries = routineManager.snapshotRawEntries("same-mod");
256
+ * // ...buildAPI runs, candidate is rejected...
257
+ * routineManager.revertSpeculativeSubtree(apiToMerge, "same-mod", "thing", priorEntries);
258
+ */
259
+ public revertSpeculativeSubtree(api: object, moduleID: string, path: string, priorEntries: Map<string, Function>, visited?: WeakSet<any>): void;
260
+ /**
261
+ * Revert every speculative raw contribution currently on record for a module, driven by the
262
+ * module's own current `raw` entries rather than a candidate api-tree reference
263
+ * @param {string} moduleID - Module identifier whose speculative raw entries to revert.
264
+ * @param {Map<string, Function>} priorEntries - Snapshot from
265
+ * {@link RoutineManager#snapshotRawEntries}, taken before the candidate build ran.
266
+ * @returns {void}
267
+ * @public
268
+ *
269
+ * @description
270
+ * Mirrors `OwnershipManager#revertSpeculativeState()`'s reasoning: `revertSpeculativeSubtree`
271
+ * needs a concrete api-tree value to walk, which may not exist when `buildAPI()` or
272
+ * `setValueAtPath()` throws partway through a candidate build. Reads `this.raw` directly for
273
+ * whatever paths this moduleID currently has an entry at, restoring the ones already present in
274
+ * `priorEntries` and dropping the rest (#372 review).
275
+ *
276
+ * Also invalidates ({@link module:@cldmv/slothlet/handlers/unified-wrapper~UnifiedWrapper#___invalidate})
277
+ * whatever wrapper this now-aborted build attempt itself constructed at each reverted path —
278
+ * `revertSpeculativeSubtree()`'s two callers already pair it with
279
+ * `ApiManager#invalidateSpeculativeWrappers()` on a concrete tree; this is the state-only
280
+ * variant's equivalent, since there is no tree here to walk. Restoring/dropping the raw `fn`
281
+ * alone is not enough when `materializeOnCreate` (`config.backgroundMaterialize`) is set: that
282
+ * wrapper can already be materializing in the background and would otherwise re-apply its
283
+ * result and re-fire `impl:changed` after this rollback (#372/#373 review, suppressed finding).
284
+ * A path this build attempt never touched (`raw`'s current `fn` still equals what `priorEntries`
285
+ * already had) keeps whatever wrapper it already had — only a CHANGED (apiPath, moduleID) pair
286
+ * had its wrapper constructed during this now-abandoned attempt.
287
+ *
288
+ * @example
289
+ * const priorEntries = routineManager.snapshotRawEntries("same-mod");
290
+ * try {
291
+ * // ...buildAPI/setValueAtPath run and throw...
292
+ * } catch (err) {
293
+ * routineManager.revertSpeculativeState("same-mod", priorEntries);
294
+ * throw err;
295
+ * }
296
+ */
297
+ public revertSpeculativeState(moduleID: string, priorEntries: Map<string, Function>): void;
298
+ /**
299
+ * Run one exact api path's stacked contributors directly (the callable installed at that path
300
+ * by {@link rebuildStacks} delegates here). Every contributor runs regardless of an earlier
301
+ * one's failure; if any failed, one aggregate `ROUTINE_FAILED` error is thrown once all of them
302
+ * have run (see {@link #throwAggregate}).
303
+ * @param {string} apiPath - Exact composed api path.
304
+ * @param {Array} [args] - Arguments forwarded to every contributor.
305
+ * @param {object} [routine] - The specific routine config this callable was built for
306
+ * ({@link #buildStackedCallable}'s own caller, {@link rebuildStacks}, always supplies it).
307
+ * When present, entries are also filtered by {@link #matches} so a mount-relative name
308
+ * pattern belonging to a DIFFERENT routine that happens to resolve to the same exact apiPath
309
+ * (e.g. a root module's bare `"initialize"` and an `api.add()`-mounted module's own
310
+ * `"initialize"`, both composing to the same final path) doesn't invoke that other routine's
311
+ * raw functions too (#366 review).
312
+ * @returns {Promise<*>} The sole contributor's return value, an ordered array of every
313
+ * contributor's return value when there are two or more, or `[]` when there are none (e.g. a
314
+ * stacked callable left in place after its last contributor was removed without an
315
+ * intervening rebuild) — matching {@link runCascade}'s identical "no contributors" contract.
316
+ * @throws {SlothletError} `ROUTINE_FAILED` — see {@link #throwAggregate}.
317
+ * @public
318
+ */
319
+ public runPath(apiPath: string, args?: any[], routine?: object): Promise<any>;
320
+ /**
321
+ * Run the root cascade for a routine: every matching contribution anywhere, grouped by exact
322
+ * api path — with `stackRoutines: true`, contributors colliding at the same path all run
323
+ * together, adjacently; with the default `stackRoutines: false`, only the current owner at
324
+ * that path runs, exactly like a direct call — the groups themselves ordered per the routine's
325
+ * configured `order` (see {@link #orderPaths}). No-op — and returns `undefined` — when the
326
+ * routine isn't configured.
327
+ *
328
+ * @description
329
+ * Force-materializes whatever the routine's pattern requires (see {@link #materializeFor})
330
+ * before reading `this.raw`, so a not-yet-touched lazy contribution is captured rather than
331
+ * missed. Best-effort across the whole cascade: every matching path's group runs regardless of
332
+ * an earlier group's failure; if any contributor anywhere failed, one aggregate `ROUTINE_FAILED`
333
+ * error is thrown once everything has run (see {@link #throwAggregate}).
334
+ * @param {string} name - Routine name.
335
+ * @param {Array} [args] - Arguments forwarded, unchanged, to EVERY contributor at EVERY matching
336
+ * path — the same broadcast `#runEntries` already gives a single path via {@link runPath}
337
+ * (#362 review: the cascade previously took no arguments at all).
338
+ * @param {boolean} [skipMaterialize=false] - Skip the {@link #materializeFor} call — internal
339
+ * use only, for a caller (`#runModeRoutines`) that already force-materialized this exact
340
+ * routine immediately beforehand and would otherwise re-walk the same tree for no new
341
+ * information. Always leave this `false` for any externally-triggered cascade (the installed
342
+ * `api[name]()` / `api.slothlet[name]()` callables never pass it), since those calls have no
343
+ * such prior guarantee.
344
+ * @returns {Promise<*>} The sole involved path's result, an ordered array of every involved
345
+ * path's result when there are two or more, `[]` when the routine has no contributors
346
+ * anywhere, or `undefined` when the routine isn't configured at all OR the instance has
347
+ * already been destroyed.
348
+ * @throws {SlothletError} `ROUTINE_FAILED` — see {@link #throwAggregate}.
349
+ * @public
350
+ */
351
+ public runCascade(name: string, args?: any[], skipMaterialize?: boolean): Promise<any>;
352
+ /**
353
+ * Run every configured `mode: "shutdown"` routine's cascade. Called from the framework's
354
+ * existing dispose builtin (`createShutdownFunction()` in api_builder.mjs) — the
355
+ * `"shutdown"`-mode integration point the spec calls "the existing dispose path". Covers the
356
+ * default `shutdown` routine and any custom-named `mode: "shutdown"` routine alike.
357
+ *
358
+ * @description
359
+ * TEMPORARY v3-compat gate (#341): a no-op unless `config.autoRoutines` is `true` (its
360
+ * deprecated alias is `collectLifecycleHooks`, which also expands into an implicit
361
+ * `mode: "shutdown"` routine — see `Config.normalizeRoutines`). Every routine is always
362
+ * stacked/wrapped regardless of this flag — `self.<path>()` is always directly callable — only
363
+ * the AUTOMATIC firing at dispose is gated, so that a project upgrading to a slothlet version
364
+ * carrying #341 sees no behavior change by default. Planned for v4: default `autoRoutines` to
365
+ * `true` and remove `collectLifecycleHooks` — do not lose track of this before that release.
366
+ * @returns {Promise<void>}
367
+ * @public
368
+ */
369
+ public runShutdownModeRoutines(): Promise<void>;
370
+ /**
371
+ * Run every configured `mode: "destroy"` routine's cascade. Called from the framework's
372
+ * existing dispose builtin (`createDestroyFunction()` in api_builder.mjs) — the "existing
373
+ * dispose path" for `destroy`, replacing what used to be a separate `_collectLifecycleHooks
374
+ * ("destroy")` walk. `destroy()` also calls the root `shutdown()` afterward, so
375
+ * `mode: "shutdown"` routines still run as part of `destroy()` too — this only covers routines
376
+ * a consumer wants to fire on `destroy()` specifically, not on a plain `shutdown()`.
377
+ *
378
+ * @description
379
+ * Same TEMPORARY v3-compat `autoRoutines` gate as {@link runShutdownModeRoutines} — see that
380
+ * method's description for the full rationale and the v4 migration plan.
381
+ * @returns {Promise<void>}
382
+ * @public
383
+ */
384
+ public runDestroyModeRoutines(): Promise<void>;
385
+ /**
386
+ * Run every configured `mode: "startup"` routine's cascade. Called once, as the final awaited
387
+ * step of `load()` — `await slothlet(...)` resolves only after this completes.
388
+ *
389
+ * @description
390
+ * Same TEMPORARY v3-compat `autoRoutines` gate as {@link runShutdownModeRoutines} — see that
391
+ * method's description for the full rationale and the v4 migration plan. Wrapping still always
392
+ * happens; only automatic firing is gated.
393
+ * @returns {Promise<void>}
394
+ * @public
395
+ */
396
+ public runStartupModeRoutines(): Promise<void>;
397
+ /**
398
+ * Overwrite every currently-known matching api path's slot on the live api tree with its
399
+ * stacked callable, and (re)attach every configured routine's root cascade at the api root and
400
+ * under `api.slothlet` (using the routine's `name` verbatim as the property key — a dotted or
401
+ * `^`-prefixed name is reachable via bracket notation, e.g. `api.slothlet["admin.initialize"]`,
402
+ * `api["^ext.*.initialize"]`; only a bare name gets clean dot-notation access).
403
+ *
404
+ * @description
405
+ * Safe to call repeatedly — at the end of initial `load()`, again after every
406
+ * `api.slothlet.api.add()`, and immediately before an auto-fired mode cascade — it re-derives
407
+ * every path from current state. A path whose container no longer resolves (its owning module
408
+ * was removed without ever un-registering) is silently skipped rather than throwing: teardown
409
+ * ordering across removal + rebuild is best-effort, not a correctness guarantee this feature
410
+ * makes. Between those trigger points, {@link onImplCreated}'s own narrower
411
+ * `#reactivelyPatchStack()` (#362) keeps a path that gains a second contributor from going
412
+ * stale — see its doc for why a full sweep here isn't needed for that case.
413
+ *
414
+ * Guards writes with `recording = false`: the property write below re-enters `onImplCreated`
415
+ * via the same `impl:created` event ordinary module writes go through, and without the guard
416
+ * the stacked callable being installed would be captured as a phantom contributor on the very
417
+ * next rebuild.
418
+ * @param {object} api - The fully composed, bound api object.
419
+ * @returns {Promise<void>}
420
+ * @public
421
+ */
422
+ public rebuildStacks(api: object): Promise<void>;
423
+ #private;
424
+ }
425
+ import { ComponentBase } from "#factories/component-base";
@@ -1,5 +1,46 @@
1
- export const PROTECT_SENTINEL: unique symbol;
2
- export const TRUSTED_ROOT: unique symbol;
3
- export function enforceContextKeyWrite(ctx: any, prop: any): void;
1
+ /**
2
+ * Enforce owner-locking on a top-level runtime `context` set-trap write. If `prop` is an owner-locked
3
+ * key on the active store, the write is allowed only when the writing caller matches the declared
4
+ * owner: a `PROTECT_SENTINEL` owner is write-once/unowned (never writable via the set trap), and a
5
+ * named owner (a caller apiPath) permits only the caller whose current identity equals that name.
6
+ *
7
+ * @param {object} ctx - The active context store (carries `__contextOwners` and `currentWrapper`).
8
+ * @param {string|symbol} prop - The context key being written.
9
+ * @returns {void}
10
+ * @throws {SlothletError} CONTEXT_KEY_PROTECTED when the write is not permitted by the key's owner.
11
+ * @internal
12
+ */
13
+ export function enforceContextKeyWrite(ctx: object, prop: string | symbol): void;
14
+ /**
15
+ * Resolve a runtime `context` get-trap read. For an owner-locked key (declared via
16
+ * `scope({ protect, owners })`) whose value is a plain object or array, returns a recursive protected
17
+ * view so nested writes stay enforced (#207); for every other key it returns the raw value, leaving
18
+ * unprotected context reads (and non-wrappable protected values) exactly as before.
19
+ *
20
+ * @param {object} ctx - The active context store.
21
+ * @param {string|symbol} prop - The context key being read.
22
+ * @param {function(): object|null} getContext - The runtime's active-store resolver, threaded into
23
+ * the view so nested writes resolve the writer at write time (see makeProtectedContextView).
24
+ * @returns {*} The raw value, or a protected view when the key is owner-locked and wrappable.
25
+ * @internal
26
+ */
27
+ export function readProtectedContextValue(ctx: object, prop: string | symbol, getContext: () => object | null): any;
28
+ /**
29
+ * Marker set on a slothlet instance's base context store to identify host-initiated calls.
30
+ * @type {symbol}
31
+ * @internal
32
+ */
33
+ export const TRUSTED_ROOT: symbol;
34
+ /**
35
+ * Registry of every genuine `UnifiedWrapper` instance. A caller identity absent from this set is a
36
+ * forged object and is denied by permission enforcement.
37
+ * @type {WeakSet<object>}
38
+ * @internal
39
+ */
4
40
  export const genuineWrappers: WeakSet<object>;
5
- export function readProtectedContextValue(ctx: any, prop: any, getContext: any): any;
41
+ /**
42
+ * Sentinel owner for write-once/unowned ("protected") context keys.
43
+ * @type {symbol}
44
+ * @internal
45
+ */
46
+ export const PROTECT_SENTINEL: symbol;