@cldmv/slothlet-types 3.16.3 → 3.17.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/handlers/event-manager.d.mts +77 -0
- package/lib/handlers/permission-manager.d.mts +60 -1
- package/lib/handlers/routine-manager.d.mts +36 -4
- package/lib/helpers/config.d.mts +11 -8
- package/lib/helpers/pattern-matcher.d.mts +3 -1
- package/lib/helpers/utilities.d.mts +4 -2
- package/lib/processors/flatten.d.mts +1 -1
- package/package.json +2 -2
- package/slothlet.d.mts +2 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instance-wide event manager (#407).
|
|
3
|
+
* @extends ComponentBase
|
|
4
|
+
* @public
|
|
5
|
+
*/
|
|
6
|
+
export class EventManager extends ComponentBase {
|
|
7
|
+
/**
|
|
8
|
+
* Where this component mounts on the Slothlet instance (`slothlet.handlers.eventManager`).
|
|
9
|
+
* @type {string}
|
|
10
|
+
*/
|
|
11
|
+
static slothletProperty: string;
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} slothlet - Slothlet instance.
|
|
14
|
+
*/
|
|
15
|
+
constructor(slothlet: object);
|
|
16
|
+
/**
|
|
17
|
+
* Subscribe a listener to an event. The subscriber's granted delivery level is resolved from the
|
|
18
|
+
* event-rule pool against its own identity and returned so the caller knows whether it will
|
|
19
|
+
* receive payloads.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} event - Event name to subscribe to.
|
|
22
|
+
* @param {Function} listener - Listener, called `(payload, meta)` on emit. At `notify`, `payload`
|
|
23
|
+
* is `undefined`; `meta` is `{ event, at, instanceID }`.
|
|
24
|
+
* @param {object} [options={}] - Options.
|
|
25
|
+
* @param {boolean} [options.once=false] - Remove the subscription after its first delivery.
|
|
26
|
+
* @returns {{ level: "deny"|"notify"|"allow", off: Function }} The granted level and an unsubscribe
|
|
27
|
+
* function. At `deny` the listener is not registered and `off` is a no-op.
|
|
28
|
+
* @throws {SlothletError} INVALID_ARGUMENT for a non-string event or non-function listener.
|
|
29
|
+
* @public
|
|
30
|
+
*/
|
|
31
|
+
public on(event: string, listener: Function, options?: {
|
|
32
|
+
once?: boolean | undefined;
|
|
33
|
+
}): {
|
|
34
|
+
level: "deny" | "notify" | "allow";
|
|
35
|
+
off: Function;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Subscribe for a single delivery, then auto-unsubscribe. Shorthand for `on(event, listener, { once: true })`.
|
|
39
|
+
* @param {string} event - Event name.
|
|
40
|
+
* @param {Function} listener - Listener.
|
|
41
|
+
* @param {object} [options={}] - Options (merged with `once: true`).
|
|
42
|
+
* @returns {{ level: "deny"|"notify"|"allow", off: Function }} The granted level and unsubscribe.
|
|
43
|
+
* @public
|
|
44
|
+
*/
|
|
45
|
+
public once(event: string, listener: Function, options?: object): {
|
|
46
|
+
level: "deny" | "notify" | "allow";
|
|
47
|
+
off: Function;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Remove a specific listener from an event.
|
|
51
|
+
* @param {string} event - Event name.
|
|
52
|
+
* @param {Function} listener - The listener reference passed to `on`/`once`.
|
|
53
|
+
* @returns {boolean} True if a matching subscription was removed.
|
|
54
|
+
* @public
|
|
55
|
+
*/
|
|
56
|
+
public off(event: string, listener: Function): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Emit an event to its subscribers. NOT gated — any caller may emit; the three-level policy is
|
|
59
|
+
* enforced per subscriber at delivery. Async, fire-and-forget, per-listener error isolation:
|
|
60
|
+
* a throwing listener surfaces a `SlothletWarning` and never affects the others or the emitter.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} event - Event name.
|
|
63
|
+
* @param {*} [payload] - Domain payload, delivered only to `allow`-level subscribers.
|
|
64
|
+
* @returns {Promise<void>} Resolves once all listeners (including async) have settled.
|
|
65
|
+
* @throws {SlothletError} INVALID_ARGUMENT for a non-string event.
|
|
66
|
+
* @public
|
|
67
|
+
*/
|
|
68
|
+
public emit(event: string, payload?: any): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Tear down all subscriptions. Called from `Slothlet.shutdown()`.
|
|
71
|
+
* @returns {void}
|
|
72
|
+
* @public
|
|
73
|
+
*/
|
|
74
|
+
public shutdown(): void;
|
|
75
|
+
#private;
|
|
76
|
+
}
|
|
77
|
+
import { ComponentBase } from "#factories/component-base";
|
|
@@ -39,7 +39,7 @@ export class PermissionManager extends ComponentBase {
|
|
|
39
39
|
caller: string;
|
|
40
40
|
target: string;
|
|
41
41
|
effect: string;
|
|
42
|
-
}, ownerModuleID?: string | null, ruleId?: string | null): string;
|
|
42
|
+
}, ownerModuleID?: string | null, ruleId?: string | null, layer?: null): string;
|
|
43
43
|
/**
|
|
44
44
|
* Remove a permission rule by ID.
|
|
45
45
|
* A module cannot remove rules it owns (immutability).
|
|
@@ -52,6 +52,65 @@ export class PermissionManager extends ComponentBase {
|
|
|
52
52
|
* pm.removeRule("perm-3", "mod_other");
|
|
53
53
|
*/
|
|
54
54
|
removeRule(ruleId: string, callerModuleID?: string | null): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Add an event rule (#407). Separate three-level construct from {@link addRule}: `effect` is
|
|
57
|
+
* "deny" | "notify" | "allow". Matched most-specific-wins with the same layered tiebreak; the
|
|
58
|
+
* EventManager uses it to resolve each subscriber's delivery level.
|
|
59
|
+
*
|
|
60
|
+
* @param {object} rule - The event-rule definition.
|
|
61
|
+
* @param {string} rule.caller - Glob matching the SUBSCRIBER's api path.
|
|
62
|
+
* @param {string} rule.event - Glob matching the event name.
|
|
63
|
+
* @param {"deny"|"notify"|"allow"} rule.effect - Delivery level.
|
|
64
|
+
* @param {object|Function|Array<object|Function>} [rule.condition] - Optional condition(s).
|
|
65
|
+
* @param {string|null} [ownerModuleID=null] - Owning module id ("__builtin__" for framework).
|
|
66
|
+
* @param {string|null} [ruleId=null] - Optional rule id to reuse (for reload replay).
|
|
67
|
+
* @param {string|null} [layer=null] - Explicit precedence layer; derived from ownerModuleID when null.
|
|
68
|
+
* @returns {string} The rule id.
|
|
69
|
+
* @throws {SlothletError} INVALID_PERMISSION_RULE if the rule is malformed.
|
|
70
|
+
* @example
|
|
71
|
+
* pm.addEventRule({ caller: "orders.**", event: "orders.*", effect: "allow" }, "mod_orders", null, "manifest");
|
|
72
|
+
*/
|
|
73
|
+
addEventRule(rule: {
|
|
74
|
+
caller: string;
|
|
75
|
+
event: string;
|
|
76
|
+
effect: "deny" | "notify" | "allow";
|
|
77
|
+
condition?: object | Function | (object | Function)[] | undefined;
|
|
78
|
+
}, ownerModuleID?: string | null, ruleId?: string | null, layer?: string | null): string;
|
|
79
|
+
/**
|
|
80
|
+
* Remove an event rule by id. A module cannot remove an event rule it owns (immutability),
|
|
81
|
+
* mirroring {@link removeRule}.
|
|
82
|
+
*
|
|
83
|
+
* @param {string} ruleId - The event-rule id.
|
|
84
|
+
* @param {string|null} [callerModuleID=null] - Module id attempting removal.
|
|
85
|
+
* @returns {boolean} True if a rule was removed.
|
|
86
|
+
* @throws {SlothletError} PERMISSION_SELF_MODIFY if the caller owns the rule.
|
|
87
|
+
*/
|
|
88
|
+
removeEventRule(ruleId: string, callerModuleID?: string | null): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Resolve the delivery level for a subscriber/event pair (#407): "deny" | "notify" | "allow".
|
|
91
|
+
* Most-specific-wins with the layered tiebreak (see {@link RULE_LAYER_RANK}); falls back to the
|
|
92
|
+
* base default (`permissions.events.default`, built-in "notify") when no rule matches. A host
|
|
93
|
+
* subscription (no module caller) is trusted like a host-initiated call and always resolves "allow".
|
|
94
|
+
*
|
|
95
|
+
* @param {string|null} subscriberPath - The subscribing module's api path, or null for the host.
|
|
96
|
+
* @param {string} eventName - The event name being subscribed to / emitted.
|
|
97
|
+
* @param {object|null} [runtimeContext=null] - Per-request ALS context for condition evaluation.
|
|
98
|
+
* @returns {"deny"|"notify"|"allow"} The resolved delivery level.
|
|
99
|
+
*/
|
|
100
|
+
resolveEventLevel(subscriberPath: string | null, eventName: string, runtimeContext?: object | null): "deny" | "notify" | "allow";
|
|
101
|
+
/**
|
|
102
|
+
* Monotonic epoch that changes on every event-rule mutation. The EventManager caches resolved
|
|
103
|
+
* levels against it and re-resolves only when it changes.
|
|
104
|
+
* @returns {number} The current event-rules epoch.
|
|
105
|
+
*/
|
|
106
|
+
get eventRulesEpoch(): number;
|
|
107
|
+
/**
|
|
108
|
+
* Whether any event rule carries a condition. When false, a resolved subscriber level depends
|
|
109
|
+
* only on the rule set and can be safely cached against {@link eventRulesEpoch}; when true, the
|
|
110
|
+
* level can vary with the per-request context and must be re-resolved on each emit.
|
|
111
|
+
* @returns {boolean} True if at least one event rule has a condition.
|
|
112
|
+
*/
|
|
113
|
+
get hasConditionalEventRules(): boolean;
|
|
55
114
|
/**
|
|
56
115
|
* Silent query: check whether a caller path is allowed to access a target path.
|
|
57
116
|
* Never emits lifecycle or debug events — use {@link enforceAccess} at actual enforcement points.
|
|
@@ -317,6 +317,37 @@ export class RoutineManager extends ComponentBase {
|
|
|
317
317
|
* @public
|
|
318
318
|
*/
|
|
319
319
|
public runPath(apiPath: string, args?: any[], routine?: object): Promise<any>;
|
|
320
|
+
/**
|
|
321
|
+
* Run ONE contributor at an exact api path, selected by its `moduleID` (#400 — the mechanism
|
|
322
|
+
* behind `api.<path>.<name>.for(key)`). Unlike {@link runPath}, this deliberately does NOT apply
|
|
323
|
+
* the `stackRoutines` owner-filter: selecting a specific co-owner by key is the whole point, so a
|
|
324
|
+
* contributor that would lose the shared-path collision still runs when addressed directly. The
|
|
325
|
+
* contributor runs in its own extent + identity via {@link #runEntries}, exactly as a normal
|
|
326
|
+
* stacked/cascade run does — so `self.*` and permission checks resolve against that contributor.
|
|
327
|
+
* @param {string} apiPath - Exact composed api path.
|
|
328
|
+
* @param {string} key - The contributor's `moduleID`.
|
|
329
|
+
* @param {Array} [args] - Arguments forwarded to the selected contributor.
|
|
330
|
+
* @param {object} [routine] - The routine config the selector was built for; entries are filtered
|
|
331
|
+
* by {@link #matches} so a different routine sharing the same exact apiPath isn't selected.
|
|
332
|
+
* @returns {Promise<*>} The selected contributor's return value.
|
|
333
|
+
* @throws {SlothletError} `INVALID_ARGUMENT` when no contributor with `moduleID === key` matches
|
|
334
|
+
* this routine at this path; `ROUTINE_FAILED` when the selected contributor throws.
|
|
335
|
+
* @public
|
|
336
|
+
*/
|
|
337
|
+
public runPathFor(apiPath: string, key: string, args?: any[], routine?: object): Promise<any>;
|
|
338
|
+
/**
|
|
339
|
+
* List the `moduleID`s of every contributor to a routine at an exact api path, in registration
|
|
340
|
+
* order (#400 — the mechanism behind `api.<path>.<name>.contributors`). Symmetry with
|
|
341
|
+
* `versioning.list(path)`: it lets a host discover which co-owners it can address via
|
|
342
|
+
* {@link runPathFor} / `.for(key)`. Enumerates every contribution regardless of the
|
|
343
|
+
* `stackRoutines` owner-filter, since `.for(key)` can address any of them.
|
|
344
|
+
* @param {string} apiPath - Exact composed api path.
|
|
345
|
+
* @param {object} [routine] - The routine config; entries are filtered by {@link #matches} so a
|
|
346
|
+
* different routine sharing the same exact apiPath isn't counted.
|
|
347
|
+
* @returns {string[]} The contributors' `moduleID`s at this path, in registration order.
|
|
348
|
+
* @public
|
|
349
|
+
*/
|
|
350
|
+
public contributorsAt(apiPath: string, routine?: object): string[];
|
|
320
351
|
/**
|
|
321
352
|
* Run the root cascade for a routine: every matching contribution anywhere, grouped by exact
|
|
322
353
|
* api path — with `stackRoutines: true`, contributors colliding at the same path all run
|
|
@@ -396,10 +427,11 @@ export class RoutineManager extends ComponentBase {
|
|
|
396
427
|
public runStartupModeRoutines(): Promise<void>;
|
|
397
428
|
/**
|
|
398
429
|
* 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
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
430
|
+
* stacked callable, and (re)attach every configured routine's root cascade at the api root
|
|
431
|
+
* (using the routine's `name` verbatim as the property key — a dotted or `^`-prefixed name is
|
|
432
|
+
* reachable via bracket notation, e.g. `api["^ext.*.initialize"]`; only a bare name gets clean
|
|
433
|
+
* dot-notation access). A routine configured with `cascade: false` (#400) gets no root cascade.
|
|
434
|
+
* The cascade lives only at the root `api.<name>`, never mirrored onto `api.slothlet.*` (#399).
|
|
403
435
|
*
|
|
404
436
|
* @description
|
|
405
437
|
* Safe to call repeatedly — at the end of initial `load()`, again after every
|
package/lib/helpers/config.d.mts
CHANGED
|
@@ -242,12 +242,13 @@ export class Config extends ComponentBase {
|
|
|
242
242
|
* a root cascade runs every matching contribution anywhere, ordered per the entry's `order`.
|
|
243
243
|
* See `docs/LIFECYCLE.md` ("Routines") for the full contract.
|
|
244
244
|
*
|
|
245
|
-
* Each entry normalizes to `{ name, mode, recursive, order }` — `recursive
|
|
246
|
-
* always present on the normalized output, even when the raw entry omitted them:
|
|
247
|
-
* - `"name"` (string, no `:`) → `{ name, mode: "manual", recursive: false, order: "mount" }`.
|
|
248
|
-
* - `"name:mode"` (string, split once on the first `:`) → `{ name, mode, recursive: false, order: <mode-defaulted
|
|
249
|
-
* - `{ name, mode?, recursive?, order? }` (object) → `mode` defaults to `"manual"`, `recursive`
|
|
250
|
-
* `false`,
|
|
245
|
+
* Each entry normalizes to `{ name, mode, recursive, order, cascade }` — `recursive`, `order` and
|
|
246
|
+
* `cascade` are always present on the normalized output, even when the raw entry omitted them:
|
|
247
|
+
* - `"name"` (string, no `:`) → `{ name, mode: "manual", recursive: false, order: "mount", cascade: true }`.
|
|
248
|
+
* - `"name:mode"` (string, split once on the first `:`) → `{ name, mode, recursive: false, order: <mode-defaulted>, cascade: true }`.
|
|
249
|
+
* - `{ name, mode?, recursive?, order?, cascade? }` (object) → `mode` defaults to `"manual"`, `recursive`
|
|
250
|
+
* to `false`, `order` to {@link DEFAULT_ROUTINE_ORDER_BY_MODE}`[mode]`, and `cascade` to `true` when each
|
|
251
|
+
* is omitted. `cascade: false` (#400) suppresses the root `api.<name>()` run-all cascade for that routine.
|
|
251
252
|
*
|
|
252
253
|
* Providing `routines` at all REPLACES {@link DEFAULT_ROUTINES} — that is the off-switch
|
|
253
254
|
* (`routines: []` disables every routine). Omitting the option keeps the built-in defaults.
|
|
@@ -258,8 +259,8 @@ export class Config extends ComponentBase {
|
|
|
258
259
|
* normalizes to an equivalent list — same values, always freshly-built objects (never the same
|
|
259
260
|
* references) — so `reload()` can safely re-feed it.
|
|
260
261
|
*
|
|
261
|
-
* @param {undefined|null|Array<string|{name: string, mode?: string, recursive?: boolean, order?: string}>} routines - Raw `routines` option.
|
|
262
|
-
* @returns {Array<{name: string, mode: "manual"|"startup"|"shutdown"|"destroy", recursive: boolean, order: "mount"|"depth"}>} Normalized routines list.
|
|
262
|
+
* @param {undefined|null|Array<string|{name: string, mode?: string, recursive?: boolean, order?: string, cascade?: boolean}>} routines - Raw `routines` option.
|
|
263
|
+
* @returns {Array<{name: string, mode: "manual"|"startup"|"shutdown"|"destroy", recursive: boolean, order: "mount"|"depth", cascade: boolean}>} Normalized routines list.
|
|
263
264
|
* @throws {SlothletError} INVALID_CONFIG when the shape is invalid, a name is empty/reserved/an invalid glob, or a mode/order is unrecognized.
|
|
264
265
|
* @public
|
|
265
266
|
*
|
|
@@ -283,11 +284,13 @@ export class Config extends ComponentBase {
|
|
|
283
284
|
mode?: string;
|
|
284
285
|
recursive?: boolean;
|
|
285
286
|
order?: string;
|
|
287
|
+
cascade?: boolean;
|
|
286
288
|
}>): Array<{
|
|
287
289
|
name: string;
|
|
288
290
|
mode: "manual" | "startup" | "shutdown" | "destroy";
|
|
289
291
|
recursive: boolean;
|
|
290
292
|
order: "mount" | "depth";
|
|
293
|
+
cascade: boolean;
|
|
291
294
|
}>;
|
|
292
295
|
/**
|
|
293
296
|
* Normalize permissions configuration.
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Compile a glob pattern into a matcher function.
|
|
3
3
|
* Supports: * (any chars except .), ** (any chars including .), ? (single char),
|
|
4
|
-
* {a,b} brace expansion, !pattern negation
|
|
4
|
+
* {a,b} brace expansion, !pattern whole-pattern negation, and !(a|b) scoped exclusion —
|
|
5
|
+
* a per-segment complement matching any single segment except the listed literal alternatives
|
|
6
|
+
* (e.g. `admin.!(initialize)` matches `admin.start` but not `admin.initialize`).
|
|
5
7
|
*
|
|
6
8
|
* @param {string} pattern - Glob pattern
|
|
7
9
|
* @param {object} [options={}] - Options
|
|
@@ -38,8 +38,10 @@ export class Utilities extends ComponentBase {
|
|
|
38
38
|
* Strategy:
|
|
39
39
|
* 1. Try `structuredClone` — fast and spec-correct for plain data.
|
|
40
40
|
* 2. Fall back to a manual recursive copy for Proxies, callables, and other
|
|
41
|
-
* non-serialisable objects
|
|
42
|
-
*
|
|
41
|
+
* non-serialisable objects. Callables (functions / callable Proxies) are kept
|
|
42
|
+
* BY REFERENCE — they cannot be reconstructed from a property copy — while the
|
|
43
|
+
* surrounding data is still deep-cloned; errors on individual property clones are
|
|
44
|
+
* swallowed and the original reference is retained for that key.
|
|
43
45
|
*
|
|
44
46
|
* @param {unknown} obj - Value to clone.
|
|
45
47
|
* @returns {unknown} Deep clone of `obj`.
|
|
@@ -78,7 +78,7 @@ export class Flatten extends ComponentBase {
|
|
|
78
78
|
};
|
|
79
79
|
/**
|
|
80
80
|
* Build category-level flattening decisions.
|
|
81
|
-
* Implements conditions C10-
|
|
81
|
+
* Implements conditions C10-C24 from buildCategoryDecisions().
|
|
82
82
|
* @param {object} options - Category options
|
|
83
83
|
* @param {string} options.categoryName - Category name
|
|
84
84
|
* @param {object} options.mod - Module exports
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cldmv/slothlet-types",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"description": "TypeScript declaration files (.d.mts) for @cldmv/slothlet. Install alongside @cldmv/slothlet for editor and type-checker support.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"slothlet",
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
"LICENSE"
|
|
80
80
|
],
|
|
81
81
|
"peerDependencies": {
|
|
82
|
-
"@cldmv/slothlet": "3.
|
|
82
|
+
"@cldmv/slothlet": "3.17.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
85
85
|
"@cldmv/slothlet": {
|
package/slothlet.d.mts
CHANGED
|
@@ -156,13 +156,14 @@ export type SlothletOptions = {
|
|
|
156
156
|
*/
|
|
157
157
|
collectLifecycleHooks?: boolean | undefined;
|
|
158
158
|
/**
|
|
159
|
-
* - Stackable lifecycle routines (#341). Every mounted module exporting a function matching a configured routine name is composed into one callable at its exact composed api path, plus a root cascade (`self.<name>()`, i.e. `api.<name>()`) that runs every matching contribution anywhere. Entries: `"name"` (mode `"manual"`), `"name:mode"`, or `{ name, mode?, recursive?, order? }` (`recursive`/`order` only settable via the object form). `name` is mount-relative by default (a bare name matches only a mount's own top level; a dotted name matches a fixed relative sub-path, or with `recursive: true` any depth within the mount); a `^`-prefixed name is root-anchored, matched via glob (`*`, `**`, `{}`, `!`) against the full api path, crossing mount boundaries. `order` (`"mount"` | `"depth"`, mode-defaulted) controls the root cascade's grouping order. Providing `routines` at all REPLACES the built-in defaults (`slothlet.defaults.routines`: `initialize` → `startup`, `shutdown` → `shutdown`) — spread `slothlet.defaults.routines` to extend them instead, or pass `[]` to disable every routine. Every configured routine is always wrapped and directly callable regardless of `autoRoutines`. Whether two or more contributors colliding at the identical api path all run is governed by `stackRoutines` (#365), independent of `collisionMode` — by default only the single contribution that actually owns that path runs, matching ordinary collision behavior. A throwing contributor doesn't stop the chain — every contributor runs (best-effort), and one aggregate `ROUTINE_FAILED` error is thrown afterward if any failed. See [LIFECYCLE.md](docs/LIFECYCLE.md#routines).
|
|
159
|
+
* - Stackable lifecycle routines (#341). Every mounted module exporting a function matching a configured routine name is composed into one callable at its exact composed api path, plus a root cascade (`self.<name>()`, i.e. `api.<name>()`) that runs every matching contribution anywhere. Entries: `"name"` (mode `"manual"`), `"name:mode"`, or `{ name, mode?, recursive?, order?, cascade? }` (`recursive`/`order`/`cascade` only settable via the object form). `name` is mount-relative by default (a bare name matches only a mount's own top level; a dotted name matches a fixed relative sub-path, or with `recursive: true` any depth within the mount); a `^`-prefixed name is root-anchored, matched via glob (`*`, `**`, `{}`, `!`) against the full api path, crossing mount boundaries. `order` (`"mount"` | `"depth"`, mode-defaulted) controls the root cascade's grouping order. `cascade` (default `true`) controls whether the root `api.<name>()` run-all cascade is created at all — set `cascade: false` (#400) for a per-entity lifecycle routine, where the host must invoke exactly one co-owner by key via `api.<path>.<name>.for(moduleID)(...)` rather than a run-all; every stacked path also exposes `api.<path>.<name>.contributors` (the moduleIDs present there). `.for(key)` runs that one contributor in its own extent/identity with the args passed straight through, and bypasses the `stackRoutines` owner-filter so a specific co-owner runs even if it lost the shared-path collision. Providing `routines` at all REPLACES the built-in defaults (`slothlet.defaults.routines`: `initialize` → `startup`, `shutdown` → `shutdown`) — spread `slothlet.defaults.routines` to extend them instead, or pass `[]` to disable every routine. Every configured routine is always wrapped and directly callable regardless of `autoRoutines`. Whether two or more contributors colliding at the identical api path all run is governed by `stackRoutines` (#365), independent of `collisionMode` — by default only the single contribution that actually owns that path runs, matching ordinary collision behavior. A throwing contributor doesn't stop the chain — every contributor runs (best-effort), and one aggregate `ROUTINE_FAILED` error is thrown afterward if any failed. See [LIFECYCLE.md](docs/LIFECYCLE.md#routines).
|
|
160
160
|
*/
|
|
161
161
|
routines?: (string | {
|
|
162
162
|
name: string;
|
|
163
163
|
mode?: ("manual" | "startup" | "shutdown" | "destroy");
|
|
164
164
|
recursive?: boolean;
|
|
165
165
|
order?: ("mount" | "depth");
|
|
166
|
+
cascade?: boolean;
|
|
166
167
|
})[] | undefined;
|
|
167
168
|
/**
|
|
168
169
|
* - The non-deprecated replacement for `collectLifecycleHooks`. TEMPORARY v3-compat default (#341): `false` for now, so a project upgrading sees no behavior change from a pre-existing nested leaf that happens to share a routine's name (e.g. `shutdown`) — it stays stacked and directly callable, but does not start auto-firing. When `true`, every `mode: "startup"` routine's cascade runs at the end of compose, and every `mode: "shutdown"`/`"destroy"` routine's cascade runs on the corresponding dispose call. Planned to default to `true` in v4 (`collectLifecycleHooks` removed at the same time) — see [LIFECYCLE.md](docs/LIFECYCLE.md#routines).
|