@cldmv/slothlet 2.6.3 → 2.7.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.
@@ -0,0 +1,389 @@
1
+ /*
2
+ Copyright 2025 CLDMV/Shinrai
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+
18
+
19
+
20
+
21
+ const MAX_BRACE_NESTING = 10;
22
+
23
+
24
+ export class HookManager {
25
+
26
+ constructor(enabled = true, defaultPattern = "**", options = {}) {
27
+ this.enabled = enabled;
28
+ this.defaultPattern = defaultPattern;
29
+ this.suppressErrors = options.suppressErrors || false;
30
+ this.hooks = new Map();
31
+ this.registrationOrder = 0;
32
+ this.reportedErrors = new WeakSet();
33
+ }
34
+
35
+
36
+ on(name, type, handler, options = {}) {
37
+ const priority = options.priority ?? 100;
38
+ const pattern = options.pattern || this.defaultPattern;
39
+ const compiledPattern = this._compilePattern(pattern);
40
+ const order = this.registrationOrder++;
41
+
42
+ this.hooks.set(name, {
43
+ tag: name,
44
+ type,
45
+ handler,
46
+ priority,
47
+ pattern,
48
+ compiledPattern,
49
+ order
50
+ });
51
+
52
+ return name;
53
+ }
54
+
55
+
56
+ cleanup() {
57
+ this.hooks.clear();
58
+ this.reportedErrors = new WeakSet();
59
+ this.registrationOrder = 0;
60
+ this.enabled = false;
61
+ }
62
+
63
+
64
+ off(nameOrPattern) {
65
+
66
+ if (this.hooks.has(nameOrPattern)) {
67
+ return this.hooks.delete(nameOrPattern);
68
+ }
69
+
70
+
71
+ const compiled = this._compilePattern(nameOrPattern);
72
+ let removed = false;
73
+ for (const key of [...this.hooks.keys()]) {
74
+ if (this._matchPattern(compiled, key)) {
75
+ this.hooks.delete(key);
76
+ removed = true;
77
+ }
78
+ }
79
+ return removed;
80
+ }
81
+
82
+
83
+ clear(type) {
84
+ if (type === undefined) {
85
+ this.hooks.clear();
86
+ this.registrationOrder = 0;
87
+ return;
88
+ }
89
+
90
+
91
+ for (const [name, hook] of this.hooks) {
92
+ if (hook.type === type) {
93
+ this.hooks.delete(name);
94
+ }
95
+ }
96
+ }
97
+
98
+
99
+ list(type) {
100
+ const result = [];
101
+ for (const [name, hook] of this.hooks) {
102
+ if (type === undefined || hook.type === type) {
103
+ result.push({
104
+ name,
105
+ type: hook.type,
106
+ priority: hook.priority,
107
+ pattern: hook.pattern,
108
+ order: hook.order
109
+ });
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+
115
+
116
+ enable(pattern) {
117
+ this.enabled = true;
118
+ if (pattern !== undefined) {
119
+ this.defaultPattern = pattern;
120
+ }
121
+ }
122
+
123
+
124
+ disable() {
125
+ this.enabled = false;
126
+ }
127
+
128
+
129
+ executeBeforeHooks(path, args) {
130
+ const hooks = this._getMatchingHooks("before", path);
131
+ let currentArgs = args;
132
+
133
+ for (const hook of hooks) {
134
+ try {
135
+ const result = hook.handler({ path, args: currentArgs });
136
+
137
+
138
+ if (result === undefined) {
139
+ continue;
140
+ }
141
+
142
+
143
+ if (Array.isArray(result)) {
144
+ currentArgs = result;
145
+ continue;
146
+ }
147
+
148
+
149
+ return { cancelled: true, value: result, args: currentArgs };
150
+ } catch (error) {
151
+
152
+ this.reportedErrors.add(error);
153
+ this.executeErrorHooks(path, error, {
154
+ type: "before",
155
+ hookId: hook.id,
156
+ hookTag: hook.tag
157
+ });
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ return { cancelled: false, args: currentArgs };
163
+ }
164
+
165
+
166
+ executeAfterHooks(path, initialResult) {
167
+ const hooks = this._getMatchingHooks("after", path);
168
+ let currentResult = initialResult;
169
+
170
+ for (const hook of hooks) {
171
+ try {
172
+ const transformed = hook.handler({ path, result: currentResult });
173
+
174
+ if (transformed !== undefined) {
175
+ currentResult = transformed;
176
+ }
177
+ } catch (error) {
178
+
179
+ this.reportedErrors.add(error);
180
+ this.executeErrorHooks(path, error, {
181
+ type: "after",
182
+ hookId: hook.id,
183
+ hookTag: hook.tag
184
+ });
185
+ throw error;
186
+ }
187
+ }
188
+
189
+ return currentResult;
190
+ }
191
+
192
+
193
+ executeAlwaysHooks(path, result, errors = []) {
194
+ const hooks = this._getMatchingHooks("always", path);
195
+
196
+ for (const hook of hooks) {
197
+ try {
198
+ hook.handler({
199
+ path,
200
+ result,
201
+ hasError: errors.length > 0,
202
+ errors
203
+ });
204
+ } catch (error) {
205
+
206
+ this.executeErrorHooks(path, error, {
207
+ type: "always",
208
+ hookId: hook.id,
209
+ hookTag: hook.tag
210
+ });
211
+
212
+ }
213
+ }
214
+ }
215
+
216
+
217
+ executeErrorHooks(path, error, source = { type: "unknown" }) {
218
+ const hooks = this._getMatchingHooks("error", path);
219
+
220
+
221
+ const errorContext = {
222
+ path,
223
+ error,
224
+ errorType: error.constructor ? error.constructor.name : "Error",
225
+ source: {
226
+ type: source.type || "unknown",
227
+ hookId: source.hookId,
228
+ hookTag: source.hookTag,
229
+ timestamp: Date.now(),
230
+ stack: error.stack
231
+ }
232
+ };
233
+
234
+ for (const hook of hooks) {
235
+ try {
236
+ hook.handler(errorContext);
237
+ } catch (hookError) {
238
+
239
+ console.error(`Error in error hook for ${path}:`, hookError);
240
+ }
241
+ }
242
+ }
243
+
244
+
245
+ _getMatchingHooks(type, path) {
246
+ const matching = [];
247
+
248
+ for (const [hookId, hook] of this.hooks.entries()) {
249
+ if (hook.type !== type) continue;
250
+ if (!this._matchPattern(hook.compiledPattern, path)) continue;
251
+ matching.push({ ...hook, id: hookId });
252
+ }
253
+
254
+
255
+ matching.sort((a, b) => {
256
+ if (a.priority !== b.priority) {
257
+ return b.priority - a.priority;
258
+ }
259
+ return a.order - b.order;
260
+ });
261
+
262
+ return matching;
263
+ }
264
+
265
+
266
+ _compilePattern(pattern) {
267
+
268
+ if (pattern.startsWith("!")) {
269
+ return {
270
+ negation: true,
271
+ regex: this._compilePattern(pattern.slice(1))
272
+ };
273
+ }
274
+
275
+
276
+ const expanded = this._expandBraces(pattern);
277
+ if (expanded.length > 1) {
278
+
279
+ const regexes = expanded.map((p) => this._patternToRegex(p));
280
+ return new RegExp(`^(?:${regexes.join("|")})$`);
281
+ }
282
+
283
+
284
+ return new RegExp(`^${this._patternToRegex(expanded[0])}$`);
285
+ }
286
+
287
+
288
+ _expandBraces(pattern, depth = 0) {
289
+ if (depth > MAX_BRACE_NESTING) {
290
+ throw new Error(`Brace expansion exceeds maximum nesting depth of ${MAX_BRACE_NESTING}`);
291
+ }
292
+
293
+ const braceStart = pattern.indexOf("{");
294
+ if (braceStart === -1) {
295
+ return [pattern];
296
+ }
297
+
298
+
299
+ let braceEnd = -1;
300
+ let nestLevel = 0;
301
+ for (let i = braceStart; i < pattern.length; i++) {
302
+ if (pattern[i] === "{") nestLevel++;
303
+ if (pattern[i] === "}") {
304
+ nestLevel--;
305
+ if (nestLevel === 0) {
306
+ braceEnd = i;
307
+ break;
308
+ }
309
+ }
310
+ }
311
+
312
+ if (braceEnd === -1) {
313
+
314
+ return [pattern];
315
+ }
316
+
317
+ const before = pattern.slice(0, braceStart);
318
+ const inside = pattern.slice(braceStart + 1, braceEnd);
319
+ const after = pattern.slice(braceEnd + 1);
320
+
321
+
322
+ const alternatives = this._splitAlternatives(inside);
323
+
324
+
325
+ const results = [];
326
+ for (const alt of alternatives) {
327
+ const expanded = this._expandBraces(before + alt + after, depth + 1);
328
+ results.push(...expanded);
329
+ }
330
+
331
+ return results;
332
+ }
333
+
334
+
335
+ _splitAlternatives(str) {
336
+ const alternatives = [];
337
+ let current = "";
338
+ let nestLevel = 0;
339
+
340
+ for (let i = 0; i < str.length; i++) {
341
+ const char = str[i];
342
+ if (char === "{") nestLevel++;
343
+ if (char === "}") nestLevel--;
344
+
345
+ if (char === "," && nestLevel === 0) {
346
+ alternatives.push(current);
347
+ current = "";
348
+ } else {
349
+ current += char;
350
+ }
351
+ }
352
+
353
+ if (current) {
354
+ alternatives.push(current);
355
+ }
356
+
357
+ return alternatives;
358
+ }
359
+
360
+
361
+ _patternToRegex(pattern) {
362
+
363
+ let regex = pattern.replace(/[+?^${}()|[\]\\]/g, "\\$&");
364
+
365
+
366
+ regex = regex.replace(/\./g, "\\.");
367
+
368
+
369
+ regex = regex.replace(/\*\*/g, "___DOUBLESTAR___");
370
+
371
+
372
+ regex = regex.replace(/\*/g, "([^\\.]+)");
373
+
374
+
375
+ regex = regex.replace(/___DOUBLESTAR___/g, ".*?");
376
+
377
+ return regex;
378
+ }
379
+
380
+
381
+ _matchPattern(compiledPattern, path) {
382
+ if (compiledPattern.negation) {
383
+
384
+ return !this._matchPattern(compiledPattern.regex, path);
385
+ }
386
+
387
+ return compiledPattern.test(path);
388
+ }
389
+ }
@@ -341,6 +341,10 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
341
341
  if (prop === "_materialize") return _materialize;
