@cldmv/slothlet 3.2.3 → 3.3.2

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 (138) hide show
  1. package/README.md +22 -9
  2. package/REFERENCE.md +23 -0
  3. package/dist/lib/builders/api-assignment.mjs +1 -589
  4. package/dist/lib/builders/api_builder.mjs +1 -1155
  5. package/dist/lib/builders/builder.mjs +1 -78
  6. package/dist/lib/builders/modes-processor.mjs +1 -1800
  7. package/dist/lib/errors.mjs +9 -211
  8. package/dist/lib/factories/component-base.mjs +1 -80
  9. package/dist/lib/factories/context.mjs +1 -22
  10. package/dist/lib/handlers/api-cache-manager.mjs +1 -200
  11. package/dist/lib/handlers/api-manager.mjs +1 -2513
  12. package/dist/lib/handlers/context-async.mjs +1 -168
  13. package/dist/lib/handlers/context-live.mjs +1 -168
  14. package/dist/lib/handlers/hook-manager.mjs +1 -773
  15. package/dist/lib/handlers/lifecycle-token.mjs +1 -28
  16. package/dist/lib/handlers/lifecycle.mjs +1 -115
  17. package/dist/lib/handlers/materialize-manager.mjs +1 -48
  18. package/dist/lib/handlers/metadata.mjs +1 -501
  19. package/dist/lib/handlers/ownership.mjs +1 -322
  20. package/dist/lib/handlers/permission-manager.mjs +17 -0
  21. package/dist/lib/handlers/unified-wrapper.mjs +1 -3042
  22. package/dist/lib/handlers/version-manager.mjs +1 -885
  23. package/dist/lib/helpers/class-instance-wrapper.mjs +1 -109
  24. package/dist/lib/helpers/config.mjs +1 -355
  25. package/dist/lib/helpers/eventemitter-context.mjs +1 -349
  26. package/dist/lib/helpers/hint-detector.mjs +1 -47
  27. package/dist/lib/helpers/modes-utils.mjs +1 -37
  28. package/dist/lib/helpers/pattern-matcher.mjs +17 -0
  29. package/dist/lib/helpers/resolve-from-caller.mjs +1 -169
  30. package/dist/lib/helpers/sanitize.mjs +1 -340
  31. package/dist/lib/helpers/utilities.mjs +1 -70
  32. package/dist/lib/i18n/languages/de-de.json +21 -1
  33. package/dist/lib/i18n/languages/en-gb.json +21 -1
  34. package/dist/lib/i18n/languages/en-us.json +21 -1
  35. package/dist/lib/i18n/languages/es-mx.json +21 -1
  36. package/dist/lib/i18n/languages/fr-fr.json +21 -1
  37. package/dist/lib/i18n/languages/hi-in.json +21 -1
  38. package/dist/lib/i18n/languages/ja-jp.json +21 -1
  39. package/dist/lib/i18n/languages/ko-kr.json +21 -1
  40. package/dist/lib/i18n/languages/pt-br.json +21 -1
  41. package/dist/lib/i18n/languages/ru-ru.json +21 -1
  42. package/dist/lib/i18n/languages/zh-cn.json +21 -1
  43. package/dist/lib/i18n/translations.mjs +1 -126
  44. package/dist/lib/modes/eager.mjs +1 -59
  45. package/dist/lib/modes/lazy.mjs +1 -81
  46. package/dist/lib/processors/flatten.mjs +1 -437
  47. package/dist/lib/processors/loader.mjs +1 -339
  48. package/dist/lib/processors/type-generator.mjs +1 -275
  49. package/dist/lib/processors/typescript.mjs +1 -172
  50. package/dist/lib/runtime/runtime-asynclocalstorage.mjs +1 -113
  51. package/dist/lib/runtime/runtime-livebindings.mjs +1 -78
  52. package/dist/lib/runtime/runtime.mjs +1 -102
  53. package/dist/slothlet.mjs +1 -808
  54. package/package.json +37 -31
  55. package/types/dist/lib/builders/api-assignment.d.mts +3 -92
  56. package/types/dist/lib/builders/api-assignment.d.mts.map +1 -1
  57. package/types/dist/lib/builders/api_builder.d.mts +102 -91
  58. package/types/dist/lib/builders/api_builder.d.mts.map +1 -1
  59. package/types/dist/lib/builders/builder.d.mts +1 -55
  60. package/types/dist/lib/builders/builder.d.mts.map +1 -1
  61. package/types/dist/lib/builders/modes-processor.d.mts +3 -27
  62. package/types/dist/lib/builders/modes-processor.d.mts.map +1 -1
  63. package/types/dist/lib/errors.d.mts +19 -109
  64. package/types/dist/lib/errors.d.mts.map +1 -1
  65. package/types/dist/lib/factories/component-base.d.mts +7 -177
  66. package/types/dist/lib/factories/component-base.d.mts.map +1 -1
  67. package/types/dist/lib/factories/context.d.mts +4 -22
  68. package/types/dist/lib/factories/context.d.mts.map +1 -1
  69. package/types/dist/lib/handlers/api-cache-manager.d.mts +20 -203
  70. package/types/dist/lib/handlers/api-cache-manager.d.mts.map +1 -1
  71. package/types/dist/lib/handlers/api-manager.d.mts +33 -408
  72. package/types/dist/lib/handlers/api-manager.d.mts.map +1 -1
  73. package/types/dist/lib/handlers/context-async.d.mts +23 -61
  74. package/types/dist/lib/handlers/context-async.d.mts.map +1 -1
  75. package/types/dist/lib/handlers/context-live.d.mts +22 -59
  76. package/types/dist/lib/handlers/context-live.d.mts.map +1 -1
  77. package/types/dist/lib/handlers/hook-manager.d.mts +46 -185
  78. package/types/dist/lib/handlers/hook-manager.d.mts.map +1 -1
  79. package/types/dist/lib/handlers/lifecycle-token.d.mts +3 -48
  80. package/types/dist/lib/handlers/lifecycle-token.d.mts.map +1 -1
  81. package/types/dist/lib/handlers/lifecycle.d.mts +5 -82
  82. package/types/dist/lib/handlers/lifecycle.d.mts.map +1 -1
  83. package/types/dist/lib/handlers/materialize-manager.d.mts +8 -70
  84. package/types/dist/lib/handlers/materialize-manager.d.mts.map +1 -1
  85. package/types/dist/lib/handlers/metadata.d.mts +17 -221
  86. package/types/dist/lib/handlers/metadata.d.mts.map +1 -1
  87. package/types/dist/lib/handlers/ownership.d.mts +44 -160
  88. package/types/dist/lib/handlers/ownership.d.mts.map +1 -1
  89. package/types/dist/lib/handlers/permission-manager.d.mts +47 -0
  90. package/types/dist/lib/handlers/permission-manager.d.mts.map +1 -0
  91. package/types/dist/lib/handlers/unified-wrapper.d.mts +26 -239
  92. package/types/dist/lib/handlers/unified-wrapper.d.mts.map +1 -1
  93. package/types/dist/lib/handlers/version-manager.d.mts +28 -225
  94. package/types/dist/lib/handlers/version-manager.d.mts.map +1 -1
  95. package/types/dist/lib/helpers/class-instance-wrapper.d.mts +2 -52
  96. package/types/dist/lib/helpers/class-instance-wrapper.d.mts.map +1 -1
  97. package/types/dist/lib/helpers/config.d.mts +125 -123
  98. package/types/dist/lib/helpers/config.d.mts.map +1 -1
  99. package/types/dist/lib/helpers/eventemitter-context.d.mts +3 -29
  100. package/types/dist/lib/helpers/eventemitter-context.d.mts.map +1 -1
  101. package/types/dist/lib/helpers/hint-detector.d.mts +2 -15
  102. package/types/dist/lib/helpers/hint-detector.d.mts.map +1 -1
  103. package/types/dist/lib/helpers/modes-utils.d.mts +3 -30
  104. package/types/dist/lib/helpers/modes-utils.d.mts.map +1 -1
  105. package/types/dist/lib/helpers/pattern-matcher.d.mts +4 -0
  106. package/types/dist/lib/helpers/pattern-matcher.d.mts.map +1 -0
  107. package/types/dist/lib/helpers/resolve-from-caller.d.mts +3 -27
  108. package/types/dist/lib/helpers/resolve-from-caller.d.mts.map +1 -1
  109. package/types/dist/lib/helpers/sanitize.d.mts +4 -92
  110. package/types/dist/lib/helpers/sanitize.d.mts.map +1 -1
  111. package/types/dist/lib/helpers/utilities.d.mts +4 -52
  112. package/types/dist/lib/helpers/utilities.d.mts.map +1 -1
  113. package/types/dist/lib/i18n/translations.d.mts +4 -37
  114. package/types/dist/lib/i18n/translations.d.mts.map +1 -1
  115. package/types/dist/lib/modes/eager.d.mts +8 -30
  116. package/types/dist/lib/modes/eager.d.mts.map +1 -1
  117. package/types/dist/lib/modes/lazy.d.mts +10 -43
  118. package/types/dist/lib/modes/lazy.d.mts.map +1 -1
  119. package/types/dist/lib/processors/flatten.d.mts +56 -107
  120. package/types/dist/lib/processors/flatten.d.mts.map +1 -1
  121. package/types/dist/lib/processors/loader.d.mts +6 -41
  122. package/types/dist/lib/processors/loader.d.mts.map +1 -1
  123. package/types/dist/lib/processors/type-generator.d.mts +2 -16
  124. package/types/dist/lib/processors/type-generator.d.mts.map +1 -1
  125. package/types/dist/lib/processors/typescript.d.mts +6 -53
  126. package/types/dist/lib/processors/typescript.d.mts.map +1 -1
  127. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts +3 -71
  128. package/types/dist/lib/runtime/runtime-asynclocalstorage.d.mts.map +1 -1
  129. package/types/dist/lib/runtime/runtime-livebindings.d.mts +2 -37
  130. package/types/dist/lib/runtime/runtime-livebindings.d.mts.map +1 -1
  131. package/types/dist/lib/runtime/runtime.d.mts +3 -39
  132. package/types/dist/lib/runtime/runtime.d.mts.map +1 -1
  133. package/types/dist/slothlet.d.mts +3 -249
  134. package/types/dist/slothlet.d.mts.map +1 -1
  135. package/types/index.d.mts +36 -16
  136. package/types/index.d.mts.map +1 -0
  137. package/AGENT-USAGE.md +0 -736
  138. package/docs/API-RULES.md +0 -712