342
342
  if (prop === "then") return undefined;
343
343
 
344
+ if (prop === "__slothletPath") {
345
+ return pathParts.length > 0 ? pathParts.join(".") : undefined;
346
+ }
347
+
344
348
  if (materialized) {
345
349
  if (materialized && (typeof materialized === "object" || typeof materialized === "function")) return materialized[prop];
346
350
  return undefined;
@@ -348,10 +352,12 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
348
352
 
349
353
  if (!inFlight) inFlight = _materialize();
350
354
 
355
+ const apiPath = pathParts.length > 0 ? `${pathParts.join(".")}.${String(prop)}` : String(prop);
356
+
351
357
 
352
358
 
353
359
 
354
- return new Proxy(
360
+ const propertyProxy = new Proxy(
355
361
 
356
362
  function lazy_propertyAccessor(...args) {
357
363
  return inFlight.then(
@@ -360,12 +366,7 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
360
366
  const value = resolved ? resolved[prop] : undefined;
361
367
  if (typeof value === "function") {
362
368
 
363
- const ctx = instance.boundapi?.__ctx;
364
- if (ctx) {
365
- return runWithCtx(ctx, value, this, args);
366
- } else {
367
- return value.apply(this, args);
368
- }
369
+ return value.apply(this, args);
369
370
  }
370
371
  return value;
371
372
  }
@@ -377,6 +378,10 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
377
378
  if (subProp === "name") return `lazy_${prop}`;
378
379
  if (subProp === "length") return 0;
379
380
 
381
+ if (subProp === "__slothletPath") {
382
+ return pathParts.length > 0 ? `${pathParts.join(".")}.${String(prop)}` : String(prop);
383
+ }
384
+
380
385
  return new Proxy(
381
386
  function lazy_deepPropertyAccessor() {},
382
387
  {
@@ -390,12 +395,8 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
390
395
  const value = materialized[prop];
391
396
  const subValue = value ? value[subProp] : undefined;
392
397
  if (subValue && typeof subValue[nextProp] === "function") {
393
- const ctx = instance.boundapi?.__ctx;
394
- if (ctx) {
395
- return runWithCtx(ctx, subValue[nextProp], thisArg, args);
396
- } else {
397
- return subValue[nextProp].apply(thisArg, args);
398
- }
398
+
399
+ return subValue[nextProp].apply(thisArg, args);
399
400
  }
400
401
  return subValue ? subValue[nextProp] : undefined;
401
402
  }
@@ -423,12 +424,7 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
423
424
  const value = materialized[prop];
424
425
  if (value && typeof value[subProp] === "function") {
425
426
 
426
- const ctx = instance.boundapi?.__ctx;
427
- if (ctx) {
428
- return runWithCtx(ctx, value[subProp], thisArg, args);
429
- } else {
430
- return value[subProp].apply(thisArg, args);
431
- }
427
+ return value[subProp].apply(thisArg, args);
432
428
  }
433
429
  return value ? value[subProp] : undefined;
434
430
  }
@@ -457,6 +453,16 @@ function createFolderProxy({ subDirPath, key, parent, instance, depth, maxDepth,
457
453
  }
458
454
  }
459
455
  );
456
+
457
+
458
+ Object.defineProperty(propertyProxy, "__slothletPath", {
459
+ value: apiPath,
460
+ writable: false,
461
+ enumerable: false,
462
+ configurable: true
463
+ });
464
+
465
+ return propertyProxy;
460
466
  },
461
467
  has(_t, prop) {
462
468
  if (materialized && (typeof materialized === "object" || typeof materialized === "function")) return prop in materialized;
@@ -33,10 +33,81 @@ enableAlsForEventEmitters(als);
33
33
 
34
34
  export const runWithCtx = (ctx, fn, thisArg, args) => {
35
35
 
36
+ if (!ctx.hookManager?.enabled || !fn.__slothletPath) {
37
+ const runtime_runInALS = () => {
38
+ const result = Reflect.apply(fn, thisArg, args);
39
+ return result;
40
+ };
41
+ return als.run(ctx, runtime_runInALS);
42
+ }
43
+
44
+
45
+ const path = fn.__slothletPath;
46
+
47
+
36
48
  const runtime_runInALS = () => {
37
- const result = Reflect.apply(fn, thisArg, args);
38
- return result;
49
+ try {
50
+
51
+ const beforeResult = ctx.hookManager.executeBeforeHooks(path, args);
52
+
53
+
54
+ if (beforeResult.cancelled) {
55
+ ctx.hookManager.executeAlwaysHooks(path, beforeResult.value, []);
56
+ return beforeResult.value;
57
+ }
58
+
59
+
60
+ const actualArgs = beforeResult.args;
61
+
62
+
63
+ const result = Reflect.apply(fn, thisArg, actualArgs);
64
+
65
+
66
+ if (result && typeof result === "object" && typeof result.then === "function") {
67
+ return result.then(
68
+ (resolvedResult) => {
69
+
70
+ const finalResult = ctx.hookManager.executeAfterHooks(path, resolvedResult);
71
+ ctx.hookManager.executeAlwaysHooks(path, finalResult, []);
72
+ return finalResult;
73
+ },
74
+ (error) => {
75
+
76
+ if (!ctx.hookManager.reportedErrors.has(error)) {
77
+ ctx.hookManager.reportedErrors.add(error);
78
+ ctx.hookManager.executeErrorHooks(path, error, { type: "function" });
79
+ }
80
+
81
+ ctx.hookManager.executeAlwaysHooks(path, undefined, [error]);
82
+
83
+ if (!ctx.hookManager.suppressErrors) {
84
+ throw error;
85
+ }
86
+ return undefined;
87
+ }
88
+ );
89
+ }
90
+
91
+
92
+ const finalResult = ctx.hookManager.executeAfterHooks(path, result);
93
+ ctx.hookManager.executeAlwaysHooks(path, finalResult, []);
94
+ return finalResult;
95
+ } catch (error) {
96
+
97
+ if (!ctx.hookManager.reportedErrors.has(error)) {
98
+ ctx.hookManager.reportedErrors.add(error);
99
+ ctx.hookManager.executeErrorHooks(path, error, { type: "function" });
100
+ }
101
+
102
+ ctx.hookManager.executeAlwaysHooks(path, undefined, [error]);
103
+
104
+ if (!ctx.hookManager.suppressErrors) {
105
+ throw error;
106
+ }
107
+ return undefined;
108
+ }
39
109
  };
110
+
40
111
  return als.run(ctx, runtime_runInALS);
41
112
  };
42
113
 
@@ -157,7 +228,7 @@ export const makeWrapper = (ctx) => {
157
228
  const cache = new WeakMap();
158
229
  const instanceCache = new WeakMap();
159
230
  const promiseMethodCache = new WeakMap();
160
- const wrap = (val) => {
231
+ const wrap = (val, currentPath = "") => {
161
232
  if (val == null || (typeof val !== "object" && typeof val !== "function")) return val;
162
233
  if (cache.has(val)) return cache.get(val);
163
234
 
@@ -170,17 +241,6 @@ export const makeWrapper = (ctx) => {
170
241
 
171
242
  const proxied = new Proxy(val, {
172
243
  apply(target, thisArg, args) {
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
244
  const result = runWithCtx(ctx, target, thisArg, args);
185
245
 
186
246
 
@@ -205,6 +265,27 @@ export const makeWrapper = (ctx) => {
205
265
  const value = Reflect.get(target, prop, receiver);
206
266
 
207
267
 
268
+ const newPath = currentPath ? `${currentPath}.${String(prop)}` : String(prop);
269
+
270
+
271
+ const isInternalProperty = currentPath === "" && ["hooks", "__ctx", "shutdown", "_impl"].includes(String(prop));
272
+ const isInternalPath = newPath.startsWith("hooks.") || newPath.startsWith("__ctx.") || newPath.startsWith("shutdown.");
273
+
274
+
275
+ if (typeof value === "function" && !value.__slothletPath && !isInternalProperty && !isInternalPath) {
276
+ try {
277
+ Object.defineProperty(value, "__slothletPath", {
278
+ value: newPath,
279
+ writable: false,
280
+ enumerable: false,
281
+ configurable: true
282
+ });
283
+ } catch {
284
+
285
+ }
286
+ }
287
+
288
+
208
289
 
209
290
  const isPromiseMethod = typeof value === "function" && PROMISE_METHODS.has(prop);
210
291
  const isNativePromise = util.types.isPromise(target);
@@ -236,14 +317,14 @@ export const makeWrapper = (ctx) => {
236
317
 
237
318
  const result = Reflect.apply(value, target, wrappedArgs);
238
319
 
239
- return wrap(result);
320
+ return wrap(result, newPath);
240
321
  };
241
322
 
242
323
  targetMethodCache.set(prop, wrappedMethod);
243
324
  return wrappedMethod;
244
325
  }
245
326
 
246
- return wrap(value);
327
+ return wrap(value, newPath);
247
328
  },
248
329
  set(target, prop, value, receiver) {
249
330