@@ -14,776 +14,4 @@
14
14
  limitations under the License.
15
15
  */
16
16
 
17
-
18
-
19
-
20
-
21
- import { ComponentBase } from "@cldmv/slothlet/factories/component-base";
22
-
23
-
24
-
25
-
26
- const ERROR_HOOK_PROCESSED = Symbol.for("@cldmv/slothlet/hook-error-processed");
27
-
28
-
29
- export class HookManager extends ComponentBase {
30
-
31
- static slothletProperty = "hookManager";
32
-
33
-
34
- #hooks = {
35
- before: { before: {}, primary: {}, after: {} },
36
- after: { before: {}, primary: {}, after: {} },
37
- always: { before: {}, primary: {}, after: {} },
38
- error: { before: {}, primary: {}, after: {} }
39
- };
40
-
41
-
42
- #byId = new Map();
43
-
44
-
45
- #idCounter = 0;
46
-
47
-
48
- #validTypes = new Set(["before", "after", "always", "error"]);
49
-
50
-
51
- #validSubsets = new Set(["before", "primary", "after"]);
52
-
53
-
54
- constructor(slothlet) {
55
- super(slothlet);
56
-
57
-
58
- const hookConfig = slothlet.config?.hook || { enabled: false, pattern: "**", suppressErrors: false };
59
-
60
- this.enabled = hookConfig.enabled;
61
- this.defaultPattern = hookConfig.pattern || "**";
62
- this.suppressErrors = hookConfig.suppressErrors || false;
63
- this.enabledPatterns = new Set();
64
- this.patternFilterActive = false;
65
-
66
- this.hooks = new Map();
67
- this.registrationOrder = 0;
68
- this.reportedErrors = new WeakSet();
69
- }
70
-
71
-
72
- on(typePattern, handler, options = {}) {
73
-
74
- let { type, pattern } = this.#parseTypePattern(typePattern);
75
-
76
-
77
- if (options.pattern !== undefined) {
78
- pattern = options.pattern;
79
- }
80
-
81
-
82
- if (!this.#validTypes.has(type)) {
83
- throw new this.slothlet.SlothletError("INVALID_HOOK_TYPE", {
84
- type,
85
- validTypes: Array.from(this.#validTypes)
86
- });
87
- }
88
-
89
-
90
- if (typeof handler !== "function") {
91
- throw new this.slothlet.SlothletError("INVALID_HOOK_HANDLER", {
92
- receivedType: typeof handler,
93
- validationError: true
94
- });
95
- }
96
-
97
-
98
- const id = options.id || this.#generateId();
99
-
100
-
101
- if (this.#byId.has(id)) {
102
- throw new this.slothlet.SlothletError("DUPLICATE_HOOK_ID", { id, validationError: true });
103
- }
104
-
105
-
106
- const subset = options.subset || "primary";
107
- if (!this.#validSubsets.has(subset)) {
108
- throw new this.slothlet.SlothletError("INVALID_HOOK_SUBSET", {
109
- subset,
110
- validSubsets: Array.from(this.#validSubsets)
111
- });
112
- }
113
-
114
-
115
- this.#compilePattern(pattern);
116
-
117
-
118
- const hook = {
119
- id,
120
- type,
121
- pattern,
122
- handler,
123
- priority: options.priority || 0,
124
- subset,
125
- enabled: true,
126
- _compiled: null
127
- };
128
-
129
-
130
- const typeIndex = this.#hooks[type];
131
- const subsetIndex = typeIndex[subset];
132
-
133
- if (!subsetIndex[pattern]) {
134
- subsetIndex[pattern] = [];
135
- }
136
- subsetIndex[pattern].push(hook);
137
-
138
-
139
- this.#byId.set(id, hook);
140
-
141
- return id;
142
- }
143
-
144
-
145
- remove(filter = {}) {
146
- let removed = 0;
147
-
148
-
149
- if (filter.id) {
150
- const hook = this.#byId.get(filter.id);
151
- if (hook) {
152
- this.#removeHook(hook);
153
- removed = 1;
154
- }
155
- return removed;
156
- }
157
-
158
-
159
- const types = filter.type ? [filter.type] : Array.from(this.#validTypes);
160
-
161
- for (const type of types) {
162
- const typeIndex = this.#hooks[type];
163
-
164
- for (const subset of ["before", "primary", "after"]) {
165
- const subsetIndex = typeIndex[subset];
166
-
167
-
168
- if (filter.pattern) {
169
- const patternHooks = subsetIndex[filter.pattern];
170
- if (patternHooks) {
171
- removed += patternHooks.length;
172
- patternHooks.forEach((hook) => this.#byId.delete(hook.id));
173
- delete subsetIndex[filter.pattern];
174
- }
175
- } else {
176
-
177
- for (const pattern in subsetIndex) {
178
- const patternHooks = subsetIndex[pattern];
179
- removed += patternHooks.length;
180
- patternHooks.forEach((hook) => this.#byId.delete(hook.id));
181
- delete subsetIndex[pattern];
182
- }
183
- }
184
- }
185
- }
186
-
187
- return removed;
188
- }
189
-
190
-
191
- enable(filter = {}) {
192
-
193
- if (typeof filter === "string") {
194
- filter = { pattern: filter };
195
- }
196
-
197
-
198
- this.enabled = true;
199
-
200
- return this.#setEnabledState(filter, true);
201
- }
202
-
203
-
204
- disable(filter = {}) {
205
-
206
- if (typeof filter === "string") {
207
- filter = { pattern: filter };
208
- }
209
-
210
-
211
- if (Object.keys(filter).length === 0) {
212
- this.enabled = false;
213
- }
214
- return this.#setEnabledState(filter, false);
215
- }
216
-
217
-
218
- list(filter = {}) {
219
-
220
- if (typeof filter === "string") {
221
-
222
- if (this.#validTypes.has(filter)) {
223
- filter = { type: filter };
224
- } else {
225
-
226
- filter = { pattern: filter };
227
- }
228
- }
229
-
230
- const hooks = [];
231
-
232
-
233
- if (filter.id) {
234
- const hook = this.#byId.get(filter.id);
235
- if (hook && (filter.enabled === undefined || hook.enabled === filter.enabled)) {
236
- hooks.push(this.#serializeHook(hook));
237
- }
238
- return { registeredHooks: hooks };
239
- }
240
-
241
-
242
- const types = filter.type ? [filter.type] : Array.from(this.#validTypes);
243
-
244
-
245
- let patternMatcher = null;
246
- if (filter.pattern) {
247
- patternMatcher = this.#compilePattern(filter.pattern);
248
- }
249
-
250
- for (const type of types) {
251
- const typeIndex = this.#hooks[type];
252
-
253
- for (const subset of ["before", "primary", "after"]) {
254
- const subsetIndex = typeIndex[subset];
255
-
256
-
257
- for (const pattern in subsetIndex) {
258
- const patternHooks = subsetIndex[pattern];
259
- for (const hook of patternHooks) {
260
-
261
- if (filter.enabled !== undefined && hook.enabled !== filter.enabled) {
262
- continue;
263
- }
264
-
265
-
266
- if (patternMatcher && !patternMatcher(hook.pattern)) {
267
- continue;
268
- }
269
-
270
- hooks.push(this.#serializeHook(hook));
271
- }
272
- }
273
- }
274
- }
275
-
276
- return { registeredHooks: hooks };
277
- }
278
-
279
-
280
- getHooksForPath(type, apiPath) {
281
-
282
- if (this.enabled === false) {
283
- return [];
284
- }
285
-
286
- const typeIndex = this.#hooks[type];
287
- if (!typeIndex) {
288
- return [];
289
- }
290
-
291
- const hooks = [];
292
-
293
-
294
- for (const subset of ["before", "primary", "after"]) {
295
- const subsetIndex = typeIndex[subset];
296
- const subsetHooks = [];
297
-
298
-
299
- for (const pattern in subsetIndex) {
300
- const patternHooks = subsetIndex[pattern];
301
-
302
-
303
- if (pattern === apiPath) {
304
- subsetHooks.push(...patternHooks.filter((h) => h.enabled));
305
- continue;
306
- }
307
-
308
-
309
- for (const hook of patternHooks) {
310
- if (!hook.enabled) continue;
311
-
312
- if (!hook._compiled) {
313
- hook._compiled = this.#compilePattern(hook.pattern);
314
- }
315
-
316
- if (hook._compiled(apiPath)) {
317
- subsetHooks.push(hook);
318
- }
319
- }
320
- }
321
-
322
-
323
- subsetHooks.sort((a, b) => b.priority - a.priority);
324
-
325
-
326
- hooks.push(...subsetHooks);
327
- }
328
-
329
- return hooks;
330
- }
331
-
332
-
333
- executeBeforeHooks(path, args, api, ctx) {
334
- const hooks = this.getHooksForPath("before", path);
335
-
336
- for (const hook of hooks) {
337
- try {
338
- const result = hook.handler({ path, args, api, ctx });
339
-
340
-
341
- if (result && typeof result === "object" && typeof result.then === "function") {
342
- throw new this.SlothletError("HOOK_BEFORE_RETURNED_PROMISE", { id: hook.id, path }, null, { validationError: true });
343
- }
344
-
345
-
346
- if (result !== undefined && !Array.isArray(result)) {
347
- return { args, shortCircuit: true, value: result };
348
- }
349
-
350
-
351
- if (Array.isArray(result)) {
352
- args = result;
353
- }
354
- } catch (error) {
355
-
356
- const sourceInfo = {
357
- type: "before",
358
- subset: hook.subset,
359
- hookTag: hook.id,
360
- hookId: hook.id,
361
- timestamp: Date.now(),
362
- stack: error.stack
363
- };
364
- this.executeErrorHooks(path, error, sourceInfo, args, api, ctx);
365
-
366
- if (!this.suppressErrors) {
367
- throw error;
368
- }
369
-
370
- return { args, shortCircuit: true, value: undefined };
371
- }
372
- }
373
-
374
- return { args, shortCircuit: false };
375
- }
376
-
377
-
378
- executeAfterHooks(path, result, args, api, ctx) {
379
- const hooks = this.getHooksForPath("after", path);
380
- const originalResult = result;
381
- let currentResult = result;
382
-
383
- for (const hook of hooks) {
384
- try {
385
- const hookContext = {
386
- path,
387
- args,
388
- result: currentResult,
389
- api,
390
- ctx
391
- };
392
- const transformed = hook.handler(hookContext);
393
-
394
-
395
- if (transformed !== undefined) {
396
- currentResult = transformed;
397
- }
398
- } catch (error) {
399
-
400
- const sourceInfo = {
401
- type: "after",
402
- subset: hook.subset,
403
- hookTag: hook.id,
404
- hookId: hook.id,
405
- timestamp: Date.now(),
406
- stack: error.stack
407
- };
408
- this.executeErrorHooks(path, error, sourceInfo, args, api, ctx);
409
-
410
- if (!this.suppressErrors) {
411
- throw error;
412
- }
413
- }
414
- }
415
-
416
-
417
- if (currentResult === originalResult) {
418
- return { modified: false };
419
- } else {
420
- return { modified: true, result: currentResult };
421
- }
422
- }
423
-
424
-
425
- executeAlwaysHooks(path, args, resultOrError, hasError = false, errors = [], api, ctx) {
426
- const hooks = this.getHooksForPath("always", path);
427
-
428
- for (const hook of hooks) {
429
- try {
430
- hook.handler({
431
- path,
432
- args,
433
- result: hasError ? undefined : resultOrError,
434
- hasError,
435
- errors: errors,
436
- api,
437
- ctx
438
- });
439
- } catch (error) {
440
-
441
- const sourceInfo = {
442
- type: "always",
443
- subset: hook.subset,
444
- hookTag: hook.id,
445
- hookId: hook.id,
446
- timestamp: Date.now(),
447
- stack: error.stack
448
- };
449
- this.executeErrorHooks(path, error, sourceInfo, args, api, ctx);
450
- }
451
- }
452
- }
453
-
454
-
455
- executeErrorHooks(path, error, source, args, api, ctx) {
456
-
457
- if (error && typeof error === "object") {
458
- error[ERROR_HOOK_PROCESSED] = true;
459
- }
460
-
461
- const hooks = this.getHooksForPath("error", path);
462
-
463
- for (const hook of hooks) {
464
- try {
465
- hook.handler({
466
- path,
467
- args,
468
- error,
469
- errorType: error?.constructor?.name || "Error",
470
- source,
471
- timestamp: new Date(),
472
- api,
473
- ctx
474
- });
475
- } catch (hookError) {
476
-
477
- this.slothlet.debug("hooks", `Error hook failed for ${path}:`, hookError);
478
- }
479
- }
480
- }
481
-
482
-
483
- #parseTypePattern(typePattern) {
484
- if (typeof typePattern !== "string") {
485
- throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN", {
486
- typePattern,
487
- expected: "string in format 'type:pattern'"
488
- });
489
- }
490
-
491
- const colonIndex = typePattern.indexOf(":");
492
- if (colonIndex === -1) {
493
- throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN", {
494
- typePattern,
495
- expected: "string in format 'type:pattern' with at least one colon"
496
- });
497
- }
498
-
499
- const type = typePattern.substring(0, colonIndex);
500
- const pattern = typePattern.substring(colonIndex + 1);
501
-
502
- if (!type || !pattern) {
503
- throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN", {
504
- typePattern,
505
- expected: "non-empty type and pattern"
506
- });
507
- }
508
-
509
- return { type, pattern };
510
- }
511
-
512
-
513
- #compilePattern(pattern) {
514
-
515
- const isNegation = pattern.startsWith("!");
516
- if (isNegation) {
517
- pattern = pattern.slice(1);
518
- const matcher = this.#compilePattern(pattern);
519
- return (path) => !matcher(path);
520
- }
521
-
522
-
523
- const expanded = this.#expandBraces(pattern);
524
- if (expanded.length > 1) {
525
-
526
- const matchers = expanded.map((p) => this.#compilePattern(p));
527
- return (path) => matchers.some((m) => m(path));
528
- }
529
-
530
-
531
- pattern = expanded[0];
532
-
533
-
534
- let regexPattern = pattern
535
- .replace(/[+^$()|[\]\\]/g, "\\$&")
536
- .replace(/\*\*/g, "__DOUBLESTAR__")
537
- .replace(/\*/g, "[^.]*")
538
- .replace(/__DOUBLESTAR__/g, ".*")
539
- .replace(/\?/g, ".");
540
-
541
- regexPattern = `^${regexPattern}$`;
542
- const regex = new RegExp(regexPattern);
543
-
544
- return (path) => regex.test(path);
545
- }
546
-
547
-
548
- #expandBraces(pattern, depth = 0, maxDepth = 10) {
549
-
550
- if (depth >= maxDepth) {
551
- throw new this.SlothletError("HOOK_BRACE_EXPANSION_MAX_DEPTH", { maxDepth }, null, { validationError: true });
552
- }
553
-
554
-
555
- const braceStart = pattern.indexOf("{");
556
- if (braceStart === -1) {
557
- return [pattern];
558
- }
559
-
560
-
561
- let braceEnd = -1;
562
- let depth_count = 1;
563
- for (let i = braceStart + 1; i < pattern.length; i++) {
564
- if (pattern[i] === "{") depth_count++;
565
- if (pattern[i] === "}") {
566
- depth_count--;
567
- if (depth_count === 0) {
568
- braceEnd = i;
569
- break;
570
- }
571
- }
572
- }
573
-
574
- if (braceEnd === -1) {
575
- return [pattern];
576
- }
577
-
578
-
579
- const prefix = pattern.slice(0, braceStart);
580
- const braceContent = pattern.slice(braceStart + 1, braceEnd);
581
- const suffix = pattern.slice(braceEnd + 1);
582
-
583
-
584
- const alternatives = this.#splitBraceAlternatives(braceContent);
585
-
586
-
587
- const expanded = [];
588
- for (const alt of alternatives) {
589
- const combined = prefix + alt + suffix;
590
-
591
- const recursiveExpanded = this.#expandBraces(combined, depth + 1, maxDepth);
592
- expanded.push(...recursiveExpanded);
593
- }
594
-
595
- return expanded;
596
- }
597
-
598
-
599
- #splitBraceAlternatives(content) {
600
- const alternatives = [];
601
- let current = "";
602
- let depth = 0;
603
-
604
- for (let i = 0; i < content.length; i++) {
605
- const char = content[i];
606
-
607
- if (char === "{") {
608
- depth++;
609
- current += char;
610
- } else if (char === "}") {
611
- depth--;
612
- current += char;
613
- } else if (char === "," && depth === 0) {
614
- alternatives.push(current);
615
- current = "";
616
- } else {
617
- current += char;
618
- }
619
- }
620
-
621
- if (current) {
622
- alternatives.push(current);
623
- }
624
-
625
- return alternatives;
626
- }
627
-
628
-
629
- getCompilePatternForDiagnostics() {
630
- return this.#compilePattern.bind(this);
631
- }
632
-
633
-
634
- #generateId() {
635
- return `hook-${++this.#idCounter}`;
636
- }
637
-
638
-
639
- #removeHook(hook) {
640
- const typeIndex = this.#hooks[hook.type];
641
- const subsetIndex = typeIndex[hook.subset];
642
- const patternHooks = subsetIndex[hook.pattern];
643
-
644
-
645
-
646
-
647
-
648
-
649
-
650
-
651
- if (!patternHooks) {
652
- throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT", {
653
- hookId: hook.id,
654
- type: hook.type,
655
- subset: hook.subset,
656
- pattern: hook.pattern,
657
- detail: "patternHooks array missing from subsetIndex — #byId and #hooks are desynced"
658
- });
659
- }
660
-
661
- const index = patternHooks.indexOf(hook);
662
-
663
-
664
-
665
-
666
-
667
-
668
-
669
- if (index === -1) {
670
- throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT", {
671
- hookId: hook.id,
672
- type: hook.type,
673
- subset: hook.subset,
674
- pattern: hook.pattern,
675
- detail: "hook object not found in patternHooks array — #byId and #hooks are desynced"
676
- });
677
- }
678
-
679
- patternHooks.splice(index, 1);
680
-
681
-
682
- if (patternHooks.length === 0) {
683
- delete subsetIndex[hook.pattern];
684
- }
685
-
686
- this.#byId.delete(hook.id);
687
- }
688
-
689
-
690
- #setEnabledState(filter, enabled) {
691
- let affected = 0;
692
-
693
-
694
- if (filter.id) {
695
- const hook = this.#byId.get(filter.id);
696
- if (hook) {
697
- hook.enabled = enabled;
698
- affected = 1;
699
- }
700
- return affected;
701
- }
702
-
703
-
704
- const types = filter.type ? [filter.type] : Array.from(this.#validTypes);
705
-
706
- for (const type of types) {
707
- const typeIndex = this.#hooks[type];
708
-
709
- for (const subset of ["before", "primary", "after"]) {
710
- const subsetIndex = typeIndex[subset];
711
-
712
-
713
- if (filter.pattern) {
714
- const patternHooks = subsetIndex[filter.pattern] || [];
715
- for (const hook of patternHooks) {
716
- hook.enabled = enabled;
717
- affected++;
718
- }
719
- } else {
720
-
721
- for (const pattern in subsetIndex) {
722
- const patternHooks = subsetIndex[pattern];
723
- for (const hook of patternHooks) {
724
- hook.enabled = enabled;
725
- affected++;
726
- }
727
- }
728
- }
729
- }
730
- }
731
-
732
- return affected;
733
- }
734
-
735
-
736
- #serializeHook(hook) {
737
- return {
738
- id: hook.id,
739
- type: hook.type,
740
- pattern: hook.pattern,
741
- priority: hook.priority,
742
- subset: hook.subset,
743
- enabled: hook.enabled
744
-
745
- };
746
- }
747
-
748
-
749
- exportHooks() {
750
- const registrations = [];
751
- for (const hook of this.#byId.values()) {
752
- registrations.push({
753
- typePattern: `${hook.type}:${hook.pattern}`,
754
- handler: hook.handler,
755
- options: {
756
- id: hook.id,
757
- priority: hook.priority,
758
- subset: hook.subset
759
- },
760
- enabled: hook.enabled
761
- });
762
- }
763
- return registrations;
764
- }
765
-
766
-
767
- importHooks(registrations) {
768
- if (!Array.isArray(registrations)) return;
769
- for (const reg of registrations) {
770
- this.on(reg.typePattern, reg.handler, reg.options);
771
- if (!reg.enabled) {
772
- this.disable({ id: reg.options.id });
773
- }
774
- }
775
- }
776
-
777
-
778
- async shutdown() {
779
-
780
- this.#hooks = {
781
- before: { before: {}, primary: {}, after: {} },
782
- after: { before: {}, primary: {}, after: {} },
783
- always: { before: {}, primary: {}, after: {} },
784
- error: { before: {}, primary: {}, after: {} }
785
- };
786
- this.#byId.clear();
787
- this.#idCounter = 0;
788
- }
789
- }
17
+ import{ComponentBase}from"@cldmv/slothlet/factories/component-base";import{compilePattern}from"@cldmv/slothlet/helpers/pattern-matcher";const ERROR_HOOK_PROCESSED=Symbol.for("@cldmv/slothlet/hook-error-processed");class HookManager extends ComponentBase{static slothletProperty="hookManager";#hooks={before:{before:{},primary:{},after:{}},after:{before:{},primary:{},after:{}},always:{before:{},primary:{},after:{}},error:{before:{},primary:{},after:{}}};#byId=new Map;#idCounter=0;#validTypes=new Set(["before","after","always","error"]);#validSubsets=new Set(["before","primary","after"]);constructor(slothlet){super(slothlet);const hookConfig=slothlet.config?.hook||{enabled:false,pattern:"**",suppressErrors:false};this.enabled=hookConfig.enabled;this.defaultPattern=hookConfig.pattern||"**";this.suppressErrors=hookConfig.suppressErrors||false;this.enabledPatterns=new Set;this.patternFilterActive=false;this.hooks=new Map;this.registrationOrder=0;this.reportedErrors=new WeakSet}on(typePattern,handler,options={}){let{type,pattern}=this.#parseTypePattern(typePattern);if(options.pattern!==void 0){pattern=options.pattern}if(!this.#validTypes.has(type)){throw new this.slothlet.SlothletError("INVALID_HOOK_TYPE",{type,validTypes:Array.from(this.#validTypes)})}if(typeof handler!=="function"){throw new this.slothlet.SlothletError("INVALID_HOOK_HANDLER",{receivedType:typeof handler,validationError:true})}const id=options.id||this.#generateId();if(this.#byId.has(id)){throw new this.slothlet.SlothletError("DUPLICATE_HOOK_ID",{id,validationError:true})}const subset=options.subset||"primary";if(!this.#validSubsets.has(subset)){throw new this.slothlet.SlothletError("INVALID_HOOK_SUBSET",{subset,validSubsets:Array.from(this.#validSubsets)})}this.#compilePattern(pattern);const hook={id,type,pattern,handler,priority:options.priority||0,subset,enabled:true,_compiled:null};const typeIndex=this.#hooks[type];const subsetIndex=typeIndex[subset];if(!subsetIndex[pattern]){subsetIndex[pattern]=[]}subsetIndex[pattern].push(hook);this.#byId.set(id,hook);return id}remove(filter={}){let removed=0;if(filter.id){const hook=this.#byId.get(filter.id);if(hook){this.#removeHook(hook);removed=1}return removed}const types=filter.type?[filter.type]:Array.from(this.#validTypes);for(const type of types){const typeIndex=this.#hooks[type];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];if(filter.pattern){const patternHooks=subsetIndex[filter.pattern];if(patternHooks){removed+=patternHooks.length;patternHooks.forEach(hook=>this.#byId.delete(hook.id));delete subsetIndex[filter.pattern]}}else{for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];removed+=patternHooks.length;patternHooks.forEach(hook=>this.#byId.delete(hook.id));delete subsetIndex[pattern]}}}}return removed}enable(filter={}){if(typeof filter==="string"){filter={pattern:filter}}this.enabled=true;return this.#setEnabledState(filter,true)}disable(filter={}){if(typeof filter==="string"){filter={pattern:filter}}if(Object.keys(filter).length===0){this.enabled=false}return this.#setEnabledState(filter,false)}list(filter={}){if(typeof filter==="string"){if(this.#validTypes.has(filter)){filter={type:filter}}else{filter={pattern:filter}}}const hooks=[];if(filter.id){const hook=this.#byId.get(filter.id);if(hook&&(filter.enabled===void 0||hook.enabled===filter.enabled)){hooks.push(this.#serializeHook(hook))}return{registeredHooks:hooks}}const types=filter.type?[filter.type]:Array.from(this.#validTypes);let patternMatcher=null;if(filter.pattern){patternMatcher=this.#compilePattern(filter.pattern)}for(const type of types){const typeIndex=this.#hooks[type];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];for(const hook of patternHooks){if(filter.enabled!==void 0&&hook.enabled!==filter.enabled){continue}if(patternMatcher&&!patternMatcher(hook.pattern)){continue}hooks.push(this.#serializeHook(hook))}}}}return{registeredHooks:hooks}}getHooksForPath(type,apiPath){if(this.enabled===false){return[]}const typeIndex=this.#hooks[type];if(!typeIndex){return[]}const hooks=[];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];const subsetHooks=[];for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];if(pattern===apiPath){subsetHooks.push(...patternHooks.filter(h=>h.enabled));continue}for(const hook of patternHooks){if(!hook.enabled)continue;if(!hook._compiled){hook._compiled=this.#compilePattern(hook.pattern)}if(hook._compiled(apiPath)){subsetHooks.push(hook)}}}subsetHooks.sort((a,b)=>b.priority-a.priority);hooks.push(...subsetHooks)}return hooks}executeBeforeHooks(path,args,api,ctx){const hooks=this.getHooksForPath("before",path);for(const hook of hooks){try{const result=hook.handler({path,args,api,ctx});if(result&&typeof result==="object"&&typeof result.then==="function"){throw new this.SlothletError("HOOK_BEFORE_RETURNED_PROMISE",{id:hook.id,path},null,{validationError:true})}if(result!==void 0&&!Array.isArray(result)){return{args,shortCircuit:true,value:result}}if(Array.isArray(result)){args=result}}catch(error){const sourceInfo={type:"before",subset:hook.subset,hookTag:hook.id,hookId:hook.id,timestamp:Date.now(),stack:error.stack};this.executeErrorHooks(path,error,sourceInfo,args,api,ctx);if(!this.suppressErrors){throw error}return{args,shortCircuit:true,value:void 0}}}return{args,shortCircuit:false}}executeAfterHooks(path,result,args,api,ctx){const hooks=this.getHooksForPath("after",path);const originalResult=result;let currentResult=result;for(const hook of hooks){try{const hookContext={path,args,result:currentResult,api,ctx};const transformed=hook.handler(hookContext);if(transformed!==void 0){currentResult=transformed}}catch(error){const sourceInfo={type:"after",subset:hook.subset,hookTag:hook.id,hookId:hook.id,timestamp:Date.now(),stack:error.stack};this.executeErrorHooks(path,error,sourceInfo,args,api,ctx);if(!this.suppressErrors){throw error}}}if(currentResult===originalResult){return{modified:false}}else{return{modified:true,result:currentResult}}}executeAlwaysHooks(path,args,resultOrError,hasError=false,errors=[],api,ctx){const hooks=this.getHooksForPath("always",path);for(const hook of hooks){try{hook.handler({path,args,result:hasError?void 0:resultOrError,hasError,errors,api,ctx})}catch(error){const sourceInfo={type:"always",subset:hook.subset,hookTag:hook.id,hookId:hook.id,timestamp:Date.now(),stack:error.stack};this.executeErrorHooks(path,error,sourceInfo,args,api,ctx)}}}executeErrorHooks(path,error,source,args,api,ctx){if(error&&typeof error==="object"){error[ERROR_HOOK_PROCESSED]=true}const hooks=this.getHooksForPath("error",path);for(const hook of hooks){try{hook.handler({path,args,error,errorType:error?.constructor?.name||"Error",source,timestamp:new Date,api,ctx})}catch(hookError){this.slothlet.debug("hooks",`Error hook failed for ${path}:`,hookError)}}}#parseTypePattern(typePattern){if(typeof typePattern!=="string"){throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN",{typePattern,expected:"string in format 'type:pattern'"})}const colonIndex=typePattern.indexOf(":");if(colonIndex===-1){throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN",{typePattern,expected:"string in format 'type:pattern' with at least one colon"})}const type=typePattern.substring(0,colonIndex);const pattern=typePattern.substring(colonIndex+1);if(!type||!pattern){throw new this.slothlet.SlothletError("INVALID_TYPE_PATTERN",{typePattern,expected:"non-empty type and pattern"})}return{type,pattern}}#compilePattern(pattern){return compilePattern(pattern,{onMaxDepth:maxDepth=>{throw new this.SlothletError("HOOK_BRACE_EXPANSION_MAX_DEPTH",{maxDepth},null,{validationError:true})}})}getCompilePatternForDiagnostics(){return this.#compilePattern.bind(this)}#generateId(){return`hook-${++this.#idCounter}`}#removeHook(hook){const typeIndex=this.#hooks[hook.type];const subsetIndex=typeIndex[hook.subset];const patternHooks=subsetIndex[hook.pattern];if(!patternHooks){throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT",{hookId:hook.id,type:hook.type,subset:hook.subset,pattern:hook.pattern,detail:"patternHooks array missing from subsetIndex \u2014 #byId and #hooks are desynced"})}const index=patternHooks.indexOf(hook);if(index===-1){throw new this.slothlet.SlothletError("INTERNAL_HOOK_STATE_CORRUPT",{hookId:hook.id,type:hook.type,subset:hook.subset,pattern:hook.pattern,detail:"hook object not found in patternHooks array \u2014 #byId and #hooks are desynced"})}patternHooks.splice(index,1);if(patternHooks.length===0){delete subsetIndex[hook.pattern]}this.#byId.delete(hook.id)}#setEnabledState(filter,enabled){let affected=0;if(filter.id){const hook=this.#byId.get(filter.id);if(hook){hook.enabled=enabled;affected=1}return affected}const types=filter.type?[filter.type]:Array.from(this.#validTypes);for(const type of types){const typeIndex=this.#hooks[type];for(const subset of["before","primary","after"]){const subsetIndex=typeIndex[subset];if(filter.pattern){const patternHooks=subsetIndex[filter.pattern]||[];for(const hook of patternHooks){hook.enabled=enabled;affected++}}else{for(const pattern in subsetIndex){const patternHooks=subsetIndex[pattern];for(const hook of patternHooks){hook.enabled=enabled;affected++}}}}}return affected}#serializeHook(hook){return{id:hook.id,type:hook.type,pattern:hook.pattern,priority:hook.priority,subset:hook.subset,enabled:hook.enabled}}exportHooks(){const registrations=[];for(const hook of this.#byId.values()){registrations.push({typePattern:`${hook.type}:${hook.pattern}`,handler:hook.handler,options:{id:hook.id,priority:hook.priority,subset:hook.subset},enabled:hook.enabled})}return registrations}importHooks(registrations){if(!Array.isArray(registrations))return;for(const reg of registrations){this.on(reg.typePattern,reg.handler,reg.options);if(!reg.enabled){this.disable({id:reg.options.id})}}}async shutdown(){this.#hooks={before:{before:{},primary:{},after:{}},after:{before:{},primary:{},after:{}},always:{before:{},primary:{},after:{}},error:{before:{},primary:{},after:{}}};this.#byId.clear();this.#idCounter=0}}export{HookManager};